@glyphteck/veyl 0.67.0 → 0.68.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 +2000 -429
- package/dist/accountprofiles.js +98 -5
- package/dist/auth.js +1 -1
- package/dist/cli.js +4154 -2080
- package/dist/index.js +4148 -1017
- package/docs/agents.md +9 -1
- package/docs/api.md +52 -2
- package/docs/validation.md +4 -4
- package/docs/vote-market.md +248 -0
- package/examples/codex-agent/agent-instructions.js +19 -0
- package/examples/codex-agent/agent-state.js +357 -0
- package/examples/codex-agent/chat-whitelist.js +104 -0
- package/examples/codex-agent/codex-app-server.js +737 -0
- package/examples/codex-agent/codex-config.js +13 -0
- package/examples/codex-agent/codex-input.js +89 -0
- package/examples/codex-agent/connector.js +478 -0
- package/examples/codex-agent/index.js +140 -0
- package/examples/codex-agent/instance-lock.js +359 -0
- package/examples/codex-agent/readme.md +88 -0
- package/examples/codex-agent/veyl-channel.js +603 -0
- package/package.json +12 -1
- package/readme.md +1 -0
package/dist/account.js
CHANGED
|
@@ -5618,11 +5618,158 @@ function sameText(left, right) {
|
|
|
5618
5618
|
return lowerText(left) === lowerText(right);
|
|
5619
5619
|
}
|
|
5620
5620
|
|
|
5621
|
+
// ../../core/config.js
|
|
5622
|
+
var MS_PER_SECOND = 1000;
|
|
5623
|
+
var MINUTE_MS = 60 * MS_PER_SECOND;
|
|
5624
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
5625
|
+
var DAY_MS = 24 * HOUR_MS;
|
|
5626
|
+
var KIB_BYTES = 1024;
|
|
5627
|
+
var MIB_BYTES = 1024 * 1024;
|
|
5628
|
+
var SATS_PER_BITCOIN = 100000000n;
|
|
5629
|
+
var USERNAME_MAX_CHARS = 12;
|
|
5630
|
+
var PASSWORD_MIN_CHARS = 15;
|
|
5631
|
+
var PASSWORD_MAX_CHARS = 64;
|
|
5632
|
+
var PASSWORD_MAX_BYTES = 4 * 1024;
|
|
5633
|
+
var LOCAL_MEDIA_CACHE_MAX_BYTES = 512 * MIB_BYTES;
|
|
5634
|
+
var LOCAL_PROFILE_CACHE_MAX_ITEMS = 500;
|
|
5635
|
+
var LOCAL_PROFILE_CACHE_MAX_AGE_MS = 30 * DAY_MS;
|
|
5636
|
+
var LOCAL_CHAT_CACHE_MAX_ITEMS = 1000;
|
|
5637
|
+
var LOCAL_CHAT_MESSAGE_CACHE_MAX_VISIBLE = 1000;
|
|
5638
|
+
var LOCAL_AVATAR_CACHE_MAX_BYTES = 128 * MIB_BYTES;
|
|
5639
|
+
var LOCAL_AVATAR_CACHE_MAX_AGE_MS = 30 * DAY_MS;
|
|
5640
|
+
var AVATAR_IMAGE_MAX_BYTES = 256 * KIB_BYTES;
|
|
5641
|
+
var AVATAR_IMAGE_QUALITY_ATTEMPTS = Object.freeze([0.92, 0.84, 0.76, 0.68]);
|
|
5642
|
+
var CHAT_AVATAR_IMAGE_MAX_BYTES = AVATAR_IMAGE_MAX_BYTES;
|
|
5643
|
+
var CHAT_AVATAR_REF_MAX_CHARS = 256;
|
|
5644
|
+
var CHAT_MESSAGE_FILE_CACHE_MAX_BYTES = 64 * MIB_BYTES;
|
|
5645
|
+
var IDLE_CALLBACK_MIN_TIMEOUT_MS = 50;
|
|
5646
|
+
var ATTACHMENT_CACHE_IDLE_TIMEOUT_MS = 2500;
|
|
5647
|
+
var ATTACHMENT_CACHE_FALLBACK_DELAY_MS = 250;
|
|
5648
|
+
var CHAT_UNSAVED_TTL_DAYS = 21;
|
|
5649
|
+
var CHAT_UNSAVED_TTL_MS = CHAT_UNSAVED_TTL_DAYS * DAY_MS;
|
|
5650
|
+
var CHAT_AFTER_SEEN_MS = DAY_MS;
|
|
5651
|
+
var CHAT_MEDIA_TTL_DAYS = CHAT_UNSAVED_TTL_DAYS;
|
|
5652
|
+
var CHAT_MEDIA_TTL_MS = CHAT_MEDIA_TTL_DAYS * DAY_MS;
|
|
5653
|
+
var CHAT_UPLOAD_MAX_BYTES = 64 * MIB_BYTES;
|
|
5654
|
+
var CHAT_AUDIO_TRANSCODE_BITRATE_BPS = 128000;
|
|
5655
|
+
var CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS = 3000000;
|
|
5656
|
+
var CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS = CHAT_AUDIO_TRANSCODE_BITRATE_BPS;
|
|
5657
|
+
var CHAT_MESSAGE_BATCH_SIZE = 40;
|
|
5658
|
+
var CHAT_MESSAGE_QUERY_MAX_DOCS = 120;
|
|
5659
|
+
var CHAT_MESSAGE_MUTATION_MAX_ITEMS = 256;
|
|
5660
|
+
var CHAT_TTL_CLIENT_DELETE_GRACE_MS = MINUTE_MS;
|
|
5661
|
+
var CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS = 1500;
|
|
5662
|
+
var CHAT_BATCH_CLEANUP_IDLE_DELAY_MS = 250;
|
|
5663
|
+
var CHAT_LIST_PAGE_SIZE = 20;
|
|
5664
|
+
var CHAT_LIST_LIVE_COUNT = CHAT_LIST_PAGE_SIZE;
|
|
5665
|
+
var CHAT_INBOX_PING_PAGE_SIZE = 25;
|
|
5666
|
+
var CHAT_INBOX_DISCOVERY_PARALLEL_CHATS = CHAT_INBOX_PING_PAGE_SIZE;
|
|
5667
|
+
var CHAT_INBOX_SETTLEMENT_PARALLEL_CHATS = 4;
|
|
5668
|
+
var CHAT_INBOX_CURSOR_OVERLAP_MS = 5 * MS_PER_SECOND;
|
|
5669
|
+
var CHAT_LIST_CACHE_WRITE_DELAY_MS = 1500;
|
|
5670
|
+
var CHAT_LIST_SNAPSHOT_COALESCE_MS = 80;
|
|
5671
|
+
var CHAT_LIST_COMPLETE_SYNC_DELAY_MS = 250;
|
|
5672
|
+
var CHAT_LIST_LISTENER_RETRY_MS = 1000;
|
|
5673
|
+
var CHAT_TOP_WARM_COUNT = 0;
|
|
5674
|
+
var CHAT_EAGER_WARM_COUNT = 0;
|
|
5675
|
+
var CHAT_WARM_DELAY_MS = 3000;
|
|
5676
|
+
var CHAT_WARM_BATCH_SIZE = CHAT_MESSAGE_BATCH_SIZE;
|
|
5677
|
+
var CHAT_MESSAGE_VIEW_CACHE_SIZE = 30;
|
|
5678
|
+
var CHAT_MEDIA_WARM_MESSAGES_PER_CHAT = CHAT_MESSAGE_BATCH_SIZE;
|
|
5679
|
+
var CHAT_MEDIA_WARM_START_DELAY_MS = 600;
|
|
5680
|
+
var CHAT_MEDIA_WARM_STEP_DELAY_MS = 120;
|
|
5681
|
+
var CHAT_MEDIA_WARM_TYPES = Object.freeze(["img", "gif", "mp4"]);
|
|
5682
|
+
var CHAT_MEDIA_WARM_MAX_BYTES = 0;
|
|
5683
|
+
var CHAT_READ_WRITE_INTERVAL_MS = 2 * MS_PER_SECOND;
|
|
5684
|
+
var CHAT_READ_WRITE_MAX_WAIT_MS = 8 * MS_PER_SECOND;
|
|
5685
|
+
var CHAT_LIVE_PING_INTERVAL_MS = 5 * MS_PER_SECOND;
|
|
5686
|
+
var CHAT_LIVE_SWEEP_INTERVAL_MS = 15 * MS_PER_SECOND;
|
|
5687
|
+
var CHAT_LIVE_STALE_AFTER_MS = 15 * MS_PER_SECOND;
|
|
5688
|
+
var CHAT_LIVE_RECONNECT_MAX_MS = 5 * MS_PER_SECOND;
|
|
5689
|
+
var CHAT_LIVE_TYPING_IDLE_MS = 8 * MS_PER_SECOND;
|
|
5690
|
+
var CHAT_LIVE_TYPING_RENEW_MS = 5 * MS_PER_SECOND;
|
|
5691
|
+
var CHAT_LIVE_COMPOSITION_HANDOFF_MS = 15 * MS_PER_SECOND;
|
|
5692
|
+
var CHAT_LIVE_READ_SEND_INTERVAL_MS = 120;
|
|
5693
|
+
var CHAT_LIVE_READ_SEND_MAX_WAIT_MS = 500;
|
|
5694
|
+
var CHAT_LIVE_READ_WRITE_INTERVAL_MS = 12 * MS_PER_SECOND;
|
|
5695
|
+
var CHAT_LIVE_READ_WRITE_MAX_WAIT_MS = 30 * MS_PER_SECOND;
|
|
5696
|
+
var CHAT_SEND_QUEUE_RATE_LIMIT_COUNT = 12;
|
|
5697
|
+
var CHAT_SEND_QUEUE_RATE_LIMIT_WINDOW_MS = 10 * MS_PER_SECOND;
|
|
5698
|
+
var CHAT_SETTINGS_BODY_MAX_BYTES = 32 * KIB_BYTES;
|
|
5699
|
+
var CHAT_TITLE_STATE_MAX_CHARS = 128;
|
|
5700
|
+
var CHAT_MANIFEST_MAX_BYTES = 256 * KIB_BYTES;
|
|
5701
|
+
var CHAT_RECEIPT_MAX_MEMBERS = 32;
|
|
5702
|
+
var CHAT_ORDINARY_DELIVERY_MAX_MEMBERS = 128;
|
|
5703
|
+
var CHAT_MAX_MEMBERS = 256;
|
|
5704
|
+
var CHAT_LARGE_GROUP_MENTION_MAX_TARGETS = 16;
|
|
5705
|
+
var CHAT_MAX_TEXT_CHARS = 2048;
|
|
5706
|
+
var CHAT_TITLE_INPUT_MAX_CHARS = 16;
|
|
5707
|
+
var CHAT_MAX_REACTIONS = CHAT_MAX_MEMBERS;
|
|
5708
|
+
var SEARCH_DEBOUNCE_MS = 300;
|
|
5709
|
+
var RECENT_PEER_REFRESH_LIMIT = 50;
|
|
5710
|
+
var RECENT_PEER_REFRESH_DELAY_MS = 250;
|
|
5711
|
+
var RECENT_PEER_REFRESH_INTERVAL_MS = 5 * MINUTE_MS;
|
|
5712
|
+
var RECENT_PEER_REFRESH_THROTTLE_MS = 120;
|
|
5713
|
+
var BAN_REFRESH_GRACE_MS = 50;
|
|
5714
|
+
var REQUEST_MONEY_MAX_SATS = SATS_PER_BITCOIN * 100000n;
|
|
5715
|
+
var WALLET_TRANSFER_POLL_MS = 3 * MS_PER_SECOND;
|
|
5716
|
+
var WALLET_ACTIVE_TRANSFER_REFRESH_MS = MS_PER_SECOND;
|
|
5717
|
+
var WALLET_CACHE_HYDRATE_DELAY_MS = 0;
|
|
5718
|
+
var WALLET_BOOT_REFRESH_DELAY_MS = 500;
|
|
5719
|
+
var WALLET_BOOT_CACHED_REFRESH_DELAY_MS = 2500;
|
|
5720
|
+
var WALLET_REGTEST_DEPOSIT_CLAIM_POLL_MS = 20 * MS_PER_SECOND;
|
|
5721
|
+
var WALLET_MAINNET_DEPOSIT_CLAIM_POLL_MS = MINUTE_MS;
|
|
5722
|
+
var WALLET_BALANCE_EVENT_COALESCE_MS = 2 * MS_PER_SECOND;
|
|
5723
|
+
var WALLET_INCOMING_UPDATE_COALESCE_MS = 250;
|
|
5724
|
+
var WALLET_AUTO_CLAIM_MAX_FEE_SATS = 5000;
|
|
5725
|
+
var WALLET_CLAIM_PAGE_SIZE = 100;
|
|
5726
|
+
var WALLET_PENDING_TRANSFER_CLAIM_BATCH_SIZE = 50;
|
|
5727
|
+
var WALLET_PENDING_TRANSFER_STATUS_BATCH_SIZE = 2;
|
|
5728
|
+
var WALLET_PENDING_TRANSFER_COLD_REFRESH_BATCH_SIZE = 50;
|
|
5729
|
+
var WALLET_PENDING_TRANSFER_COLD_REFRESH_INTERVAL_MS = 15 * MS_PER_SECOND;
|
|
5730
|
+
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_QUEUE = 3;
|
|
5731
|
+
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_QUEUE = 10;
|
|
5732
|
+
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_SIZE = 2;
|
|
5733
|
+
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_SIZE = 3;
|
|
5734
|
+
var WALLET_PENDING_TRANSFER_SLOW_CLAIM_MS = 4 * MS_PER_SECOND;
|
|
5735
|
+
var WALLET_PENDING_TRANSFER_CLAIM_COOLDOWN_MS = 15 * MS_PER_SECOND;
|
|
5736
|
+
var WALLET_SDK_BACKGROUND_QUIET_MS = MS_PER_SECOND;
|
|
5737
|
+
var WALLET_RECENT_TRANSFER_LIMIT = 100;
|
|
5738
|
+
var WALLET_TRANSFER_PAGE_LIMIT = 100;
|
|
5739
|
+
var WALLET_TRANSFER_FETCH_THROTTLE_MS = 150;
|
|
5740
|
+
var WALLET_PENDING_TRANSFER_CLAIM_RETRY_MS = 3 * MS_PER_SECOND;
|
|
5741
|
+
var WALLET_SENT_TRANSFER_REFRESH_DELAY_MS = 3 * MS_PER_SECOND;
|
|
5742
|
+
var WALLET_PENDING_TRANSFER_BOOT_GRACE_MS = 15 * MS_PER_SECOND;
|
|
5743
|
+
var WALLET_PENDING_TRANSFER_HOT_AGE_MS = MINUTE_MS;
|
|
5744
|
+
var WALLET_PENDING_TRANSFER_WARM_AGE_MS = 10 * MINUTE_MS;
|
|
5745
|
+
var WALLET_PENDING_TRANSFER_WARM_RETRY_MS = 15 * MS_PER_SECOND;
|
|
5746
|
+
var WALLET_PENDING_TRANSFER_STALE_RETRY_MS = 2 * MINUTE_MS;
|
|
5747
|
+
var WALLET_PENDING_TRANSFER_STUCK_RETRY_MS = 10 * MINUTE_MS;
|
|
5748
|
+
var WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS = HOUR_MS;
|
|
5749
|
+
var WALLET_TRANSFER_CACHE_WRITE_DELAY_MS = 3 * MS_PER_SECOND;
|
|
5750
|
+
|
|
5621
5751
|
// ../../core/notifications.js
|
|
5622
5752
|
"use client";
|
|
5623
5753
|
var NOTIFICATION_DESCRIPTOR_VERSION = 1;
|
|
5754
|
+
var CHAT_ATTENTION_REGISTRATION_VERSION = 1;
|
|
5755
|
+
var CHAT_NOTIFICATION_MODES = Object.freeze({
|
|
5756
|
+
ALL: "all",
|
|
5757
|
+
MENTIONS: "mentions",
|
|
5758
|
+
NONE: "none"
|
|
5759
|
+
});
|
|
5760
|
+
var CHAT_ATTENTION_KINDS = Object.freeze({
|
|
5761
|
+
GENERAL: "general",
|
|
5762
|
+
MENTION: "mention",
|
|
5763
|
+
ROTATION: "rotation",
|
|
5764
|
+
SILENT: "silent"
|
|
5765
|
+
});
|
|
5624
5766
|
var HEX_32_RE = /^[0-9a-f]{64}$/u;
|
|
5625
5767
|
var PEER_TAG_RE = /^[0-9a-f]{32}$/u;
|
|
5768
|
+
var ATTENTION_KINDS = new Set(Object.values(CHAT_ATTENTION_KINDS));
|
|
5769
|
+
var NOTIFICATION_MODES = new Set(Object.values(CHAT_NOTIFICATION_MODES));
|
|
5770
|
+
function hasCurrentChatAttentionRegistration(value) {
|
|
5771
|
+
return value?.deliveryRegistered === true && value?.attentionRegistrationVersion === CHAT_ATTENTION_REGISTRATION_VERSION;
|
|
5772
|
+
}
|
|
5626
5773
|
function cleanHex32(value, label) {
|
|
5627
5774
|
const text = cleanText(value).toLowerCase();
|
|
5628
5775
|
if (!HEX_32_RE.test(text)) {
|
|
@@ -5656,6 +5803,75 @@ function deriveChatDeliveryCapability(stateCapability, fields = {}) {
|
|
|
5656
5803
|
cleanBytes(key);
|
|
5657
5804
|
}
|
|
5658
5805
|
}
|
|
5806
|
+
function cleanAttentionFields(fields = {}) {
|
|
5807
|
+
return {
|
|
5808
|
+
chatId: cleanHex32(fields.chatId, "attention chat id"),
|
|
5809
|
+
recipientChatPK: cleanHex32(fields.recipientChatPK, "attention recipient chat key"),
|
|
5810
|
+
generation: Number.isSafeInteger(fields.generation) && fields.generation > 0 ? fields.generation : 1
|
|
5811
|
+
};
|
|
5812
|
+
}
|
|
5813
|
+
function deriveChatAttentionMaterial(attentionSecret, fields, kind) {
|
|
5814
|
+
const normalized = cleanAttentionFields(fields);
|
|
5815
|
+
const attentionKind = cleanText(kind);
|
|
5816
|
+
if (!ATTENTION_KINDS.has(attentionKind)) {
|
|
5817
|
+
throw new Error("chat attention kind required");
|
|
5818
|
+
}
|
|
5819
|
+
const key = deriveKey(toBytes32(attentionSecret, "chat attention secret"), "epoch-chat-attention-capability-v1", [normalized.chatId, normalized.recipientChatPK, normalized.generation, attentionKind]);
|
|
5820
|
+
try {
|
|
5821
|
+
return toHex(key);
|
|
5822
|
+
} finally {
|
|
5823
|
+
cleanBytes(key);
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5826
|
+
function deriveChatAttentionCapability(attentionSecret, fields = {}) {
|
|
5827
|
+
return deriveChatAttentionMaterial(attentionSecret, fields, fields.kind);
|
|
5828
|
+
}
|
|
5829
|
+
function defaultChatNotificationMode(manifest) {
|
|
5830
|
+
if (manifest?.lineage !== "group")
|
|
5831
|
+
return CHAT_NOTIFICATION_MODES.ALL;
|
|
5832
|
+
return manifest.members?.length > CHAT_ORDINARY_DELIVERY_MAX_MEMBERS ? CHAT_NOTIFICATION_MODES.MENTIONS : CHAT_NOTIFICATION_MODES.ALL;
|
|
5833
|
+
}
|
|
5834
|
+
function normalizeChatNotificationMode(value, manifest) {
|
|
5835
|
+
const mode = cleanText(value);
|
|
5836
|
+
if (!NOTIFICATION_MODES.has(mode))
|
|
5837
|
+
throw new Error("chat notification mode required");
|
|
5838
|
+
if (manifest?.lineage !== "group")
|
|
5839
|
+
return CHAT_NOTIFICATION_MODES.ALL;
|
|
5840
|
+
if (mode === CHAT_NOTIFICATION_MODES.ALL && manifest.members?.length > CHAT_ORDINARY_DELIVERY_MAX_MEMBERS) {
|
|
5841
|
+
return CHAT_NOTIFICATION_MODES.MENTIONS;
|
|
5842
|
+
}
|
|
5843
|
+
return mode;
|
|
5844
|
+
}
|
|
5845
|
+
function chatAttentionCommitments(attentionSecret, fields = {}) {
|
|
5846
|
+
const mode = normalizeChatNotificationMode(fields.notificationMode, fields.manifest);
|
|
5847
|
+
const acceptedKinds = mode === CHAT_NOTIFICATION_MODES.ALL ? [CHAT_ATTENTION_KINDS.GENERAL, CHAT_ATTENTION_KINDS.MENTION] : mode === CHAT_NOTIFICATION_MODES.MENTIONS ? [CHAT_ATTENTION_KINDS.MENTION] : [];
|
|
5848
|
+
const commitments = [...acceptedKinds, CHAT_ATTENTION_KINDS.ROTATION].map((kind) => deliveryCapabilityHash(deriveChatAttentionMaterial(attentionSecret, fields, kind)));
|
|
5849
|
+
while (commitments.length < 3) {
|
|
5850
|
+
const padding = toHex(randomBytes3(32));
|
|
5851
|
+
if (!commitments.includes(padding))
|
|
5852
|
+
commitments.push(padding);
|
|
5853
|
+
}
|
|
5854
|
+
return commitments.sort();
|
|
5855
|
+
}
|
|
5856
|
+
function chatDeliveryRegistration(stateCapability, fields = {}) {
|
|
5857
|
+
const attentionFields = {
|
|
5858
|
+
...fields,
|
|
5859
|
+
notificationMode: fields.notificationMode ?? defaultChatNotificationMode(fields.manifest)
|
|
5860
|
+
};
|
|
5861
|
+
return {
|
|
5862
|
+
capability: deriveChatDeliveryCapability(stateCapability, fields),
|
|
5863
|
+
attentionCommitments: chatAttentionCommitments(fields.attentionSecret, attentionFields),
|
|
5864
|
+
singleUseAttentionCommitment: deliveryCapabilityHash(deriveChatAttentionMaterial(fields.attentionSecret, attentionFields, CHAT_ATTENTION_KINDS.ROTATION))
|
|
5865
|
+
};
|
|
5866
|
+
}
|
|
5867
|
+
function deliveryCapabilityHash(value) {
|
|
5868
|
+
const capability = fromHex(cleanHex32(value, "delivery capability"), "delivery capability");
|
|
5869
|
+
try {
|
|
5870
|
+
return toHex(sha256(capability));
|
|
5871
|
+
} finally {
|
|
5872
|
+
cleanBytes(capability);
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5659
5875
|
function deriveChatInboxDeliveryId(stateCapability, fields = {}) {
|
|
5660
5876
|
const chatId = cleanHex32(fields.chatId, "inbox delivery chat id");
|
|
5661
5877
|
const recipientChatPK = cleanHex32(fields.recipientChatPK, "inbox delivery recipient chat key");
|
|
@@ -5900,135 +6116,6 @@ function isExtendedPictographic(cp) {
|
|
|
5900
6116
|
return findUnicodeRangeCategory(cp, EXTENDED_PICTOGRAPHIC_S, EXTENDED_PICTOGRAPHIC_E) !== 0;
|
|
5901
6117
|
}
|
|
5902
6118
|
|
|
5903
|
-
// ../../core/config.js
|
|
5904
|
-
var MS_PER_SECOND = 1000;
|
|
5905
|
-
var MINUTE_MS = 60 * MS_PER_SECOND;
|
|
5906
|
-
var HOUR_MS = 60 * MINUTE_MS;
|
|
5907
|
-
var DAY_MS = 24 * HOUR_MS;
|
|
5908
|
-
var KIB_BYTES = 1024;
|
|
5909
|
-
var MIB_BYTES = 1024 * 1024;
|
|
5910
|
-
var SATS_PER_BITCOIN = 100000000n;
|
|
5911
|
-
var USERNAME_MAX_CHARS = 12;
|
|
5912
|
-
var PASSWORD_MIN_CHARS = 15;
|
|
5913
|
-
var PASSWORD_MAX_CHARS = 64;
|
|
5914
|
-
var PASSWORD_MAX_BYTES = 4 * 1024;
|
|
5915
|
-
var LOCAL_MEDIA_CACHE_MAX_BYTES = 512 * MIB_BYTES;
|
|
5916
|
-
var LOCAL_PROFILE_CACHE_MAX_ITEMS = 500;
|
|
5917
|
-
var LOCAL_PROFILE_CACHE_MAX_AGE_MS = 30 * DAY_MS;
|
|
5918
|
-
var LOCAL_CHAT_CACHE_MAX_ITEMS = 1000;
|
|
5919
|
-
var LOCAL_CHAT_MESSAGE_CACHE_MAX_VISIBLE = 1000;
|
|
5920
|
-
var LOCAL_AVATAR_CACHE_MAX_BYTES = 128 * MIB_BYTES;
|
|
5921
|
-
var LOCAL_AVATAR_CACHE_MAX_AGE_MS = 30 * DAY_MS;
|
|
5922
|
-
var AVATAR_IMAGE_MAX_BYTES = 256 * KIB_BYTES;
|
|
5923
|
-
var AVATAR_IMAGE_QUALITY_ATTEMPTS = Object.freeze([0.92, 0.84, 0.76, 0.68]);
|
|
5924
|
-
var CHAT_AVATAR_IMAGE_MAX_BYTES = AVATAR_IMAGE_MAX_BYTES;
|
|
5925
|
-
var CHAT_AVATAR_REF_MAX_CHARS = 256;
|
|
5926
|
-
var CHAT_MESSAGE_FILE_CACHE_MAX_BYTES = 64 * MIB_BYTES;
|
|
5927
|
-
var IDLE_CALLBACK_MIN_TIMEOUT_MS = 50;
|
|
5928
|
-
var ATTACHMENT_CACHE_IDLE_TIMEOUT_MS = 2500;
|
|
5929
|
-
var ATTACHMENT_CACHE_FALLBACK_DELAY_MS = 250;
|
|
5930
|
-
var CHAT_UNSAVED_TTL_DAYS = 21;
|
|
5931
|
-
var CHAT_UNSAVED_TTL_MS = CHAT_UNSAVED_TTL_DAYS * DAY_MS;
|
|
5932
|
-
var CHAT_AFTER_SEEN_MS = DAY_MS;
|
|
5933
|
-
var CHAT_MEDIA_TTL_DAYS = CHAT_UNSAVED_TTL_DAYS;
|
|
5934
|
-
var CHAT_MEDIA_TTL_MS = CHAT_MEDIA_TTL_DAYS * DAY_MS;
|
|
5935
|
-
var CHAT_UPLOAD_MAX_BYTES = 64 * MIB_BYTES;
|
|
5936
|
-
var CHAT_AUDIO_TRANSCODE_BITRATE_BPS = 128000;
|
|
5937
|
-
var CHAT_VIDEO_TRANSCODE_VIDEO_BITRATE_BPS = 3000000;
|
|
5938
|
-
var CHAT_VIDEO_TRANSCODE_AUDIO_BITRATE_BPS = CHAT_AUDIO_TRANSCODE_BITRATE_BPS;
|
|
5939
|
-
var CHAT_MESSAGE_BATCH_SIZE = 40;
|
|
5940
|
-
var CHAT_MESSAGE_QUERY_MAX_DOCS = 120;
|
|
5941
|
-
var CHAT_MESSAGE_MUTATION_MAX_ITEMS = 256;
|
|
5942
|
-
var CHAT_TTL_CLIENT_DELETE_GRACE_MS = MINUTE_MS;
|
|
5943
|
-
var CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS = 1500;
|
|
5944
|
-
var CHAT_BATCH_CLEANUP_IDLE_DELAY_MS = 250;
|
|
5945
|
-
var CHAT_LIST_PAGE_SIZE = 20;
|
|
5946
|
-
var CHAT_LIST_LIVE_COUNT = CHAT_LIST_PAGE_SIZE;
|
|
5947
|
-
var CHAT_INBOX_PING_PAGE_SIZE = 25;
|
|
5948
|
-
var CHAT_INBOX_DISCOVERY_PARALLEL_CHATS = CHAT_INBOX_PING_PAGE_SIZE;
|
|
5949
|
-
var CHAT_INBOX_SETTLEMENT_PARALLEL_CHATS = 4;
|
|
5950
|
-
var CHAT_INBOX_CURSOR_OVERLAP_MS = 5 * MS_PER_SECOND;
|
|
5951
|
-
var CHAT_LIST_CACHE_WRITE_DELAY_MS = 1500;
|
|
5952
|
-
var CHAT_LIST_SNAPSHOT_COALESCE_MS = 80;
|
|
5953
|
-
var CHAT_LIST_COMPLETE_SYNC_DELAY_MS = 250;
|
|
5954
|
-
var CHAT_LIST_LISTENER_RETRY_MS = 1000;
|
|
5955
|
-
var CHAT_TOP_WARM_COUNT = 0;
|
|
5956
|
-
var CHAT_EAGER_WARM_COUNT = 0;
|
|
5957
|
-
var CHAT_WARM_DELAY_MS = 3000;
|
|
5958
|
-
var CHAT_WARM_BATCH_SIZE = CHAT_MESSAGE_BATCH_SIZE;
|
|
5959
|
-
var CHAT_MESSAGE_VIEW_CACHE_SIZE = 30;
|
|
5960
|
-
var CHAT_MEDIA_WARM_MESSAGES_PER_CHAT = CHAT_MESSAGE_BATCH_SIZE;
|
|
5961
|
-
var CHAT_MEDIA_WARM_START_DELAY_MS = 600;
|
|
5962
|
-
var CHAT_MEDIA_WARM_STEP_DELAY_MS = 120;
|
|
5963
|
-
var CHAT_MEDIA_WARM_TYPES = Object.freeze(["img", "gif", "mp4"]);
|
|
5964
|
-
var CHAT_MEDIA_WARM_MAX_BYTES = 0;
|
|
5965
|
-
var CHAT_READ_WRITE_INTERVAL_MS = 2 * MS_PER_SECOND;
|
|
5966
|
-
var CHAT_READ_WRITE_MAX_WAIT_MS = 8 * MS_PER_SECOND;
|
|
5967
|
-
var CHAT_LIVE_PING_INTERVAL_MS = 5 * MS_PER_SECOND;
|
|
5968
|
-
var CHAT_LIVE_SWEEP_INTERVAL_MS = 15 * MS_PER_SECOND;
|
|
5969
|
-
var CHAT_LIVE_STALE_AFTER_MS = 15 * MS_PER_SECOND;
|
|
5970
|
-
var CHAT_LIVE_RECONNECT_MAX_MS = 5 * MS_PER_SECOND;
|
|
5971
|
-
var CHAT_LIVE_TYPING_IDLE_MS = 8 * MS_PER_SECOND;
|
|
5972
|
-
var CHAT_LIVE_TYPING_RENEW_MS = 5 * MS_PER_SECOND;
|
|
5973
|
-
var CHAT_LIVE_COMPOSITION_HANDOFF_MS = 15 * MS_PER_SECOND;
|
|
5974
|
-
var CHAT_LIVE_READ_SEND_INTERVAL_MS = 120;
|
|
5975
|
-
var CHAT_LIVE_READ_SEND_MAX_WAIT_MS = 500;
|
|
5976
|
-
var CHAT_LIVE_READ_WRITE_INTERVAL_MS = 12 * MS_PER_SECOND;
|
|
5977
|
-
var CHAT_LIVE_READ_WRITE_MAX_WAIT_MS = 30 * MS_PER_SECOND;
|
|
5978
|
-
var CHAT_SEND_QUEUE_RATE_LIMIT_COUNT = 12;
|
|
5979
|
-
var CHAT_SEND_QUEUE_RATE_LIMIT_WINDOW_MS = 10 * MS_PER_SECOND;
|
|
5980
|
-
var CHAT_SETTINGS_BODY_MAX_BYTES = 32 * KIB_BYTES;
|
|
5981
|
-
var CHAT_TITLE_STATE_MAX_CHARS = 128;
|
|
5982
|
-
var CHAT_MANIFEST_MAX_BYTES = 256 * KIB_BYTES;
|
|
5983
|
-
var CHAT_RECEIPT_MAX_MEMBERS = 32;
|
|
5984
|
-
var CHAT_ORDINARY_DELIVERY_MAX_MEMBERS = 128;
|
|
5985
|
-
var CHAT_MAX_MEMBERS = 256;
|
|
5986
|
-
var CHAT_MAX_TEXT_CHARS = 2048;
|
|
5987
|
-
var CHAT_TITLE_INPUT_MAX_CHARS = 16;
|
|
5988
|
-
var CHAT_MAX_REACTIONS = CHAT_MAX_MEMBERS;
|
|
5989
|
-
var SEARCH_DEBOUNCE_MS = 300;
|
|
5990
|
-
var RECENT_PEER_REFRESH_LIMIT = 50;
|
|
5991
|
-
var RECENT_PEER_REFRESH_DELAY_MS = 250;
|
|
5992
|
-
var RECENT_PEER_REFRESH_INTERVAL_MS = 5 * MINUTE_MS;
|
|
5993
|
-
var RECENT_PEER_REFRESH_THROTTLE_MS = 120;
|
|
5994
|
-
var BAN_REFRESH_GRACE_MS = 50;
|
|
5995
|
-
var REQUEST_MONEY_MAX_SATS = SATS_PER_BITCOIN * 100000n;
|
|
5996
|
-
var WALLET_TRANSFER_POLL_MS = 3 * MS_PER_SECOND;
|
|
5997
|
-
var WALLET_ACTIVE_TRANSFER_REFRESH_MS = MS_PER_SECOND;
|
|
5998
|
-
var WALLET_CACHE_HYDRATE_DELAY_MS = 0;
|
|
5999
|
-
var WALLET_BOOT_REFRESH_DELAY_MS = 500;
|
|
6000
|
-
var WALLET_BOOT_CACHED_REFRESH_DELAY_MS = 2500;
|
|
6001
|
-
var WALLET_REGTEST_DEPOSIT_CLAIM_POLL_MS = 20 * MS_PER_SECOND;
|
|
6002
|
-
var WALLET_MAINNET_DEPOSIT_CLAIM_POLL_MS = MINUTE_MS;
|
|
6003
|
-
var WALLET_BALANCE_EVENT_COALESCE_MS = 2 * MS_PER_SECOND;
|
|
6004
|
-
var WALLET_INCOMING_UPDATE_COALESCE_MS = 250;
|
|
6005
|
-
var WALLET_AUTO_CLAIM_MAX_FEE_SATS = 5000;
|
|
6006
|
-
var WALLET_CLAIM_PAGE_SIZE = 100;
|
|
6007
|
-
var WALLET_PENDING_TRANSFER_CLAIM_BATCH_SIZE = 50;
|
|
6008
|
-
var WALLET_PENDING_TRANSFER_STATUS_BATCH_SIZE = 2;
|
|
6009
|
-
var WALLET_PENDING_TRANSFER_COLD_REFRESH_BATCH_SIZE = 50;
|
|
6010
|
-
var WALLET_PENDING_TRANSFER_COLD_REFRESH_INTERVAL_MS = 15 * MS_PER_SECOND;
|
|
6011
|
-
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_QUEUE = 3;
|
|
6012
|
-
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_QUEUE = 10;
|
|
6013
|
-
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_SMALL_SIZE = 2;
|
|
6014
|
-
var WALLET_PENDING_TRANSFER_ADAPTIVE_BATCH_LARGE_SIZE = 3;
|
|
6015
|
-
var WALLET_PENDING_TRANSFER_SLOW_CLAIM_MS = 4 * MS_PER_SECOND;
|
|
6016
|
-
var WALLET_PENDING_TRANSFER_CLAIM_COOLDOWN_MS = 15 * MS_PER_SECOND;
|
|
6017
|
-
var WALLET_SDK_BACKGROUND_QUIET_MS = MS_PER_SECOND;
|
|
6018
|
-
var WALLET_RECENT_TRANSFER_LIMIT = 100;
|
|
6019
|
-
var WALLET_TRANSFER_PAGE_LIMIT = 100;
|
|
6020
|
-
var WALLET_TRANSFER_FETCH_THROTTLE_MS = 150;
|
|
6021
|
-
var WALLET_PENDING_TRANSFER_CLAIM_RETRY_MS = 3 * MS_PER_SECOND;
|
|
6022
|
-
var WALLET_SENT_TRANSFER_REFRESH_DELAY_MS = 3 * MS_PER_SECOND;
|
|
6023
|
-
var WALLET_PENDING_TRANSFER_BOOT_GRACE_MS = 15 * MS_PER_SECOND;
|
|
6024
|
-
var WALLET_PENDING_TRANSFER_HOT_AGE_MS = MINUTE_MS;
|
|
6025
|
-
var WALLET_PENDING_TRANSFER_WARM_AGE_MS = 10 * MINUTE_MS;
|
|
6026
|
-
var WALLET_PENDING_TRANSFER_WARM_RETRY_MS = 15 * MS_PER_SECOND;
|
|
6027
|
-
var WALLET_PENDING_TRANSFER_STALE_RETRY_MS = 2 * MINUTE_MS;
|
|
6028
|
-
var WALLET_PENDING_TRANSFER_STUCK_RETRY_MS = 10 * MINUTE_MS;
|
|
6029
|
-
var WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS = HOUR_MS;
|
|
6030
|
-
var WALLET_TRANSFER_CACHE_WRITE_DELAY_MS = 3 * MS_PER_SECOND;
|
|
6031
|
-
|
|
6032
6119
|
// ../../core/password.js
|
|
6033
6120
|
var MIN_PASSWORD = PASSWORD_MIN_CHARS;
|
|
6034
6121
|
var MAX_PASSWORD = PASSWORD_MAX_CHARS;
|
|
@@ -7670,7 +7757,7 @@ function cleanAvatarUsername(value) {
|
|
|
7670
7757
|
}
|
|
7671
7758
|
|
|
7672
7759
|
// ../../core/agreement.js
|
|
7673
|
-
var TERMS_VERSION = "2026-
|
|
7760
|
+
var TERMS_VERSION = "2026-09-01.1";
|
|
7674
7761
|
var CURRENT_AGREEMENT = Object.freeze({
|
|
7675
7762
|
termsVersion: TERMS_VERSION
|
|
7676
7763
|
});
|
|
@@ -7802,6 +7889,312 @@ function nextBanRefreshMs(banned, keys = ["full", "chat"], now = Date.now()) {
|
|
|
7802
7889
|
return times[0] ?? null;
|
|
7803
7890
|
}
|
|
7804
7891
|
|
|
7892
|
+
// ../../core/chat/protocol.js
|
|
7893
|
+
"use client";
|
|
7894
|
+
var CHAT_PROTOCOL_VERSION = 3;
|
|
7895
|
+
var CHAT_MANIFEST_VERSION = 2;
|
|
7896
|
+
var CHAT_TRANSITION_VERSION = 2;
|
|
7897
|
+
var CHAT_MESSAGE_ENVELOPE_VERSION = 3;
|
|
7898
|
+
var CHAT_SETTINGS_VERSION = 1;
|
|
7899
|
+
|
|
7900
|
+
// ../../core/chat/epochs/manifest.js
|
|
7901
|
+
"use client";
|
|
7902
|
+
var CHAT_LINEAGES = Object.freeze({
|
|
7903
|
+
SELF: "self",
|
|
7904
|
+
DIRECT: "direct",
|
|
7905
|
+
GROUP: "group"
|
|
7906
|
+
});
|
|
7907
|
+
var LINEAGES = new Set(Object.values(CHAT_LINEAGES));
|
|
7908
|
+
var HEX_32_RE2 = /^[0-9a-f]{64}$/u;
|
|
7909
|
+
function cleanChatHex(value, label = "chat value") {
|
|
7910
|
+
const text = cleanText(value).toLowerCase();
|
|
7911
|
+
if (!HEX_32_RE2.test(text)) {
|
|
7912
|
+
throw new Error(`${label} required`);
|
|
7913
|
+
}
|
|
7914
|
+
return text;
|
|
7915
|
+
}
|
|
7916
|
+
function cleanUid(value) {
|
|
7917
|
+
const uid = cleanText(value);
|
|
7918
|
+
if (!uid || uid.length > 128) {
|
|
7919
|
+
throw new Error("chat member uid required");
|
|
7920
|
+
}
|
|
7921
|
+
return uid;
|
|
7922
|
+
}
|
|
7923
|
+
function cleanCreatedAt(value) {
|
|
7924
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
7925
|
+
throw new Error("chat epoch createdAt required");
|
|
7926
|
+
}
|
|
7927
|
+
return value;
|
|
7928
|
+
}
|
|
7929
|
+
function cleanEpochVersion(value) {
|
|
7930
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
7931
|
+
throw new Error("chat epoch version required");
|
|
7932
|
+
}
|
|
7933
|
+
return value;
|
|
7934
|
+
}
|
|
7935
|
+
function cleanMember(value) {
|
|
7936
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7937
|
+
throw new Error("invalid chat member");
|
|
7938
|
+
}
|
|
7939
|
+
return {
|
|
7940
|
+
uid: cleanUid(value.uid),
|
|
7941
|
+
chatPK: cleanChatHex(value.chatPK, "member chat key"),
|
|
7942
|
+
chatSigningPK: cleanChatHex(value.chatSigningPK, "member signing key"),
|
|
7943
|
+
notificationPK: cleanChatHex(value.notificationPK, "member notification key"),
|
|
7944
|
+
mlsLeafId: cleanChatHex(value.mlsLeafId, "member mls leaf id")
|
|
7945
|
+
};
|
|
7946
|
+
}
|
|
7947
|
+
function assertUniqueMembers(members) {
|
|
7948
|
+
const uids = new Set;
|
|
7949
|
+
const chatKeys = new Set;
|
|
7950
|
+
const signingKeys = new Set;
|
|
7951
|
+
const mlsLeafIds = new Set;
|
|
7952
|
+
for (const member of members) {
|
|
7953
|
+
if (uids.has(member.uid) || chatKeys.has(member.chatPK) || signingKeys.has(member.chatSigningPK) || mlsLeafIds.has(member.mlsLeafId)) {
|
|
7954
|
+
throw new Error("duplicate chat member");
|
|
7955
|
+
}
|
|
7956
|
+
uids.add(member.uid);
|
|
7957
|
+
chatKeys.add(member.chatPK);
|
|
7958
|
+
signingKeys.add(member.chatSigningPK);
|
|
7959
|
+
mlsLeafIds.add(member.mlsLeafId);
|
|
7960
|
+
}
|
|
7961
|
+
}
|
|
7962
|
+
function assertLineage(manifest, parent) {
|
|
7963
|
+
const memberCount = manifest.members.length;
|
|
7964
|
+
if (memberCount > 2 && manifest.lineage !== CHAT_LINEAGES.GROUP) {
|
|
7965
|
+
throw new Error("group lineage required");
|
|
7966
|
+
}
|
|
7967
|
+
if (!parent) {
|
|
7968
|
+
return;
|
|
7969
|
+
}
|
|
7970
|
+
if (manifest.chatId !== parent.chatId || manifest.parentEpochId !== parent.epochId || manifest.epochVersion !== parent.epochVersion + 1) {
|
|
7971
|
+
throw new Error("invalid chat epoch successor");
|
|
7972
|
+
}
|
|
7973
|
+
if (parent.lineage === CHAT_LINEAGES.GROUP && manifest.lineage !== CHAT_LINEAGES.GROUP) {
|
|
7974
|
+
throw new Error("chat group lineage is permanent");
|
|
7975
|
+
}
|
|
7976
|
+
}
|
|
7977
|
+
function normalizeEpochManifest(value, options = {}) {
|
|
7978
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7979
|
+
throw new Error("chat manifest required");
|
|
7980
|
+
}
|
|
7981
|
+
if (value.v !== CHAT_MANIFEST_VERSION || value.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
7982
|
+
throw new Error("unsupported chat manifest");
|
|
7983
|
+
}
|
|
7984
|
+
if (!Array.isArray(value.members) || value.members.length < 1 || value.members.length > CHAT_MAX_MEMBERS) {
|
|
7985
|
+
throw new Error("invalid chat member count");
|
|
7986
|
+
}
|
|
7987
|
+
const lineage = cleanText(value.lineage);
|
|
7988
|
+
if (!LINEAGES.has(lineage)) {
|
|
7989
|
+
throw new Error("invalid chat lineage");
|
|
7990
|
+
}
|
|
7991
|
+
const members = value.members.map(cleanMember).sort((a, b) => a.chatPK.localeCompare(b.chatPK));
|
|
7992
|
+
assertUniqueMembers(members);
|
|
7993
|
+
const manifest = {
|
|
7994
|
+
v: CHAT_MANIFEST_VERSION,
|
|
7995
|
+
protocol: CHAT_PROTOCOL_VERSION,
|
|
7996
|
+
chatId: cleanChatHex(value.chatId, "chat id"),
|
|
7997
|
+
epochId: cleanChatHex(value.epochId, "chat epoch id"),
|
|
7998
|
+
epochVersion: cleanEpochVersion(value.epochVersion),
|
|
7999
|
+
parentEpochId: value.parentEpochId == null ? null : cleanChatHex(value.parentEpochId, "parent epoch id"),
|
|
8000
|
+
createdAt: cleanCreatedAt(value.createdAt),
|
|
8001
|
+
lineage,
|
|
8002
|
+
members
|
|
8003
|
+
};
|
|
8004
|
+
if (manifest.epochVersion === 1 !== (manifest.parentEpochId === null)) {
|
|
8005
|
+
throw new Error("invalid parent epoch");
|
|
8006
|
+
}
|
|
8007
|
+
const bytes = canonicalBytes(manifest, "chat manifest");
|
|
8008
|
+
if (bytes.length > CHAT_MANIFEST_MAX_BYTES) {
|
|
8009
|
+
throw new Error("chat manifest too large");
|
|
8010
|
+
}
|
|
8011
|
+
assertLineage(manifest, options.parent || null);
|
|
8012
|
+
return manifest;
|
|
8013
|
+
}
|
|
8014
|
+
function epochManifestDigest(manifest) {
|
|
8015
|
+
return toHex(sha256(canonicalBytes(normalizeEpochManifest(manifest), "chat manifest digest")));
|
|
8016
|
+
}
|
|
8017
|
+
function manifestMember(manifest, chatPK) {
|
|
8018
|
+
const key = cleanChatHex(chatPK, "member chat key");
|
|
8019
|
+
return normalizeEpochManifest(manifest).members.find((member) => member.chatPK === key) || null;
|
|
8020
|
+
}
|
|
8021
|
+
function manifestSigningKeys(manifest) {
|
|
8022
|
+
return Object.fromEntries(normalizeEpochManifest(manifest).members.map((member) => [member.chatPK, member.chatSigningPK]));
|
|
8023
|
+
}
|
|
8024
|
+
|
|
8025
|
+
// ../../core/chat/admission.js
|
|
8026
|
+
"use client";
|
|
8027
|
+
var CHAT_ADMISSION_VERSION = 1;
|
|
8028
|
+
var CHAT_ADMISSION_GRANT_COUNT = 8;
|
|
8029
|
+
var CHAT_ADMISSION_DECISIONS = Object.freeze({
|
|
8030
|
+
ACCEPT: "accept",
|
|
8031
|
+
REQUEST: "request",
|
|
8032
|
+
REJECT: "reject"
|
|
8033
|
+
});
|
|
8034
|
+
var CHAT_DIRECT_ADMISSION_MODES = Object.freeze({
|
|
8035
|
+
OPEN: "open",
|
|
8036
|
+
REQUESTS: "requests",
|
|
8037
|
+
CLOSED: "closed"
|
|
8038
|
+
});
|
|
8039
|
+
var CHAT_GROUP_ADMISSION_MODES = Object.freeze({
|
|
8040
|
+
OPEN: "open",
|
|
8041
|
+
CLOSED: "closed"
|
|
8042
|
+
});
|
|
8043
|
+
var HEX_32_RE3 = /^[0-9a-f]{64}$/u;
|
|
8044
|
+
var DIRECT_MODES = new Set(Object.values(CHAT_DIRECT_ADMISSION_MODES));
|
|
8045
|
+
var GROUP_MODES = new Set(Object.values(CHAT_GROUP_ADMISSION_MODES));
|
|
8046
|
+
var DECISION_RANK = Object.freeze({ accept: 0, request: 1, reject: 2 });
|
|
8047
|
+
var DEFAULT_ADMISSION = Object.freeze({
|
|
8048
|
+
v: CHAT_ADMISSION_VERSION,
|
|
8049
|
+
direct: CHAT_DIRECT_ADMISSION_MODES.OPEN,
|
|
8050
|
+
groups: CHAT_GROUP_ADMISSION_MODES.OPEN,
|
|
8051
|
+
directGrants: Object.freeze([])
|
|
8052
|
+
});
|
|
8053
|
+
var CLOSED_ADMISSION = Object.freeze({
|
|
8054
|
+
v: CHAT_ADMISSION_VERSION,
|
|
8055
|
+
direct: CHAT_DIRECT_ADMISSION_MODES.CLOSED,
|
|
8056
|
+
groups: CHAT_GROUP_ADMISSION_MODES.CLOSED,
|
|
8057
|
+
directGrants: Object.freeze([])
|
|
8058
|
+
});
|
|
8059
|
+
function validGrantList(value) {
|
|
8060
|
+
return Array.isArray(value) && value.length === CHAT_ADMISSION_GRANT_COUNT && value.every((item) => typeof item === "string" && HEX_32_RE3.test(item)) && new Set(value).size === value.length && value.every((item, index) => index === 0 || value[index - 1] < item);
|
|
8061
|
+
}
|
|
8062
|
+
function normalizeChatAdmission(value) {
|
|
8063
|
+
if (value == null)
|
|
8064
|
+
return DEFAULT_ADMISSION;
|
|
8065
|
+
if (typeof value !== "object" || Array.isArray(value) || value.v !== CHAT_ADMISSION_VERSION || !DIRECT_MODES.has(value.direct) || !GROUP_MODES.has(value.groups) || !validGrantList(value.directGrants))
|
|
8066
|
+
return CLOSED_ADMISSION;
|
|
8067
|
+
return Object.freeze({
|
|
8068
|
+
v: CHAT_ADMISSION_VERSION,
|
|
8069
|
+
direct: value.direct,
|
|
8070
|
+
groups: value.groups,
|
|
8071
|
+
directGrants: Object.freeze([...value.directGrants])
|
|
8072
|
+
});
|
|
8073
|
+
}
|
|
8074
|
+
function normalizeChatAdmissionDecision(value) {
|
|
8075
|
+
return Object.values(CHAT_ADMISSION_DECISIONS).includes(value) ? value : CHAT_ADMISSION_DECISIONS.REJECT;
|
|
8076
|
+
}
|
|
8077
|
+
function strictestChatAdmissionDecision(...values) {
|
|
8078
|
+
return values.map(normalizeChatAdmissionDecision).reduce((strictest, value) => DECISION_RANK[value] > DECISION_RANK[strictest] ? value : strictest, CHAT_ADMISSION_DECISIONS.ACCEPT);
|
|
8079
|
+
}
|
|
8080
|
+
function orderedChatKeys(first, second) {
|
|
8081
|
+
return [
|
|
8082
|
+
cleanChatHex(first, "chat public key"),
|
|
8083
|
+
cleanChatHex(second, "peer chat public key")
|
|
8084
|
+
].sort();
|
|
8085
|
+
}
|
|
8086
|
+
function deriveDirectAdmissionCapability(chatPrivateKey, chatPK, peerChatPK, recipientChatPK) {
|
|
8087
|
+
const ownChatPK = cleanChatHex(chatPK, "chat public key");
|
|
8088
|
+
const otherChatPK = cleanChatHex(peerChatPK, "peer chat public key");
|
|
8089
|
+
const recipient = cleanChatHex(recipientChatPK, "recipient chat public key");
|
|
8090
|
+
if (ownChatPK === otherChatPK || ![ownChatPK, otherChatPK].includes(recipient)) {
|
|
8091
|
+
throw new Error("direct admission peer required");
|
|
8092
|
+
}
|
|
8093
|
+
let peerKey = null;
|
|
8094
|
+
let shared = null;
|
|
8095
|
+
let capability = null;
|
|
8096
|
+
try {
|
|
8097
|
+
peerKey = fromHex(otherChatPK, "peer chat public key");
|
|
8098
|
+
shared = x25519.getSharedSecret(toBytes32(chatPrivateKey, "chat private key"), peerKey);
|
|
8099
|
+
capability = deriveKey(shared.subarray(0, 32), "direct-chat-admission-capability-v1", [...orderedChatKeys(ownChatPK, otherChatPK), recipient]);
|
|
8100
|
+
return toHex(capability);
|
|
8101
|
+
} finally {
|
|
8102
|
+
cleanBytes(peerKey, shared, capability);
|
|
8103
|
+
}
|
|
8104
|
+
}
|
|
8105
|
+
function chatAdmissionCapabilityCommitment(value) {
|
|
8106
|
+
let capability = null;
|
|
8107
|
+
try {
|
|
8108
|
+
capability = fromHex(value, "chat admission capability");
|
|
8109
|
+
return toHex(sha256(capability));
|
|
8110
|
+
} finally {
|
|
8111
|
+
cleanBytes(capability);
|
|
8112
|
+
}
|
|
8113
|
+
}
|
|
8114
|
+
function directAdmissionCapabilityForPeer(chatPrivateKey, chatPK, profile) {
|
|
8115
|
+
return deriveDirectAdmissionCapability(chatPrivateKey, chatPK, profile?.chatPK, profile?.chatPK);
|
|
8116
|
+
}
|
|
8117
|
+
function hasDirectAdmissionGrant(chatPrivateKey, chatPK, profile) {
|
|
8118
|
+
const policy = normalizeChatAdmission(profile?.chatAdmission);
|
|
8119
|
+
if (!policy.directGrants.length)
|
|
8120
|
+
return false;
|
|
8121
|
+
const capability = directAdmissionCapabilityForPeer(chatPrivateKey, chatPK, profile);
|
|
8122
|
+
try {
|
|
8123
|
+
return policy.directGrants.includes(chatAdmissionCapabilityCommitment(capability));
|
|
8124
|
+
} finally {
|
|
8125
|
+
cleanBytes(capability);
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
function directAdmissionForPeer(chatPrivateKey, chatPK, profile) {
|
|
8129
|
+
if (hasDirectAdmissionGrant(chatPrivateKey, chatPK, profile)) {
|
|
8130
|
+
return CHAT_ADMISSION_DECISIONS.ACCEPT;
|
|
8131
|
+
}
|
|
8132
|
+
const mode = normalizeChatAdmission(profile?.chatAdmission).direct;
|
|
8133
|
+
if (mode === CHAT_DIRECT_ADMISSION_MODES.OPEN)
|
|
8134
|
+
return CHAT_ADMISSION_DECISIONS.ACCEPT;
|
|
8135
|
+
if (mode === CHAT_DIRECT_ADMISSION_MODES.REQUESTS)
|
|
8136
|
+
return CHAT_ADMISSION_DECISIONS.REQUEST;
|
|
8137
|
+
return CHAT_ADMISSION_DECISIONS.REJECT;
|
|
8138
|
+
}
|
|
8139
|
+
function canStartDirectChat(chatPrivateKey, chatPK, profile) {
|
|
8140
|
+
return directAdmissionForPeer(chatPrivateKey, chatPK, profile) !== CHAT_ADMISSION_DECISIONS.REJECT;
|
|
8141
|
+
}
|
|
8142
|
+
function canInviteToGroup(profile) {
|
|
8143
|
+
return normalizeChatAdmission(profile?.chatAdmission).groups === CHAT_GROUP_ADMISSION_MODES.OPEN;
|
|
8144
|
+
}
|
|
8145
|
+
function incomingChatAdmissionDecision(chatPrivateKey, chatPK, senderProfile, recipientAdmission, lineage) {
|
|
8146
|
+
const policy = normalizeChatAdmission(recipientAdmission);
|
|
8147
|
+
if (lineage === "direct") {
|
|
8148
|
+
const capability = deriveDirectAdmissionCapability(chatPrivateKey, chatPK, senderProfile?.chatPK, chatPK);
|
|
8149
|
+
try {
|
|
8150
|
+
if (policy.directGrants.includes(chatAdmissionCapabilityCommitment(capability))) {
|
|
8151
|
+
return CHAT_ADMISSION_DECISIONS.ACCEPT;
|
|
8152
|
+
}
|
|
8153
|
+
} finally {
|
|
8154
|
+
cleanBytes(capability);
|
|
8155
|
+
}
|
|
8156
|
+
if (policy.direct === CHAT_DIRECT_ADMISSION_MODES.OPEN)
|
|
8157
|
+
return CHAT_ADMISSION_DECISIONS.ACCEPT;
|
|
8158
|
+
if (policy.direct === CHAT_DIRECT_ADMISSION_MODES.REQUESTS)
|
|
8159
|
+
return CHAT_ADMISSION_DECISIONS.REQUEST;
|
|
8160
|
+
return CHAT_ADMISSION_DECISIONS.REJECT;
|
|
8161
|
+
}
|
|
8162
|
+
return lineage === "group" && policy.groups === CHAT_GROUP_ADMISSION_MODES.OPEN ? CHAT_ADMISSION_DECISIONS.ACCEPT : CHAT_ADMISSION_DECISIONS.REJECT;
|
|
8163
|
+
}
|
|
8164
|
+
function makeChatAdmission(chatPrivateKey, chatPK, options = {}) {
|
|
8165
|
+
const direct = options.direct || CHAT_DIRECT_ADMISSION_MODES.OPEN;
|
|
8166
|
+
const groups = options.groups || CHAT_GROUP_ADMISSION_MODES.OPEN;
|
|
8167
|
+
if (!DIRECT_MODES.has(direct) || !GROUP_MODES.has(groups)) {
|
|
8168
|
+
throw new Error("valid chat admission modes required");
|
|
8169
|
+
}
|
|
8170
|
+
const peers = [...new Map((options.allow || []).map((profile) => {
|
|
8171
|
+
const peerChatPK = cleanChatHex(profile?.chatPK, "allowed peer chat public key");
|
|
8172
|
+
return [peerChatPK, profile];
|
|
8173
|
+
})).values()];
|
|
8174
|
+
if (peers.length > CHAT_ADMISSION_GRANT_COUNT) {
|
|
8175
|
+
throw new Error(`chat admission allows at most ${CHAT_ADMISSION_GRANT_COUNT} direct peers`);
|
|
8176
|
+
}
|
|
8177
|
+
const commitments = peers.map((profile) => {
|
|
8178
|
+
const capability = deriveDirectAdmissionCapability(chatPrivateKey, chatPK, profile.chatPK, chatPK);
|
|
8179
|
+
try {
|
|
8180
|
+
return chatAdmissionCapabilityCommitment(capability);
|
|
8181
|
+
} finally {
|
|
8182
|
+
cleanBytes(capability);
|
|
8183
|
+
}
|
|
8184
|
+
});
|
|
8185
|
+
while (commitments.length < CHAT_ADMISSION_GRANT_COUNT) {
|
|
8186
|
+
const padding = toHex(randomBytes3(32));
|
|
8187
|
+
if (!commitments.includes(padding))
|
|
8188
|
+
commitments.push(padding);
|
|
8189
|
+
}
|
|
8190
|
+
return Object.freeze({
|
|
8191
|
+
v: CHAT_ADMISSION_VERSION,
|
|
8192
|
+
direct,
|
|
8193
|
+
groups,
|
|
8194
|
+
directGrants: Object.freeze(commitments.sort())
|
|
8195
|
+
});
|
|
8196
|
+
}
|
|
8197
|
+
|
|
7805
8198
|
// ../../core/profile.js
|
|
7806
8199
|
var BOT_PROFILE_MARKER = "glyphteck";
|
|
7807
8200
|
function readBotMarker(profile) {
|
|
@@ -7834,6 +8227,7 @@ function normalizeProfile(profile, uid = profile?.uid || null) {
|
|
|
7834
8227
|
chatPK: profile?.chatPK || null,
|
|
7835
8228
|
chatSigningPK: profile?.chatSigningPK || null,
|
|
7836
8229
|
notificationPK: profile?.notificationPK || null,
|
|
8230
|
+
chatAdmission: normalizeChatAdmission(profile?.chatAdmission),
|
|
7837
8231
|
active: profile?.active ?? false,
|
|
7838
8232
|
bot: readBotMarker(profile),
|
|
7839
8233
|
avatarVersion: readAvatarVersion(profile?.avatarVersion),
|
|
@@ -7872,6 +8266,7 @@ var defaultUser = {
|
|
|
7872
8266
|
chatPK: null,
|
|
7873
8267
|
chatSigningPK: null,
|
|
7874
8268
|
notificationPK: null,
|
|
8269
|
+
chatAdmission: normalizeChatAdmission(null),
|
|
7875
8270
|
active: false,
|
|
7876
8271
|
banned: null,
|
|
7877
8272
|
agreement: null,
|
|
@@ -7986,7 +8381,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
7986
8381
|
if (!uid || !avatarCache)
|
|
7987
8382
|
return null;
|
|
7988
8383
|
try {
|
|
7989
|
-
const cached = await avatarCache.read(uid);
|
|
8384
|
+
const cached = await avatarCache.read(uid, expectedVersion);
|
|
7990
8385
|
const version = readAvatarVersion(cached?.version);
|
|
7991
8386
|
if (expectedVersion != null && version !== expectedVersion) {
|
|
7992
8387
|
return null;
|
|
@@ -8296,14 +8691,15 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8296
8691
|
const walletPK = resolveWalletPK(profileData, activeNetwork);
|
|
8297
8692
|
const { chatPK, chatSigningPK } = readProfileChatIdentity(profileData);
|
|
8298
8693
|
const notificationPK = readProfileNotificationPK(profileData);
|
|
8694
|
+
const chatAdmission = normalizeChatAdmission(profileData.chatAdmission);
|
|
8299
8695
|
const active = profileData.active ?? false;
|
|
8300
8696
|
const hasAvatarEntry = !!info.exists && Object.prototype.hasOwnProperty.call(profileData, "avatar");
|
|
8301
8697
|
const avatarVersion2 = readAvatarVersion(profileData.avatar);
|
|
8302
8698
|
const avatar = avatarVersion2 == null ? null : user.avatar;
|
|
8303
|
-
if (user.profileReady && user.uid === authUser.uid && user.username === username && user.identities === identities && user.walletPK === walletPK && user.chatPK === chatPK && user.chatSigningPK === chatSigningPK && user.notificationPK === notificationPK && user.active === active && user.hasAvatarEntry === hasAvatarEntry && user.avatarVersion === avatarVersion2 && user.avatar === avatar) {
|
|
8699
|
+
if (user.profileReady && user.uid === authUser.uid && user.username === username && user.identities === identities && user.walletPK === walletPK && user.chatPK === chatPK && user.chatSigningPK === chatSigningPK && user.notificationPK === notificationPK && user.chatAdmission === chatAdmission && user.active === active && user.hasAvatarEntry === hasAvatarEntry && user.avatarVersion === avatarVersion2 && user.avatar === avatar) {
|
|
8304
8700
|
return user;
|
|
8305
8701
|
}
|
|
8306
|
-
return { ...user, uid: authUser.uid, profileReady: true, username, identities, walletPK, chatPK, chatSigningPK, notificationPK, active, hasAvatarEntry, avatarVersion: avatarVersion2, avatar };
|
|
8702
|
+
return { ...user, uid: authUser.uid, profileReady: true, username, identities, walletPK, chatPK, chatSigningPK, notificationPK, chatAdmission, active, hasAvatarEntry, avatarVersion: avatarVersion2, avatar };
|
|
8307
8703
|
});
|
|
8308
8704
|
const avatarVersion = readAvatarVersion(profileData.avatar);
|
|
8309
8705
|
if (avatarVersion == null) {
|
|
@@ -8342,6 +8738,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8342
8738
|
chatPK: null,
|
|
8343
8739
|
chatSigningPK: null,
|
|
8344
8740
|
notificationPK: null,
|
|
8741
|
+
chatAdmission: normalizeChatAdmission(null),
|
|
8345
8742
|
active: false,
|
|
8346
8743
|
avatarVersion: null,
|
|
8347
8744
|
avatar: null,
|
|
@@ -8684,139 +9081,6 @@ async function pushCriticalInbox(cloud, recipientUid, ping, options = {}, retry
|
|
|
8684
9081
|
}
|
|
8685
9082
|
var criticalDeliveryInternals = Object.freeze({ retryableDeliveryError });
|
|
8686
9083
|
|
|
8687
|
-
// ../../core/chat/protocol.js
|
|
8688
|
-
"use client";
|
|
8689
|
-
var CHAT_PROTOCOL_VERSION = 3;
|
|
8690
|
-
var CHAT_MANIFEST_VERSION = 2;
|
|
8691
|
-
var CHAT_TRANSITION_VERSION = 2;
|
|
8692
|
-
var CHAT_MESSAGE_ENVELOPE_VERSION = 3;
|
|
8693
|
-
var CHAT_SETTINGS_VERSION = 1;
|
|
8694
|
-
|
|
8695
|
-
// ../../core/chat/epochs/manifest.js
|
|
8696
|
-
"use client";
|
|
8697
|
-
var CHAT_LINEAGES = Object.freeze({
|
|
8698
|
-
SELF: "self",
|
|
8699
|
-
DIRECT: "direct",
|
|
8700
|
-
GROUP: "group"
|
|
8701
|
-
});
|
|
8702
|
-
var LINEAGES = new Set(Object.values(CHAT_LINEAGES));
|
|
8703
|
-
var HEX_32_RE2 = /^[0-9a-f]{64}$/u;
|
|
8704
|
-
function cleanChatHex(value, label = "chat value") {
|
|
8705
|
-
const text = cleanText(value).toLowerCase();
|
|
8706
|
-
if (!HEX_32_RE2.test(text)) {
|
|
8707
|
-
throw new Error(`${label} required`);
|
|
8708
|
-
}
|
|
8709
|
-
return text;
|
|
8710
|
-
}
|
|
8711
|
-
function cleanUid(value) {
|
|
8712
|
-
const uid = cleanText(value);
|
|
8713
|
-
if (!uid || uid.length > 128) {
|
|
8714
|
-
throw new Error("chat member uid required");
|
|
8715
|
-
}
|
|
8716
|
-
return uid;
|
|
8717
|
-
}
|
|
8718
|
-
function cleanCreatedAt(value) {
|
|
8719
|
-
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
8720
|
-
throw new Error("chat epoch createdAt required");
|
|
8721
|
-
}
|
|
8722
|
-
return value;
|
|
8723
|
-
}
|
|
8724
|
-
function cleanEpochVersion(value) {
|
|
8725
|
-
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
8726
|
-
throw new Error("chat epoch version required");
|
|
8727
|
-
}
|
|
8728
|
-
return value;
|
|
8729
|
-
}
|
|
8730
|
-
function cleanMember(value) {
|
|
8731
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8732
|
-
throw new Error("invalid chat member");
|
|
8733
|
-
}
|
|
8734
|
-
return {
|
|
8735
|
-
uid: cleanUid(value.uid),
|
|
8736
|
-
chatPK: cleanChatHex(value.chatPK, "member chat key"),
|
|
8737
|
-
chatSigningPK: cleanChatHex(value.chatSigningPK, "member signing key"),
|
|
8738
|
-
notificationPK: cleanChatHex(value.notificationPK, "member notification key"),
|
|
8739
|
-
mlsLeafId: cleanChatHex(value.mlsLeafId, "member mls leaf id")
|
|
8740
|
-
};
|
|
8741
|
-
}
|
|
8742
|
-
function assertUniqueMembers(members) {
|
|
8743
|
-
const uids = new Set;
|
|
8744
|
-
const chatKeys = new Set;
|
|
8745
|
-
const signingKeys = new Set;
|
|
8746
|
-
const mlsLeafIds = new Set;
|
|
8747
|
-
for (const member of members) {
|
|
8748
|
-
if (uids.has(member.uid) || chatKeys.has(member.chatPK) || signingKeys.has(member.chatSigningPK) || mlsLeafIds.has(member.mlsLeafId)) {
|
|
8749
|
-
throw new Error("duplicate chat member");
|
|
8750
|
-
}
|
|
8751
|
-
uids.add(member.uid);
|
|
8752
|
-
chatKeys.add(member.chatPK);
|
|
8753
|
-
signingKeys.add(member.chatSigningPK);
|
|
8754
|
-
mlsLeafIds.add(member.mlsLeafId);
|
|
8755
|
-
}
|
|
8756
|
-
}
|
|
8757
|
-
function assertLineage(manifest, parent) {
|
|
8758
|
-
const memberCount = manifest.members.length;
|
|
8759
|
-
if (memberCount > 2 && manifest.lineage !== CHAT_LINEAGES.GROUP) {
|
|
8760
|
-
throw new Error("group lineage required");
|
|
8761
|
-
}
|
|
8762
|
-
if (!parent) {
|
|
8763
|
-
return;
|
|
8764
|
-
}
|
|
8765
|
-
if (manifest.chatId !== parent.chatId || manifest.parentEpochId !== parent.epochId || manifest.epochVersion !== parent.epochVersion + 1) {
|
|
8766
|
-
throw new Error("invalid chat epoch successor");
|
|
8767
|
-
}
|
|
8768
|
-
if (parent.lineage === CHAT_LINEAGES.GROUP && manifest.lineage !== CHAT_LINEAGES.GROUP) {
|
|
8769
|
-
throw new Error("chat group lineage is permanent");
|
|
8770
|
-
}
|
|
8771
|
-
}
|
|
8772
|
-
function normalizeEpochManifest(value, options = {}) {
|
|
8773
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8774
|
-
throw new Error("chat manifest required");
|
|
8775
|
-
}
|
|
8776
|
-
if (value.v !== CHAT_MANIFEST_VERSION || value.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
8777
|
-
throw new Error("unsupported chat manifest");
|
|
8778
|
-
}
|
|
8779
|
-
if (!Array.isArray(value.members) || value.members.length < 1 || value.members.length > CHAT_MAX_MEMBERS) {
|
|
8780
|
-
throw new Error("invalid chat member count");
|
|
8781
|
-
}
|
|
8782
|
-
const lineage = cleanText(value.lineage);
|
|
8783
|
-
if (!LINEAGES.has(lineage)) {
|
|
8784
|
-
throw new Error("invalid chat lineage");
|
|
8785
|
-
}
|
|
8786
|
-
const members = value.members.map(cleanMember).sort((a, b) => a.chatPK.localeCompare(b.chatPK));
|
|
8787
|
-
assertUniqueMembers(members);
|
|
8788
|
-
const manifest = {
|
|
8789
|
-
v: CHAT_MANIFEST_VERSION,
|
|
8790
|
-
protocol: CHAT_PROTOCOL_VERSION,
|
|
8791
|
-
chatId: cleanChatHex(value.chatId, "chat id"),
|
|
8792
|
-
epochId: cleanChatHex(value.epochId, "chat epoch id"),
|
|
8793
|
-
epochVersion: cleanEpochVersion(value.epochVersion),
|
|
8794
|
-
parentEpochId: value.parentEpochId == null ? null : cleanChatHex(value.parentEpochId, "parent epoch id"),
|
|
8795
|
-
createdAt: cleanCreatedAt(value.createdAt),
|
|
8796
|
-
lineage,
|
|
8797
|
-
members
|
|
8798
|
-
};
|
|
8799
|
-
if (manifest.epochVersion === 1 !== (manifest.parentEpochId === null)) {
|
|
8800
|
-
throw new Error("invalid parent epoch");
|
|
8801
|
-
}
|
|
8802
|
-
const bytes = canonicalBytes(manifest, "chat manifest");
|
|
8803
|
-
if (bytes.length > CHAT_MANIFEST_MAX_BYTES) {
|
|
8804
|
-
throw new Error("chat manifest too large");
|
|
8805
|
-
}
|
|
8806
|
-
assertLineage(manifest, options.parent || null);
|
|
8807
|
-
return manifest;
|
|
8808
|
-
}
|
|
8809
|
-
function epochManifestDigest(manifest) {
|
|
8810
|
-
return toHex(sha256(canonicalBytes(normalizeEpochManifest(manifest), "chat manifest digest")));
|
|
8811
|
-
}
|
|
8812
|
-
function manifestMember(manifest, chatPK) {
|
|
8813
|
-
const key = cleanChatHex(chatPK, "member chat key");
|
|
8814
|
-
return normalizeEpochManifest(manifest).members.find((member) => member.chatPK === key) || null;
|
|
8815
|
-
}
|
|
8816
|
-
function manifestSigningKeys(manifest) {
|
|
8817
|
-
return Object.fromEntries(normalizeEpochManifest(manifest).members.map((member) => [member.chatPK, member.chatSigningPK]));
|
|
8818
|
-
}
|
|
8819
|
-
|
|
8820
9084
|
// ../../core/chat/epochs/state.js
|
|
8821
9085
|
"use client";
|
|
8822
9086
|
var CAPABILITY_WITNESS_SCOPE = "veyl-chat-state-witness-v3:";
|
|
@@ -10028,6 +10292,7 @@ function epochChatSettingsId(epochState) {
|
|
|
10028
10292
|
"use client";
|
|
10029
10293
|
var CHAT_ENTRY_VERSION = 4;
|
|
10030
10294
|
var CHAT_OWNER_EPOCH_ENTRY_VERSION = 2;
|
|
10295
|
+
var CHAT_NOTIFICATION_PREFERENCE_VERSION = 1;
|
|
10031
10296
|
var CHAT_OWNER_RETIREMENT_KINDS = Object.freeze({
|
|
10032
10297
|
LEAVE: "leave",
|
|
10033
10298
|
REMOVED: "removed"
|
|
@@ -10049,6 +10314,16 @@ function entryKey(chatPrivateKey, entryId) {
|
|
|
10049
10314
|
function entryAad(entryId) {
|
|
10050
10315
|
return canonicalBytes({ v: CHAT_ENTRY_VERSION, protocol: CHAT_PROTOCOL_VERSION, entryId }, "chat entry aad");
|
|
10051
10316
|
}
|
|
10317
|
+
function notificationPreferenceKey(chatPrivateKey, entryId) {
|
|
10318
|
+
return deriveKey(toBytes32(chatPrivateKey, "chat private key"), "user-chat-notification-preference-v1", [cleanText(entryId)]);
|
|
10319
|
+
}
|
|
10320
|
+
function notificationPreferenceAad(entryId) {
|
|
10321
|
+
return canonicalBytes({
|
|
10322
|
+
v: CHAT_NOTIFICATION_PREFERENCE_VERSION,
|
|
10323
|
+
protocol: CHAT_PROTOCOL_VERSION,
|
|
10324
|
+
entryId
|
|
10325
|
+
}, "chat notification preference aad");
|
|
10326
|
+
}
|
|
10052
10327
|
function ownerEpochKey(chatPrivateKey, entryId, epochEntryId) {
|
|
10053
10328
|
return deriveKey(toBytes32(chatPrivateKey, "chat private key"), "user-chat-epoch-entry-v3", [entryId, epochEntryId]);
|
|
10054
10329
|
}
|
|
@@ -10084,12 +10359,13 @@ function normalizeRoutes(value) {
|
|
|
10084
10359
|
}
|
|
10085
10360
|
const uid = cleanText(route.uid);
|
|
10086
10361
|
const deliveryCapability = route.deliveryCapability == null ? null : cleanChatHex(route.deliveryCapability, "delivery capability");
|
|
10362
|
+
const admissionCapability = route.admissionCapability == null ? null : cleanChatHex(route.admissionCapability, "admission capability");
|
|
10087
10363
|
const notificationPK = route.notificationPK == null ? null : cleanChatHex(route.notificationPK, "notification key");
|
|
10088
10364
|
const generation = Number.isSafeInteger(route.generation) && route.generation > 0 ? route.generation : 1;
|
|
10089
10365
|
if (!uid || uid.length > 128) {
|
|
10090
10366
|
throw new Error("chat route uid required");
|
|
10091
10367
|
}
|
|
10092
|
-
routes[chatPK] = { uid, deliveryCapability, notificationPK, generation };
|
|
10368
|
+
routes[chatPK] = { uid, deliveryCapability, admissionCapability, notificationPK, generation };
|
|
10093
10369
|
}
|
|
10094
10370
|
return routes;
|
|
10095
10371
|
}
|
|
@@ -10146,6 +10422,32 @@ function normalizeOwnerEntry(value) {
|
|
|
10146
10422
|
retirement: normalizeOwnerRetirement(value.retirement, current)
|
|
10147
10423
|
};
|
|
10148
10424
|
}
|
|
10425
|
+
function withNotificationPreference(entry, value = {}) {
|
|
10426
|
+
const manifest = entry.current.manifest;
|
|
10427
|
+
const notificationMode = normalizeChatNotificationMode(value.notificationMode ?? defaultChatNotificationMode(manifest), manifest);
|
|
10428
|
+
const registrationMatches = value.registeredEpochId === manifest.epochId && value.attentionRegistrationVersion === CHAT_ATTENTION_REGISTRATION_VERSION;
|
|
10429
|
+
return {
|
|
10430
|
+
...entry,
|
|
10431
|
+
attentionRegistrationVersion: registrationMatches ? CHAT_ATTENTION_REGISTRATION_VERSION : 0,
|
|
10432
|
+
notificationMode
|
|
10433
|
+
};
|
|
10434
|
+
}
|
|
10435
|
+
function normalizeNotificationPreference(value, entry) {
|
|
10436
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || value.v !== CHAT_NOTIFICATION_PREFERENCE_VERSION || value.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
10437
|
+
throw new Error("invalid chat notification preference");
|
|
10438
|
+
}
|
|
10439
|
+
const manifest = entry.current.manifest;
|
|
10440
|
+
const notificationMode = normalizeChatNotificationMode(value.notificationMode, manifest);
|
|
10441
|
+
const registeredEpochId = value.registeredEpochId == null ? null : cleanChatHex(value.registeredEpochId, "registered chat epoch id");
|
|
10442
|
+
const attentionRegistrationVersion = registeredEpochId === manifest.epochId && value.attentionRegistrationVersion === CHAT_ATTENTION_REGISTRATION_VERSION ? CHAT_ATTENTION_REGISTRATION_VERSION : 0;
|
|
10443
|
+
return {
|
|
10444
|
+
v: CHAT_NOTIFICATION_PREFERENCE_VERSION,
|
|
10445
|
+
protocol: CHAT_PROTOCOL_VERSION,
|
|
10446
|
+
notificationMode,
|
|
10447
|
+
registeredEpochId: attentionRegistrationVersion ? registeredEpochId : null,
|
|
10448
|
+
attentionRegistrationVersion
|
|
10449
|
+
};
|
|
10450
|
+
}
|
|
10149
10451
|
async function sealOwnChatEntry(chatPrivateKey, entryId, entry) {
|
|
10150
10452
|
const key = entryKey(chatPrivateKey, entryId);
|
|
10151
10453
|
try {
|
|
@@ -10159,14 +10461,14 @@ async function openOwnChatEntry(chatPrivateKey, entryId, body) {
|
|
|
10159
10461
|
const key = entryKey(chatPrivateKey, entryId);
|
|
10160
10462
|
try {
|
|
10161
10463
|
const { nonce, ct } = unpackBodyData(body);
|
|
10162
|
-
return normalizeOwnerEntry(await openJson(key, nonce, ct, entryAad(entryId)));
|
|
10464
|
+
return withNotificationPreference(normalizeOwnerEntry(await openJson(key, nonce, ct, entryAad(entryId))));
|
|
10163
10465
|
} finally {
|
|
10164
10466
|
cleanBytes(key);
|
|
10165
10467
|
}
|
|
10166
10468
|
}
|
|
10167
10469
|
function makeOwnChatEntry(epoch, fields = {}) {
|
|
10168
10470
|
const manifest = normalizeEpochManifest(epoch?.manifest);
|
|
10169
|
-
|
|
10471
|
+
const entry = normalizeOwnerEntry({
|
|
10170
10472
|
v: CHAT_ENTRY_VERSION,
|
|
10171
10473
|
protocol: CHAT_PROTOCOL_VERSION,
|
|
10172
10474
|
ownerRevision: Number.isSafeInteger(fields.ownerRevision) && fields.ownerRevision > 0 ? fields.ownerRevision : 1,
|
|
@@ -10187,6 +10489,48 @@ function makeOwnChatEntry(epoch, fields = {}) {
|
|
|
10187
10489
|
notificationTag: cleanText(fields.notificationTag) || null,
|
|
10188
10490
|
retirement: fields.retirement || null
|
|
10189
10491
|
});
|
|
10492
|
+
return withNotificationPreference(entry, {
|
|
10493
|
+
attentionRegistrationVersion: fields.deliveryRegistered === true ? fields.attentionRegistrationVersion ?? CHAT_ATTENTION_REGISTRATION_VERSION : 0,
|
|
10494
|
+
notificationMode: fields.notificationMode,
|
|
10495
|
+
registeredEpochId: fields.deliveryRegistered === true ? manifest.epochId : null
|
|
10496
|
+
});
|
|
10497
|
+
}
|
|
10498
|
+
async function sealOwnChatNotificationPreference(chatPrivateKey, entryId, entry) {
|
|
10499
|
+
const key = notificationPreferenceKey(chatPrivateKey, entryId);
|
|
10500
|
+
try {
|
|
10501
|
+
const manifest = entry?.current?.manifest;
|
|
10502
|
+
if (!manifest)
|
|
10503
|
+
throw new Error("chat notification preference entry required");
|
|
10504
|
+
const preference = normalizeNotificationPreference({
|
|
10505
|
+
v: CHAT_NOTIFICATION_PREFERENCE_VERSION,
|
|
10506
|
+
protocol: CHAT_PROTOCOL_VERSION,
|
|
10507
|
+
notificationMode: entry.notificationMode,
|
|
10508
|
+
registeredEpochId: entry.attentionRegistrationVersion === CHAT_ATTENTION_REGISTRATION_VERSION ? manifest.epochId : null,
|
|
10509
|
+
attentionRegistrationVersion: entry.attentionRegistrationVersion
|
|
10510
|
+
}, entry);
|
|
10511
|
+
const { nonce, ct } = await sealJson(key, preference, notificationPreferenceAad(entryId));
|
|
10512
|
+
return packBodyData(nonce, ct);
|
|
10513
|
+
} finally {
|
|
10514
|
+
cleanBytes(key);
|
|
10515
|
+
}
|
|
10516
|
+
}
|
|
10517
|
+
async function openOwnChatNotificationPreference(chatPrivateKey, entryId, body, entry) {
|
|
10518
|
+
if (!body)
|
|
10519
|
+
return withNotificationPreference(entry);
|
|
10520
|
+
const key = notificationPreferenceKey(chatPrivateKey, entryId);
|
|
10521
|
+
try {
|
|
10522
|
+
const { nonce, ct } = unpackBodyData(body);
|
|
10523
|
+
const preference = normalizeNotificationPreference(await openJson(key, nonce, ct, notificationPreferenceAad(entryId)), entry);
|
|
10524
|
+
return withNotificationPreference(entry, preference);
|
|
10525
|
+
} finally {
|
|
10526
|
+
cleanBytes(key);
|
|
10527
|
+
}
|
|
10528
|
+
}
|
|
10529
|
+
async function openOwnChatRecord(chatPrivateKey, entryId, record) {
|
|
10530
|
+
if (!record?.body)
|
|
10531
|
+
throw new Error("chat owner record required");
|
|
10532
|
+
const entry = await openOwnChatEntry(chatPrivateKey, entryId, record.body);
|
|
10533
|
+
return openOwnChatNotificationPreference(chatPrivateKey, entryId, record.notificationBody, entry);
|
|
10190
10534
|
}
|
|
10191
10535
|
function normalizeOwnerEpochRecord(value) {
|
|
10192
10536
|
if (!value || typeof value !== "object" || Array.isArray(value) || value.v !== CHAT_OWNER_EPOCH_ENTRY_VERSION || value.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
@@ -10258,6 +10602,7 @@ async function prepareMutation(identity, entryId, current, update) {
|
|
|
10258
10602
|
entryId,
|
|
10259
10603
|
record: {
|
|
10260
10604
|
body: await sealOwnChatEntry(identity.chatPrivateKey, entryId, entry),
|
|
10605
|
+
notificationBody: await sealOwnChatNotificationPreference(identity.chatPrivateKey, entryId, entry),
|
|
10261
10606
|
revision: entry.ownerRevision,
|
|
10262
10607
|
...Number.isFinite(prepared.tsMs) ? { tsMs: prepared.tsMs } : {},
|
|
10263
10608
|
...prepared.touchTs === true ? { touchTs: true } : {}
|
|
@@ -10285,7 +10630,7 @@ async function prepareOwnChatMutation(identity, entryId, current, update) {
|
|
|
10285
10630
|
if (!record?.body || !Number.isSafeInteger(record.revision) || record.revision < 1) {
|
|
10286
10631
|
throw new Error("current chat owner record required");
|
|
10287
10632
|
}
|
|
10288
|
-
const opened = await
|
|
10633
|
+
const opened = await openOwnChatRecord(identity.chatPrivateKey, entryId, record);
|
|
10289
10634
|
if (opened.ownerRevision !== record.revision) {
|
|
10290
10635
|
throw new Error("chat owner revision mismatch");
|
|
10291
10636
|
}
|
|
@@ -10321,6 +10666,7 @@ async function retireOwnChatEntry(cloud, identity, entryId, current, fields = {}
|
|
|
10321
10666
|
...latest,
|
|
10322
10667
|
routes: {},
|
|
10323
10668
|
deliveryRegistered: false,
|
|
10669
|
+
attentionRegistrationVersion: 0,
|
|
10324
10670
|
notificationTag: null,
|
|
10325
10671
|
retirement
|
|
10326
10672
|
},
|
|
@@ -10334,7 +10680,7 @@ async function openOwnChatMutationEntry(identity, entryId, record) {
|
|
|
10334
10680
|
return null;
|
|
10335
10681
|
if (!record?.body || !Number.isSafeInteger(record.revision) || record.revision < 1)
|
|
10336
10682
|
throw new Error("current chat owner record required");
|
|
10337
|
-
const current = await
|
|
10683
|
+
const current = await openOwnChatRecord(identity.chatPrivateKey, entryId, record);
|
|
10338
10684
|
if (current.ownerRevision !== record.revision) {
|
|
10339
10685
|
throw new Error("chat owner revision mismatch");
|
|
10340
10686
|
}
|
|
@@ -10439,6 +10785,166 @@ var MAX_REACTIONS = CHAT_MAX_REACTIONS;
|
|
|
10439
10785
|
var HOLD_VISIBLE_KEY = "__holdVisible";
|
|
10440
10786
|
var SOURCE_GONE_VISIBLE_KEY = "__sourceGoneVisible";
|
|
10441
10787
|
|
|
10788
|
+
// ../../core/username.js
|
|
10789
|
+
var MAX_USERNAME = USERNAME_MAX_CHARS;
|
|
10790
|
+
var usernameKeyRegex = /^[a-z0-9]$/i;
|
|
10791
|
+
var usernameStripRegex = /[^a-z0-9]/g;
|
|
10792
|
+
var usernameRegex = new RegExp(`^[a-z0-9]{1,${MAX_USERNAME}}$`);
|
|
10793
|
+
function normalizeUsername(value = "") {
|
|
10794
|
+
return String(value).trim().toLowerCase();
|
|
10795
|
+
}
|
|
10796
|
+
function cleanUsername(value = "") {
|
|
10797
|
+
return normalizeUsername(value).replace(usernameStripRegex, "").slice(0, MAX_USERNAME);
|
|
10798
|
+
}
|
|
10799
|
+
function isUsername(value = "") {
|
|
10800
|
+
return usernameRegex.test(value);
|
|
10801
|
+
}
|
|
10802
|
+
function isUsernameKey(value = "") {
|
|
10803
|
+
return usernameKeyRegex.test(value);
|
|
10804
|
+
}
|
|
10805
|
+
|
|
10806
|
+
// ../../core/chat/messages/mentions.js
|
|
10807
|
+
"use client";
|
|
10808
|
+
var CHAT_MENTION_KINDS = Object.freeze({
|
|
10809
|
+
MEMBER: "member",
|
|
10810
|
+
EVERYONE: "everyone"
|
|
10811
|
+
});
|
|
10812
|
+
var CHAT_PK_RE = /^[0-9a-f]{64}$/u;
|
|
10813
|
+
function cleanChatPK(value) {
|
|
10814
|
+
return typeof value === "string" && CHAT_PK_RE.test(value) ? value : "";
|
|
10815
|
+
}
|
|
10816
|
+
function sourceMention(value, allowBinding = false) {
|
|
10817
|
+
const target = allowBinding && value?.target && typeof value.target === "object" ? value.target : value;
|
|
10818
|
+
if (target?.kind === CHAT_MENTION_KINDS.EVERYONE) {
|
|
10819
|
+
return {
|
|
10820
|
+
start: value?.start,
|
|
10821
|
+
end: value?.end,
|
|
10822
|
+
kind: CHAT_MENTION_KINDS.EVERYONE
|
|
10823
|
+
};
|
|
10824
|
+
}
|
|
10825
|
+
if (target?.kind !== CHAT_MENTION_KINDS.MEMBER)
|
|
10826
|
+
return null;
|
|
10827
|
+
return {
|
|
10828
|
+
start: value?.start,
|
|
10829
|
+
end: value?.end,
|
|
10830
|
+
kind: CHAT_MENTION_KINDS.MEMBER,
|
|
10831
|
+
chatPK: target.chatPK
|
|
10832
|
+
};
|
|
10833
|
+
}
|
|
10834
|
+
function mentionToken(text, mention) {
|
|
10835
|
+
if (!Number.isSafeInteger(mention?.start) || !Number.isSafeInteger(mention?.end) || mention.start < 0 || mention.end <= mention.start || mention.end > text.length) {
|
|
10836
|
+
return "";
|
|
10837
|
+
}
|
|
10838
|
+
return text.slice(mention.start, mention.end);
|
|
10839
|
+
}
|
|
10840
|
+
function canonicalMention(text, value, allowBinding = false) {
|
|
10841
|
+
const mention = sourceMention(value, allowBinding);
|
|
10842
|
+
const token = mentionToken(text, mention);
|
|
10843
|
+
if (!token)
|
|
10844
|
+
return null;
|
|
10845
|
+
const before = text[mention.start - 1] || "";
|
|
10846
|
+
const after = text[mention.end] || "";
|
|
10847
|
+
if (before && (before === "@" || isUsernameKey(before)) || after && isUsernameKey(after)) {
|
|
10848
|
+
return null;
|
|
10849
|
+
}
|
|
10850
|
+
if (mention.kind === CHAT_MENTION_KINDS.EVERYONE) {
|
|
10851
|
+
return token === "@everyone" ? mention : null;
|
|
10852
|
+
}
|
|
10853
|
+
if (!cleanChatPK(mention.chatPK) || token[0] !== "@" || !isUsername(token.slice(1))) {
|
|
10854
|
+
return null;
|
|
10855
|
+
}
|
|
10856
|
+
return mention;
|
|
10857
|
+
}
|
|
10858
|
+
function invalidMentions(message, strict) {
|
|
10859
|
+
if (strict)
|
|
10860
|
+
throw new Error(message);
|
|
10861
|
+
return [];
|
|
10862
|
+
}
|
|
10863
|
+
function manifestContext(manifest) {
|
|
10864
|
+
const members = Array.isArray(manifest?.members) ? manifest.members : [];
|
|
10865
|
+
const memberChatPKs = new Set(members.map((member) => cleanChatPK(member?.chatPK)).filter(Boolean));
|
|
10866
|
+
return {
|
|
10867
|
+
group: manifest?.lineage === "group" && members.length > 0,
|
|
10868
|
+
memberCount: members.length,
|
|
10869
|
+
memberChatPKs
|
|
10870
|
+
};
|
|
10871
|
+
}
|
|
10872
|
+
function normalizeMentionList(message, manifest, { strict = false, senderChatPK = "" } = {}) {
|
|
10873
|
+
const raw = message?.mentions;
|
|
10874
|
+
if (raw == null)
|
|
10875
|
+
return [];
|
|
10876
|
+
if (!Array.isArray(raw))
|
|
10877
|
+
return invalidMentions("invalid chat mentions", strict);
|
|
10878
|
+
if (!raw.length)
|
|
10879
|
+
return [];
|
|
10880
|
+
if (message?.t !== "txt" || typeof message?.c !== "string") {
|
|
10881
|
+
return invalidMentions("text message required for mentions", strict);
|
|
10882
|
+
}
|
|
10883
|
+
const context = manifestContext(manifest);
|
|
10884
|
+
if (!context.group)
|
|
10885
|
+
return invalidMentions("group chat required for mentions", strict);
|
|
10886
|
+
const sender = cleanChatPK(senderChatPK || message?.s || message?.from);
|
|
10887
|
+
const mentions = [];
|
|
10888
|
+
const uniqueTargets = new Set;
|
|
10889
|
+
let previousEnd = 0;
|
|
10890
|
+
for (const value of raw) {
|
|
10891
|
+
const mention = canonicalMention(message.c, value);
|
|
10892
|
+
if (!mention || mention.start < previousEnd) {
|
|
10893
|
+
if (strict)
|
|
10894
|
+
throw new Error("invalid chat mention");
|
|
10895
|
+
continue;
|
|
10896
|
+
}
|
|
10897
|
+
if (mention.kind === CHAT_MENTION_KINDS.EVERYONE) {
|
|
10898
|
+
if (context.memberCount > CHAT_ORDINARY_DELIVERY_MAX_MEMBERS) {
|
|
10899
|
+
if (strict)
|
|
10900
|
+
throw new Error("everyone mentions are unavailable in large groups");
|
|
10901
|
+
continue;
|
|
10902
|
+
}
|
|
10903
|
+
} else {
|
|
10904
|
+
if (!context.memberChatPKs.has(mention.chatPK) || mention.chatPK === sender) {
|
|
10905
|
+
if (strict)
|
|
10906
|
+
throw new Error("mention target must be a current chat member");
|
|
10907
|
+
continue;
|
|
10908
|
+
}
|
|
10909
|
+
if (context.memberCount > CHAT_ORDINARY_DELIVERY_MAX_MEMBERS && !uniqueTargets.has(mention.chatPK) && uniqueTargets.size >= CHAT_LARGE_GROUP_MENTION_MAX_TARGETS) {
|
|
10910
|
+
if (strict)
|
|
10911
|
+
throw new Error(`large group mentions support up to ${CHAT_LARGE_GROUP_MENTION_MAX_TARGETS} members`);
|
|
10912
|
+
continue;
|
|
10913
|
+
}
|
|
10914
|
+
uniqueTargets.add(mention.chatPK);
|
|
10915
|
+
}
|
|
10916
|
+
mentions.push(mention);
|
|
10917
|
+
previousEnd = mention.end;
|
|
10918
|
+
}
|
|
10919
|
+
return mentions;
|
|
10920
|
+
}
|
|
10921
|
+
function normalizeChatMessageMentions(message, manifest, options = {}) {
|
|
10922
|
+
if (!message || typeof message !== "object" || Array.isArray(message))
|
|
10923
|
+
return message;
|
|
10924
|
+
if (message.t !== "txt") {
|
|
10925
|
+
const { mentions: raw, ...rest } = message;
|
|
10926
|
+
if (options.strict && raw != null) {
|
|
10927
|
+
throw new Error("text message required for mentions");
|
|
10928
|
+
}
|
|
10929
|
+
return rest;
|
|
10930
|
+
}
|
|
10931
|
+
return {
|
|
10932
|
+
...message,
|
|
10933
|
+
mentions: normalizeMentionList(message, manifest, options)
|
|
10934
|
+
};
|
|
10935
|
+
}
|
|
10936
|
+
function hasChatMessageMentions(message) {
|
|
10937
|
+
return Array.isArray(message?.mentions) && message.mentions.length > 0;
|
|
10938
|
+
}
|
|
10939
|
+
function chatMessageMentionTargets(message, manifest, options = {}) {
|
|
10940
|
+
const normalized = normalizeChatMessageMentions(message, manifest, { ...options, strict: true });
|
|
10941
|
+
const sender = cleanChatPK(options.senderChatPK || normalized?.s || normalized?.from);
|
|
10942
|
+
const memberChatPKs = [...new Set(normalized.mentions.filter((mention) => mention.kind === CHAT_MENTION_KINDS.MEMBER).map((mention) => mention.chatPK))].sort();
|
|
10943
|
+
const everyone = normalized.mentions.some((mention) => mention.kind === CHAT_MENTION_KINDS.EVERYONE);
|
|
10944
|
+
const recipientChatPKs = everyone ? [...new Set((manifest?.members || []).map((member) => cleanChatPK(member?.chatPK)).filter((chatPK) => chatPK && chatPK !== sender))].sort() : memberChatPKs;
|
|
10945
|
+
return { everyone, memberChatPKs, recipientChatPKs };
|
|
10946
|
+
}
|
|
10947
|
+
|
|
10442
10948
|
// ../../core/chat/messages/text.js
|
|
10443
10949
|
var DOMAIN_LABEL_PATTERN = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
|
|
10444
10950
|
var DOMAIN_TLD_PATTERN = "(?:[a-z]{2,63}|xn--[a-z0-9-]{2,59})";
|
|
@@ -12287,7 +12793,6 @@ function getDisplayMessages(messages, selfChatPublicKey, peerChatPublicKey, opti
|
|
|
12287
12793
|
|
|
12288
12794
|
// ../../core/chat/messages/compact.js
|
|
12289
12795
|
"use client";
|
|
12290
|
-
var pendingCompactKeys = new Set;
|
|
12291
12796
|
function ownsMessageCompaction(memberChatPKs, chatPK) {
|
|
12292
12797
|
const members = [...new Set((memberChatPKs || []).filter(Boolean))].sort();
|
|
12293
12798
|
return !!chatPK && members[0] === chatPK;
|
|
@@ -12363,39 +12868,45 @@ function compactKey(chatId, message) {
|
|
|
12363
12868
|
const id = cleanText(message?.id);
|
|
12364
12869
|
return chatId && id ? `${chatId}/${id}` : "";
|
|
12365
12870
|
}
|
|
12366
|
-
function claimMessages(chatId, messages) {
|
|
12871
|
+
function claimMessages(maintenance, chatId, messages) {
|
|
12367
12872
|
const claimed = [];
|
|
12368
12873
|
for (const message of messages || []) {
|
|
12369
12874
|
const key = compactKey(chatId, message);
|
|
12370
|
-
if (!key
|
|
12875
|
+
if (!key)
|
|
12876
|
+
continue;
|
|
12877
|
+
const lease = maintenance.claim("message-compaction", key);
|
|
12878
|
+
if (!lease)
|
|
12371
12879
|
continue;
|
|
12372
|
-
|
|
12373
|
-
claimed.push(message);
|
|
12880
|
+
claimed.push({ lease, message });
|
|
12374
12881
|
}
|
|
12375
12882
|
return claimed;
|
|
12376
12883
|
}
|
|
12377
|
-
function releaseMessages(
|
|
12378
|
-
for (const
|
|
12379
|
-
|
|
12380
|
-
if (key)
|
|
12381
|
-
pendingCompactKeys.delete(key);
|
|
12382
|
-
}
|
|
12884
|
+
function releaseMessages(claimed) {
|
|
12885
|
+
for (const item of claimed || [])
|
|
12886
|
+
item.lease.release();
|
|
12383
12887
|
}
|
|
12384
|
-
async function compactMessages({ chatId, messages, deletedKeys, protectedKeys, deleteMessages, scannedKeys, scanComplete }) {
|
|
12888
|
+
async function compactMessages({ chatId, maintenance, messages, deletedKeys, protectedKeys, deleteMessages, scannedKeys, scanComplete }) {
|
|
12385
12889
|
if (!chatId || typeof deleteMessages !== "function")
|
|
12386
12890
|
return [];
|
|
12387
|
-
|
|
12388
|
-
|
|
12891
|
+
if (!maintenance?.claim)
|
|
12892
|
+
throw new Error("account message maintenance required");
|
|
12893
|
+
const claimed = claimMessages(maintenance, chatId, getCompactMessages(messages, { deletedKeys, protectedKeys, scannedKeys, scanComplete }));
|
|
12894
|
+
if (!claimed.length)
|
|
12389
12895
|
return [];
|
|
12896
|
+
const targets = claimed.map((item) => item.message);
|
|
12390
12897
|
try {
|
|
12898
|
+
for (const item of claimed)
|
|
12899
|
+
item.lease.assertCurrent();
|
|
12391
12900
|
await deleteMessages(targets);
|
|
12901
|
+
for (const item of claimed)
|
|
12902
|
+
item.lease.assertCurrent();
|
|
12392
12903
|
if (deletedKeys?.add) {
|
|
12393
12904
|
for (const message of targets)
|
|
12394
12905
|
addMessageKeys(deletedKeys, message);
|
|
12395
12906
|
}
|
|
12396
12907
|
return targets;
|
|
12397
12908
|
} finally {
|
|
12398
|
-
releaseMessages(
|
|
12909
|
+
releaseMessages(claimed);
|
|
12399
12910
|
}
|
|
12400
12911
|
}
|
|
12401
12912
|
|
|
@@ -12927,20 +13438,23 @@ function ownerPreview(senderChatPK, message, messageId, head, tsMs, ttlMs) {
|
|
|
12927
13438
|
}
|
|
12928
13439
|
async function ensureDeliveryRoute(cloud, identity, epochState, entry, shouldPing) {
|
|
12929
13440
|
if (!shouldPing)
|
|
12930
|
-
return { capability: "", registered: entry
|
|
13441
|
+
return { capability: "", registered: hasCurrentChatAttentionRegistration(entry) };
|
|
12931
13442
|
if (!identity.notificationPK) {
|
|
12932
13443
|
throw new Error("notification identity required");
|
|
12933
13444
|
}
|
|
12934
|
-
const
|
|
13445
|
+
const registration = chatDeliveryRegistration(epochState.stateCapability, {
|
|
13446
|
+
attentionSecret: epochState.epochSecret,
|
|
12935
13447
|
chatId: epochState.manifest.chatId,
|
|
12936
13448
|
recipientChatPK: identity.chatPK,
|
|
12937
|
-
generation: epochState.manifest.epochVersion
|
|
13449
|
+
generation: epochState.manifest.epochVersion,
|
|
13450
|
+
manifest: epochState.manifest,
|
|
13451
|
+
notificationMode: entry?.notificationMode
|
|
12938
13452
|
});
|
|
12939
|
-
if (entry
|
|
12940
|
-
return { capability, registered: true };
|
|
13453
|
+
if (hasCurrentChatAttentionRegistration(entry)) {
|
|
13454
|
+
return { capability: registration.capability, registered: true };
|
|
12941
13455
|
}
|
|
12942
|
-
const registered = await cloud.delivery?.register?.(
|
|
12943
|
-
return { capability: registered ? capability : "", registered };
|
|
13456
|
+
const registered = await cloud.delivery?.register?.(registration).then(() => true, () => false) || false;
|
|
13457
|
+
return { capability: registered ? registration.capability : "", registered };
|
|
12944
13458
|
}
|
|
12945
13459
|
function mergedRoutes(entry, options, epochState) {
|
|
12946
13460
|
const routes = {
|
|
@@ -12954,6 +13468,7 @@ function mergedRoutes(entry, options, epochState) {
|
|
|
12954
13468
|
...routes[member.chatPK] || {},
|
|
12955
13469
|
uid: member.uid,
|
|
12956
13470
|
notificationPK: member.notificationPK,
|
|
13471
|
+
admissionCapability: routes[member.chatPK]?.admissionCapability || null,
|
|
12957
13472
|
deliveryCapability: deriveChatDeliveryCapability(epochState.stateCapability, {
|
|
12958
13473
|
chatId: manifest.chatId,
|
|
12959
13474
|
recipientChatPK: member.chatPK,
|
|
@@ -12972,6 +13487,7 @@ async function makeOwnerEntryMutation(identity, epochState, existing, routes, fi
|
|
|
12972
13487
|
...existing,
|
|
12973
13488
|
routes,
|
|
12974
13489
|
deliveryRegistered: fields.deliveryRegistered || existing.deliveryRegistered,
|
|
13490
|
+
attentionRegistrationVersion: fields.deliveryRegistered ? CHAT_ATTENTION_REGISTRATION_VERSION : existing.attentionRegistrationVersion,
|
|
12975
13491
|
notificationTag: notificationChatTag(epochState.stateCapability, chatId, identity.chatPK),
|
|
12976
13492
|
...fields.settings ? {
|
|
12977
13493
|
current: { ...existing.current, settings: normalizeChatSettingsProjection(fields.settings) }
|
|
@@ -12995,12 +13511,13 @@ async function makeOwnerEntryMutation(identity, epochState, existing, routes, fi
|
|
|
12995
13511
|
...current,
|
|
12996
13512
|
routes: { ...current.routes, ...routes },
|
|
12997
13513
|
deliveryRegistered: fields.deliveryRegistered || current.deliveryRegistered,
|
|
13514
|
+
attentionRegistrationVersion: fields.deliveryRegistered ? CHAT_ATTENTION_REGISTRATION_VERSION : current.attentionRegistrationVersion,
|
|
12998
13515
|
notificationTag: notificationChatTag(epochState.stateCapability, chatId, identity.chatPK),
|
|
12999
13516
|
...fields.settings ? {
|
|
13000
13517
|
current: { ...current.current, settings: normalizeChatSettingsProjection(fields.settings) }
|
|
13001
13518
|
} : {}
|
|
13002
13519
|
};
|
|
13003
|
-
const unchanged = JSON.stringify(next.routes) === JSON.stringify(current.routes) && next.deliveryRegistered === current.deliveryRegistered && next.notificationTag === current.notificationTag && next.current.settings.digest === current.current.settings.digest;
|
|
13520
|
+
const unchanged = JSON.stringify(next.routes) === JSON.stringify(current.routes) && next.deliveryRegistered === current.deliveryRegistered && next.attentionRegistrationVersion === current.attentionRegistrationVersion && next.notificationTag === current.notificationTag && next.current.settings.digest === current.current.settings.digest;
|
|
13004
13521
|
return unchanged ? { result: current } : {
|
|
13005
13522
|
entry: next,
|
|
13006
13523
|
...Number.isFinite(fields.tsMs) ? { tsMs: fields.tsMs } : {}
|
|
@@ -13040,16 +13557,20 @@ async function makeChatRecipientDeliveries(identity, epochState, routes, message
|
|
|
13040
13557
|
if (!fields.shouldPing || fields.deliverRecipients === false)
|
|
13041
13558
|
return [];
|
|
13042
13559
|
const capabilities = chatEpochCapabilities(epochState);
|
|
13043
|
-
const
|
|
13044
|
-
const
|
|
13045
|
-
|
|
13560
|
+
const currentMemberChatPKs = new Set(epochState.manifest.members.map((member) => member.chatPK));
|
|
13561
|
+
const targetedRecipients = new Set((fields.recipientChatPKs || []).map((value) => cleanText(value).toLowerCase()).filter((chatPK) => chatPK !== identity.chatPK && currentMemberChatPKs.has(chatPK)));
|
|
13562
|
+
const attentionRecipients = new Set((fields.attentionRecipientChatPKs || []).map((value) => cleanText(value).toLowerCase()).filter((chatPK) => chatPK !== identity.chatPK && currentMemberChatPKs.has(chatPK)));
|
|
13563
|
+
const rotation = fields.deliveryClass === "rotation";
|
|
13564
|
+
if (!rotation && !capabilities.ordinaryDelivery && !targetedRecipients.size)
|
|
13046
13565
|
return [];
|
|
13047
13566
|
const deliveries = [];
|
|
13048
13567
|
for (const member of epochState.manifest.members) {
|
|
13049
13568
|
if (member.chatPK === identity.chatPK)
|
|
13050
13569
|
continue;
|
|
13051
|
-
if (!
|
|
13570
|
+
if (!rotation && !capabilities.ordinaryDelivery && !targetedRecipients.has(member.chatPK))
|
|
13052
13571
|
continue;
|
|
13572
|
+
const attentionKind = rotation ? CHAT_ATTENTION_KINDS.ROTATION : attentionRecipients.has(member.chatPK) ? CHAT_ATTENTION_KINDS.MENTION : fields.silent === true || !capabilities.ordinaryDelivery ? CHAT_ATTENTION_KINDS.SILENT : CHAT_ATTENTION_KINDS.GENERAL;
|
|
13573
|
+
const attentionPriority = attentionKind === CHAT_ATTENTION_KINDS.ROTATION ? 3 : attentionKind === CHAT_ATTENTION_KINDS.MENTION ? 2 : attentionKind === CHAT_ATTENTION_KINDS.GENERAL ? 1 : 0;
|
|
13053
13574
|
const route = routes[member.chatPK] || {};
|
|
13054
13575
|
const descriptor = await sealNotificationDescriptor(member.notificationPK, {
|
|
13055
13576
|
peerTag: notificationChatTag(epochState.stateCapability, epochState.manifest.chatId, member.chatPK),
|
|
@@ -13069,7 +13590,10 @@ async function makeChatRecipientDeliveries(identity, epochState, routes, message
|
|
|
13069
13590
|
});
|
|
13070
13591
|
deliveries.push({
|
|
13071
13592
|
recipientChatPK: member.chatPK,
|
|
13072
|
-
...route.deliveryCapability ? { capability: route.deliveryCapability } : {
|
|
13593
|
+
...route.deliveryCapability ? { capability: route.deliveryCapability } : {
|
|
13594
|
+
recipientUid: member.uid,
|
|
13595
|
+
admissionCapability: route.admissionCapability || null
|
|
13596
|
+
},
|
|
13073
13597
|
deliveryId: deriveChatInboxDeliveryId(epochState.stateCapability, {
|
|
13074
13598
|
chatId: epochState.manifest.chatId,
|
|
13075
13599
|
recipientChatPK: member.chatPK,
|
|
@@ -13078,7 +13602,14 @@ async function makeChatRecipientDeliveries(identity, epochState, routes, message
|
|
|
13078
13602
|
ping,
|
|
13079
13603
|
descriptor,
|
|
13080
13604
|
routeTag: notificationRouteTag(descriptor),
|
|
13081
|
-
|
|
13605
|
+
attentionProof: deriveChatAttentionCapability(epochState.epochSecret, {
|
|
13606
|
+
chatId: epochState.manifest.chatId,
|
|
13607
|
+
recipientChatPK: member.chatPK,
|
|
13608
|
+
generation: epochState.manifest.epochVersion,
|
|
13609
|
+
kind: attentionKind
|
|
13610
|
+
}),
|
|
13611
|
+
attentionPriority,
|
|
13612
|
+
notify: attentionKind !== CHAT_ATTENTION_KINDS.SILENT
|
|
13082
13613
|
});
|
|
13083
13614
|
}
|
|
13084
13615
|
return deliveries;
|
|
@@ -13096,9 +13627,14 @@ async function prepareMsgRecord(identity, epochState, message, options = {}) {
|
|
|
13096
13627
|
}
|
|
13097
13628
|
const chatRetention = cleanChatRetention(options.retention ?? epochState.settings.values.retention);
|
|
13098
13629
|
const tsMs = Date.now();
|
|
13099
|
-
const
|
|
13630
|
+
const retainedMessage = withMessageRetention(message, chatRetention);
|
|
13631
|
+
const messagePayload = normalizeChatMessageMentions(retainedMessage, epochState.manifest, {
|
|
13632
|
+
strict: true,
|
|
13633
|
+
senderChatPK: identity.chatPK
|
|
13634
|
+
});
|
|
13100
13635
|
const retention = getMessageRetention(messagePayload, chatRetention);
|
|
13101
13636
|
const actionOp = options.actionOp || actionOpForPayload(messagePayload);
|
|
13637
|
+
const mentionTargets = actionOp === CHAT_ACTION_OPS.CREATE && messagePayload?.t === "txt" ? chatMessageMentionTargets(messagePayload, epochState.manifest, { senderChatPK: identity.chatPK }) : { recipientChatPKs: [] };
|
|
13102
13638
|
const { head, body } = await sealMsg(epoch, messagePayload, {
|
|
13103
13639
|
op: actionOp,
|
|
13104
13640
|
target: options.actionTarget,
|
|
@@ -13113,6 +13649,7 @@ async function prepareMsgRecord(identity, epochState, message, options = {}) {
|
|
|
13113
13649
|
cid: head.cid,
|
|
13114
13650
|
record: { lane: epoch.messageLane, head, body, ttlMs },
|
|
13115
13651
|
message: ownerPreview(identity.chatPK, messagePayload, messageId, head, tsMs, ttlMs),
|
|
13652
|
+
mentionRecipientChatPKs: mentionTargets.recipientChatPKs,
|
|
13116
13653
|
tsMs
|
|
13117
13654
|
};
|
|
13118
13655
|
} finally {
|
|
@@ -13145,6 +13682,7 @@ async function sendMsg(cloud, senderChatPK, senderPrivateKey, _receiverChatPK, m
|
|
|
13145
13682
|
msgId: messageId,
|
|
13146
13683
|
record,
|
|
13147
13684
|
message: sentMessage,
|
|
13685
|
+
mentionRecipientChatPKs,
|
|
13148
13686
|
tsMs
|
|
13149
13687
|
} = prepared;
|
|
13150
13688
|
const updatePreview = options.updatePreview !== false;
|
|
@@ -13153,7 +13691,7 @@ async function sendMsg(cloud, senderChatPK, senderPrivateKey, _receiverChatPK, m
|
|
|
13153
13691
|
const routes = mergedRoutes(options.ownEntry, options, epochState);
|
|
13154
13692
|
const preview = updatePreview ? sentMessage : null;
|
|
13155
13693
|
const settingsProjection = options.settingsProjection ? normalizeChatSettingsProjection(options.settingsProjection) : null;
|
|
13156
|
-
const ownerStateChanged = !options.ownEntry || settingsProjection || shouldPing && routeState.registered && options.ownEntry
|
|
13694
|
+
const ownerStateChanged = !options.ownEntry || settingsProjection || shouldPing && routeState.registered && !hasCurrentChatAttentionRegistration(options.ownEntry);
|
|
13157
13695
|
const ownerMutation = ownerStateChanged ? await makeOwnerEntryMutation(identity, epochState, options.ownEntry, routes, {
|
|
13158
13696
|
settings: settingsProjection,
|
|
13159
13697
|
immediate: !!settingsProjection || options.ownerImmediate === true,
|
|
@@ -13166,13 +13704,18 @@ async function sendMsg(cloud, senderChatPK, senderPrivateKey, _receiverChatPK, m
|
|
|
13166
13704
|
kind: pingKind,
|
|
13167
13705
|
tsMs
|
|
13168
13706
|
});
|
|
13707
|
+
const deliveryRecipientChatPKs = [
|
|
13708
|
+
...options.deliveryRecipientChatPKs || [],
|
|
13709
|
+
...mentionRecipientChatPKs
|
|
13710
|
+
];
|
|
13169
13711
|
const deliveries = await makeChatRecipientDeliveries(identity, epochState, routes, messageId, {
|
|
13170
13712
|
shouldPing,
|
|
13171
13713
|
deliverRecipients: options.deliverRecipients !== false,
|
|
13172
13714
|
kind: pingKind,
|
|
13173
13715
|
ownDeliveryCapability: routeState.capability,
|
|
13174
|
-
|
|
13175
|
-
recipientChatPKs:
|
|
13716
|
+
silent: options.notify === false,
|
|
13717
|
+
recipientChatPKs: deliveryRecipientChatPKs,
|
|
13718
|
+
attentionRecipientChatPKs: mentionRecipientChatPKs,
|
|
13176
13719
|
tsMs
|
|
13177
13720
|
});
|
|
13178
13721
|
const write = await cloud.chat.messages.send({
|
|
@@ -13794,14 +14337,19 @@ function normalizeDelivery(value) {
|
|
|
13794
14337
|
throw new Error("invalid chat membership delivery mode");
|
|
13795
14338
|
const recipientUid = cleanText(value.recipientUid);
|
|
13796
14339
|
const capability = cleanText(value.capability).toLowerCase();
|
|
14340
|
+
const admissionCapability = cleanText(value.admissionCapability).toLowerCase();
|
|
13797
14341
|
if (mode === "critical" && !recipientUid || mode === "established" && !/^[0-9a-f]{64}$/u.test(capability)) {
|
|
13798
14342
|
throw new Error("invalid chat membership delivery target");
|
|
13799
14343
|
}
|
|
14344
|
+
if (admissionCapability && !/^[0-9a-f]{64}$/u.test(admissionCapability)) {
|
|
14345
|
+
throw new Error("invalid chat admission capability");
|
|
14346
|
+
}
|
|
13800
14347
|
return {
|
|
13801
14348
|
mode,
|
|
13802
14349
|
recipientUid,
|
|
13803
14350
|
recipientChatPK: cleanChatHex(value.recipientChatPK, "membership recipient"),
|
|
13804
14351
|
capability,
|
|
14352
|
+
admissionCapability,
|
|
13805
14353
|
deliveryId: cleanChatHex(value.deliveryId, "membership delivery id"),
|
|
13806
14354
|
ping: openPing(value.ping),
|
|
13807
14355
|
descriptor: openDescriptor(value.descriptor),
|
|
@@ -14012,9 +14560,11 @@ function successorManifest(parent, members, epochId) {
|
|
|
14012
14560
|
members
|
|
14013
14561
|
}, { parent, official: true });
|
|
14014
14562
|
}
|
|
14015
|
-
function nextRoutes(entry, manifest, stateCapability) {
|
|
14563
|
+
function nextRoutes(entry, manifest, stateCapability, privateMembers = []) {
|
|
14564
|
+
const privateByChatPK = new Map(privateMembers.map((member) => [member.chatPK, member]));
|
|
14016
14565
|
return Object.fromEntries(manifest.members.map((member) => {
|
|
14017
14566
|
const current = entry.routes?.[member.chatPK] || {};
|
|
14567
|
+
const privateMember = privateByChatPK.get(member.chatPK) || {};
|
|
14018
14568
|
return [member.chatPK, {
|
|
14019
14569
|
uid: member.uid,
|
|
14020
14570
|
deliveryCapability: deriveChatDeliveryCapability(stateCapability, {
|
|
@@ -14023,6 +14573,7 @@ function nextRoutes(entry, manifest, stateCapability) {
|
|
|
14023
14573
|
generation: manifest.epochVersion
|
|
14024
14574
|
}),
|
|
14025
14575
|
notificationPK: member.notificationPK || current.notificationPK || null,
|
|
14576
|
+
admissionCapability: privateMember.admissionCapability || current.admissionCapability || null,
|
|
14026
14577
|
generation: manifest.epochVersion
|
|
14027
14578
|
}];
|
|
14028
14579
|
}));
|
|
@@ -14071,7 +14622,11 @@ async function makeMlsWelcomeDeliveries(identity, nextState2, addedMembers, opti
|
|
|
14071
14622
|
senderUid: identity.uid,
|
|
14072
14623
|
messageId,
|
|
14073
14624
|
transitionMessageId: nextState2.transitionMessageId,
|
|
14074
|
-
deliveryCapability:
|
|
14625
|
+
deliveryCapability: deriveChatDeliveryCapability(nextState2.stateCapability, {
|
|
14626
|
+
chatId: nextState2.manifest.chatId,
|
|
14627
|
+
recipientChatPK: identity.chatPK,
|
|
14628
|
+
generation: nextState2.manifest.epochVersion
|
|
14629
|
+
}),
|
|
14075
14630
|
notificationPK: identity.notificationPK,
|
|
14076
14631
|
transitionCommitment: nextState2.transitionCommitment,
|
|
14077
14632
|
settingsId,
|
|
@@ -14092,6 +14647,7 @@ async function makeMlsWelcomeDeliveries(identity, nextState2, addedMembers, opti
|
|
|
14092
14647
|
ping,
|
|
14093
14648
|
descriptor,
|
|
14094
14649
|
routeTag,
|
|
14650
|
+
admissionCapability: member.admissionCapability || null,
|
|
14095
14651
|
member
|
|
14096
14652
|
});
|
|
14097
14653
|
}
|
|
@@ -14106,7 +14662,7 @@ async function makeTransitionWakes(identity, parentState, entry, messageId, tsMs
|
|
|
14106
14662
|
shouldPing: true,
|
|
14107
14663
|
deliverRecipients: true,
|
|
14108
14664
|
kind: "transition",
|
|
14109
|
-
deliveryClass: "
|
|
14665
|
+
deliveryClass: "rotation",
|
|
14110
14666
|
ownDeliveryCapability: "",
|
|
14111
14667
|
notify: true,
|
|
14112
14668
|
tsMs
|
|
@@ -14118,14 +14674,18 @@ async function makeTransitionWakes(identity, parentState, entry, messageId, tsMs
|
|
|
14118
14674
|
}
|
|
14119
14675
|
async function deliverMembershipRecord(cloud, delivery) {
|
|
14120
14676
|
if (delivery.mode === "established") {
|
|
14677
|
+
if (!/^[0-9a-f]{64}$/u.test(delivery.attentionProof || "")) {
|
|
14678
|
+
throw new Error("chat membership attention proof required");
|
|
14679
|
+
}
|
|
14121
14680
|
await cloud.inbox.deliver(delivery.capability, delivery.ping, {
|
|
14681
|
+
attentionProof: delivery.attentionProof,
|
|
14122
14682
|
descriptor: delivery.descriptor,
|
|
14123
|
-
routeTag: delivery.routeTag
|
|
14124
|
-
notify: true
|
|
14683
|
+
routeTag: delivery.routeTag
|
|
14125
14684
|
});
|
|
14126
14685
|
return;
|
|
14127
14686
|
}
|
|
14128
14687
|
await pushCriticalInbox(cloud, delivery.recipientUid || delivery.member?.uid, delivery.ping, {
|
|
14688
|
+
admissionCapability: delivery.admissionCapability || null,
|
|
14129
14689
|
deliveryId: delivery.deliveryId,
|
|
14130
14690
|
descriptor: delivery.descriptor,
|
|
14131
14691
|
routeTag: delivery.routeTag,
|
|
@@ -14155,6 +14715,46 @@ async function deliverQueuedMembership(cloud, identity, deliveries) {
|
|
|
14155
14715
|
deliveredMembers: delivered.map((delivery) => delivery.member).filter(Boolean)
|
|
14156
14716
|
};
|
|
14157
14717
|
}
|
|
14718
|
+
async function recoverMembershipAttentionSecret(cloud, identity, record, mlsPackage) {
|
|
14719
|
+
const parentEpochVersion = Number(mlsPackage?.head?.epochVersion) - 1;
|
|
14720
|
+
if (!Number.isSafeInteger(parentEpochVersion) || parentEpochVersion < 1) {
|
|
14721
|
+
throw new Error("chat membership parent epoch required");
|
|
14722
|
+
}
|
|
14723
|
+
const entryId = ownChatEntryId(identity.chatPrivateKey, record.chatId);
|
|
14724
|
+
let afterEpoch = null;
|
|
14725
|
+
while (true) {
|
|
14726
|
+
const page = await cloud.user.chats.epochs.list(identity.uid, entryId, {
|
|
14727
|
+
count: 50,
|
|
14728
|
+
...afterEpoch ? { afterEpoch } : {}
|
|
14729
|
+
});
|
|
14730
|
+
for (const epochRecord of page?.records || []) {
|
|
14731
|
+
const epoch = await openOwnerEpochRecord(identity.chatPrivateKey, entryId, epochRecord.id, epochRecord.body).catch(() => null);
|
|
14732
|
+
if (epoch?.epochVersion === parentEpochVersion && epoch.successorTransitionCommitment === mlsPackage.head.transitionCommitment) {
|
|
14733
|
+
return epoch.epochSecret;
|
|
14734
|
+
}
|
|
14735
|
+
}
|
|
14736
|
+
afterEpoch = page?.nextAfterEpoch ?? null;
|
|
14737
|
+
if (!page?.hasMore || !afterEpoch)
|
|
14738
|
+
break;
|
|
14739
|
+
}
|
|
14740
|
+
throw new Error("chat membership parent attention secret unavailable");
|
|
14741
|
+
}
|
|
14742
|
+
async function recoverMembershipAttentionProofs(cloud, identity, record, mlsPackage) {
|
|
14743
|
+
if (!record.deliveries.some((delivery) => delivery.mode === "established")) {
|
|
14744
|
+
return record.deliveries;
|
|
14745
|
+
}
|
|
14746
|
+
const attentionSecret = await recoverMembershipAttentionSecret(cloud, identity, record, mlsPackage);
|
|
14747
|
+
const generation = mlsPackage.head.epochVersion - 1;
|
|
14748
|
+
return record.deliveries.map((delivery) => delivery.mode === "established" ? {
|
|
14749
|
+
...delivery,
|
|
14750
|
+
attentionProof: deriveChatAttentionCapability(attentionSecret, {
|
|
14751
|
+
chatId: record.chatId,
|
|
14752
|
+
recipientChatPK: delivery.recipientChatPK,
|
|
14753
|
+
generation,
|
|
14754
|
+
kind: CHAT_ATTENTION_KINDS.ROTATION
|
|
14755
|
+
})
|
|
14756
|
+
} : delivery);
|
|
14757
|
+
}
|
|
14158
14758
|
async function drainChatMembershipOutbox(cloud, identity, options = {}) {
|
|
14159
14759
|
const records = await cloud.user.chats.membershipOutbox.list(identity.uid, { count: options.count || 20 });
|
|
14160
14760
|
const committedPackages = new Map;
|
|
@@ -14167,13 +14767,15 @@ async function drainChatMembershipOutbox(cloud, identity, options = {}) {
|
|
|
14167
14767
|
}
|
|
14168
14768
|
const packageKey = `${opened.chatId}:${opened.packageId}:${opened.packageDigest}`;
|
|
14169
14769
|
if (!committedPackages.has(packageKey)) {
|
|
14170
|
-
const
|
|
14171
|
-
committedPackages.set(packageKey,
|
|
14770
|
+
const mlsPackage2 = await cloud.chat.mls.packages.read(opened.chatId, opened.packageId).catch(() => null);
|
|
14771
|
+
committedPackages.set(packageKey, mlsPackage2?.digest === opened.packageDigest ? mlsPackage2 : null);
|
|
14172
14772
|
}
|
|
14173
|
-
|
|
14773
|
+
const mlsPackage = committedPackages.get(packageKey);
|
|
14774
|
+
if (!mlsPackage)
|
|
14174
14775
|
continue;
|
|
14175
14776
|
try {
|
|
14176
|
-
const
|
|
14777
|
+
const deliveries = await recoverMembershipAttentionProofs(cloud, identity, opened, mlsPackage);
|
|
14778
|
+
const results = await Promise.allSettled(deliveries.map((delivery) => deliverMembershipRecord(cloud, delivery)));
|
|
14177
14779
|
delivered += results.filter((result) => result.status === "fulfilled").length;
|
|
14178
14780
|
if (results.every((result) => result.status === "fulfilled")) {
|
|
14179
14781
|
await cloud.user.chats.membershipOutbox.delete(identity.uid, record.id);
|
|
@@ -14305,13 +14907,6 @@ async function prepareMembershipTransition(context) {
|
|
|
14305
14907
|
});
|
|
14306
14908
|
if (transitionMessage.msgId !== transitionMessageId)
|
|
14307
14909
|
throw new Error("chat transition message id mismatch");
|
|
14308
|
-
runtime.ownDeliveryCapability = deriveChatDeliveryCapability(runtime.stateCapability, {
|
|
14309
|
-
chatId: parent.chatId,
|
|
14310
|
-
recipientChatPK: identity.chatPK,
|
|
14311
|
-
generation: next.epochVersion
|
|
14312
|
-
});
|
|
14313
|
-
runtime.stage = "register-delivery";
|
|
14314
|
-
await cloud.delivery.register(runtime.ownDeliveryCapability);
|
|
14315
14910
|
runtime.stage = "prepare-deliveries";
|
|
14316
14911
|
const [wakeDeliveries, welcomeDeliveries] = await Promise.all([
|
|
14317
14912
|
makeTransitionWakes(identity, parentState, entry, transitionMessage.msgId, transitionMessage.tsMs),
|
|
@@ -14338,16 +14933,26 @@ async function prepareMembershipTransition(context) {
|
|
|
14338
14933
|
if (current.current.manifest.epochId !== parent.epochId || current.current.manifest.epochVersion !== parent.epochVersion) {
|
|
14339
14934
|
throw new Error("chat owner epoch changed");
|
|
14340
14935
|
}
|
|
14936
|
+
const notificationMode = normalizeChatNotificationMode(current.notificationMode, next);
|
|
14341
14937
|
const nextEntry = makeOwnChatEntry(candidateState, {
|
|
14342
14938
|
ownerRevision: current.ownerRevision,
|
|
14343
|
-
routes: nextRoutes(current, next, runtime.stateCapability),
|
|
14939
|
+
routes: nextRoutes(current, next, runtime.stateCapability, delta.members),
|
|
14344
14940
|
saved: current.saved,
|
|
14345
14941
|
startMs: current.startMs,
|
|
14346
14942
|
deliveryRegistered: true,
|
|
14943
|
+
notificationMode,
|
|
14347
14944
|
notificationTag: notificationChatTag(runtime.stateCapability, parent.chatId, identity.chatPK)
|
|
14348
14945
|
});
|
|
14349
14946
|
return {
|
|
14350
14947
|
entry: nextEntry,
|
|
14948
|
+
deliveryRegistration: chatDeliveryRegistration(runtime.stateCapability, {
|
|
14949
|
+
attentionSecret: runtime.epochSecret,
|
|
14950
|
+
chatId: parent.chatId,
|
|
14951
|
+
recipientChatPK: identity.chatPK,
|
|
14952
|
+
generation: next.epochVersion,
|
|
14953
|
+
manifest: next,
|
|
14954
|
+
notificationMode
|
|
14955
|
+
}),
|
|
14351
14956
|
...Number.isFinite(fields.ownerTsMs) ? { tsMs: fields.ownerTsMs } : { touchTs: true },
|
|
14352
14957
|
epochs: [{ id: historyId, record: historyRecord }],
|
|
14353
14958
|
mlsWrites: [{ id: ownerMlsState.stateId, value: ownerMlsState.value }],
|
|
@@ -14490,7 +15095,6 @@ async function transitionChatEpoch(cloud, identityValue, entry, fields = {}) {
|
|
|
14490
15095
|
epochSecret: null,
|
|
14491
15096
|
metrics: {},
|
|
14492
15097
|
nextSnapshot: null,
|
|
14493
|
-
ownDeliveryCapability: "",
|
|
14494
15098
|
parentSnapshot: null,
|
|
14495
15099
|
stage: "read-owner-mls",
|
|
14496
15100
|
stateCapability: null
|
|
@@ -14518,9 +15122,6 @@ async function transitionChatEpoch(cloud, identityValue, entry, fields = {}) {
|
|
|
14518
15122
|
});
|
|
14519
15123
|
throw error;
|
|
14520
15124
|
} finally {
|
|
14521
|
-
if (runtime.ownDeliveryCapability && !runtime.committed) {
|
|
14522
|
-
await cloud.delivery.revoke(runtime.ownDeliveryCapability).catch(() => false);
|
|
14523
|
-
}
|
|
14524
15125
|
cleanBytes(runtime.parentSnapshot, runtime.nextSnapshot, runtime.epochSecret, runtime.stateCapability);
|
|
14525
15126
|
}
|
|
14526
15127
|
}
|
|
@@ -14913,7 +15514,7 @@ function sortedUniqueValues(items) {
|
|
|
14913
15514
|
|
|
14914
15515
|
// ../../core/chat/chats.js
|
|
14915
15516
|
"use client";
|
|
14916
|
-
var
|
|
15517
|
+
var HEX_32_RE4 = /^[0-9a-f]{32}$/i;
|
|
14917
15518
|
var HEX_64_RE = /^[0-9a-f]{64}$/i;
|
|
14918
15519
|
function sameSigningKeys(left, right) {
|
|
14919
15520
|
if (left === right)
|
|
@@ -14937,7 +15538,7 @@ function sameMembers(left, right) {
|
|
|
14937
15538
|
function sameChatShape(a, b) {
|
|
14938
15539
|
if (!a || !b)
|
|
14939
15540
|
return a === b;
|
|
14940
|
-
return a.id === b.id && a.protocol === b.protocol && a.entryId === b.entryId && a.epochId === b.epochId && a.epochVersion === b.epochVersion && a.lineage === b.lineage && a.memberCount === b.memberCount && sameMembers(a.members, b.members) && a.peerChatPK === b.peerChatPK && a.peerUid === b.peerUid && sameSigningKeys(a.signingKeysByChatKey, b.signingKeysByChatKey) && a.signerShared === b.signerShared && a.deliveryRegistered === b.deliveryRegistered && a.peerDeliveryCapability === b.peerDeliveryCapability && a.peerNotificationPK === b.peerNotificationPK && a.notificationTag === b.notificationTag && a.ts === b.ts && a.startMs === b.startMs && a.readMs === b.readMs && a.inboxMessageId === b.inboxMessageId && a.inboxMessageAt === b.inboxMessageAt && a.inboxMessage === b.inboxMessage && a.unseen === b.unseen && a.settings?.retention === b.settings?.retention && a.settings?.title === b.settings?.title && a.settings?.avatarRef === b.settings?.avatarRef && a.membershipRemoved === true === (b.membershipRemoved === true) && sameChatPreview(a.preview, b.preview);
|
|
15541
|
+
return a.id === b.id && a.protocol === b.protocol && a.entryId === b.entryId && a.epochId === b.epochId && a.epochVersion === b.epochVersion && a.lineage === b.lineage && a.memberCount === b.memberCount && sameMembers(a.members, b.members) && a.peerChatPK === b.peerChatPK && a.peerUid === b.peerUid && sameSigningKeys(a.signingKeysByChatKey, b.signingKeysByChatKey) && a.signerShared === b.signerShared && a.deliveryRegistered === b.deliveryRegistered && a.attentionRegistrationVersion === b.attentionRegistrationVersion && a.notificationMode === b.notificationMode && a.peerDeliveryCapability === b.peerDeliveryCapability && a.peerNotificationPK === b.peerNotificationPK && a.notificationTag === b.notificationTag && a.ts === b.ts && a.startMs === b.startMs && a.readMs === b.readMs && a.inboxMessageId === b.inboxMessageId && a.inboxMessageAt === b.inboxMessageAt && a.inboxMessage === b.inboxMessage && a.unseen === b.unseen && a.settings?.retention === b.settings?.retention && a.settings?.title === b.settings?.title && a.settings?.avatarRef === b.settings?.avatarRef && a.membershipRemoved === true === (b.membershipRemoved === true) && sameChatPreview(a.preview, b.preview);
|
|
14941
15542
|
}
|
|
14942
15543
|
function sameChats(prev, next) {
|
|
14943
15544
|
if (prev.length !== next.length)
|
|
@@ -15011,7 +15612,7 @@ function chatVersionKey(chat) {
|
|
|
15011
15612
|
return "";
|
|
15012
15613
|
}
|
|
15013
15614
|
function isHex32(value) {
|
|
15014
|
-
return
|
|
15615
|
+
return HEX_32_RE4.test(cleanText(value));
|
|
15015
15616
|
}
|
|
15016
15617
|
function isHex64(value) {
|
|
15017
15618
|
return HEX_64_RE.test(cleanText(value));
|
|
@@ -16067,7 +16668,9 @@ function serializeChat(chat) {
|
|
|
16067
16668
|
ts: timestampMs(chat.ts, null, { positive: true }) || 0,
|
|
16068
16669
|
lastUsedAt: Date.now(),
|
|
16069
16670
|
unseen: !!chat.unseen,
|
|
16070
|
-
membershipRemoved: chat.membershipRemoved === true
|
|
16671
|
+
membershipRemoved: chat.membershipRemoved === true,
|
|
16672
|
+
attentionRegistrationVersion: Number(chat.attentionRegistrationVersion) || 0,
|
|
16673
|
+
notificationMode: chat.notificationMode || null
|
|
16071
16674
|
};
|
|
16072
16675
|
}
|
|
16073
16676
|
function reviveChat(chat) {
|
|
@@ -16098,7 +16701,9 @@ function reviveChat(chat) {
|
|
|
16098
16701
|
ts: timestampMs(chat.ts, null, { positive: true }) || 0,
|
|
16099
16702
|
lastUsedAt: timestampMs(chat.lastUsedAt, null, { positive: true }) || 0,
|
|
16100
16703
|
unseen: !!chat.unseen,
|
|
16101
|
-
membershipRemoved: chat.membershipRemoved === true
|
|
16704
|
+
membershipRemoved: chat.membershipRemoved === true,
|
|
16705
|
+
attentionRegistrationVersion: Number(chat.attentionRegistrationVersion) || 0,
|
|
16706
|
+
notificationMode: chat.notificationMode || null
|
|
16102
16707
|
};
|
|
16103
16708
|
}
|
|
16104
16709
|
function isReadableCachedChat(chat) {
|
|
@@ -17501,7 +18106,7 @@ function createChatLive({
|
|
|
17501
18106
|
return sendSnapshot(entry);
|
|
17502
18107
|
}
|
|
17503
18108
|
if (!nextCompositionId) {
|
|
17504
|
-
if (!entry.typing)
|
|
18109
|
+
if (!entry.typing && !entry.compositionId)
|
|
17505
18110
|
return false;
|
|
17506
18111
|
entry.typing = false;
|
|
17507
18112
|
entry.compositionId = "";
|
|
@@ -18104,7 +18709,10 @@ ${cid}` : "";
|
|
|
18104
18709
|
}
|
|
18105
18710
|
if (adopt && !adoptedLocalMediaRef.current.has(mediaKey)) {
|
|
18106
18711
|
adoptedLocalMediaRef.current.add(mediaKey);
|
|
18107
|
-
adoptLocalMessageMedia?.(message, local, {
|
|
18712
|
+
adoptLocalMessageMedia?.(message, local, {
|
|
18713
|
+
chatId,
|
|
18714
|
+
peerChatPK: local.peerChatPK
|
|
18715
|
+
});
|
|
18108
18716
|
}
|
|
18109
18717
|
}
|
|
18110
18718
|
if (changed) {
|
|
@@ -18325,6 +18933,9 @@ ${cid}` : "";
|
|
|
18325
18933
|
const sendOptions = mergeSendOptions(sendOptionsForPeer(peerChatPK), options);
|
|
18326
18934
|
const nextMessage = withMessageRetention(message, sendOptions.retention);
|
|
18327
18935
|
if (isLongTxt(nextMessage)) {
|
|
18936
|
+
if (hasChatMessageMentions(nextMessage)) {
|
|
18937
|
+
throw new Error("messages with mentions must fit in a text message");
|
|
18938
|
+
}
|
|
18328
18939
|
const cid = makeSendCid(nextMessage);
|
|
18329
18940
|
const attachment = makeTxtFileAttachment(nextMessage);
|
|
18330
18941
|
const localMessage = makeLongTxtLocalMessage(chatPK, cid, attachment, nextMessage);
|
|
@@ -19384,9 +19995,12 @@ function normalizeDecryptedMsg(msgData, message, epoch, readContext) {
|
|
|
19384
19995
|
if (!message || typeof message !== "object")
|
|
19385
19996
|
return null;
|
|
19386
19997
|
const epochSigningKeysVersion = signingKeysFingerprint(readContext?.signingKeysByChatKey);
|
|
19998
|
+
const content = normalizeChatMessageMentions(message, epoch?.manifest, {
|
|
19999
|
+
senderChatPK: message.s || message.from
|
|
20000
|
+
});
|
|
19387
20001
|
const normalized = {
|
|
19388
|
-
...
|
|
19389
|
-
id:
|
|
20002
|
+
...content,
|
|
20003
|
+
id: content.id || msgData?.id || null,
|
|
19390
20004
|
ts: msgData?.ts ?? null,
|
|
19391
20005
|
ttl: msgData?.ttl ?? null,
|
|
19392
20006
|
...decryptedEpochFields(epoch),
|
|
@@ -20193,11 +20807,171 @@ async function readMsg({ cloud, messageId, options = {} }) {
|
|
|
20193
20807
|
deletedKeys: []
|
|
20194
20808
|
};
|
|
20195
20809
|
}
|
|
20810
|
+
// ../../core/account/scope.js
|
|
20811
|
+
var ACCOUNT_SCOPE_STALE_CODE = "account-scope/stale";
|
|
20812
|
+
var PROFILE_ID_RE = /^[0-9a-f]{32}$/;
|
|
20813
|
+
var ACCOUNT_SCOPE_CAPABILITIES = new WeakSet;
|
|
20814
|
+
function requireGeneration(value) {
|
|
20815
|
+
const generation = Number(value);
|
|
20816
|
+
if (!Number.isSafeInteger(generation) || generation < 0) {
|
|
20817
|
+
throw new Error("account scope generation required");
|
|
20818
|
+
}
|
|
20819
|
+
return generation;
|
|
20820
|
+
}
|
|
20821
|
+
function requireProfileId(value) {
|
|
20822
|
+
const profileId = lowerText(value);
|
|
20823
|
+
if (!PROFILE_ID_RE.test(profileId))
|
|
20824
|
+
throw new Error("account scope profile id required");
|
|
20825
|
+
return profileId;
|
|
20826
|
+
}
|
|
20827
|
+
function requireText(value, label) {
|
|
20828
|
+
const text = cleanText(value);
|
|
20829
|
+
if (!text)
|
|
20830
|
+
throw new Error(`${label} required`);
|
|
20831
|
+
return text;
|
|
20832
|
+
}
|
|
20833
|
+
function staleAccountScopeError(scope = null) {
|
|
20834
|
+
const error = new Error("account scope is no longer active");
|
|
20835
|
+
error.code = ACCOUNT_SCOPE_STALE_CODE;
|
|
20836
|
+
error.profileId = scope?.profileId || null;
|
|
20837
|
+
error.generation = Number.isSafeInteger(scope?.generation) ? scope.generation : null;
|
|
20838
|
+
return error;
|
|
20839
|
+
}
|
|
20840
|
+
function createAccountScope({
|
|
20841
|
+
environment,
|
|
20842
|
+
generation,
|
|
20843
|
+
isCurrent,
|
|
20844
|
+
profileId,
|
|
20845
|
+
uid
|
|
20846
|
+
} = {}) {
|
|
20847
|
+
if (typeof isCurrent !== "function")
|
|
20848
|
+
throw new Error("account scope current-state port required");
|
|
20849
|
+
const normalizedEnvironment = lowerText(requireText(environment, "account scope environment"));
|
|
20850
|
+
const normalizedGeneration = requireGeneration(generation);
|
|
20851
|
+
const normalizedProfileId = requireProfileId(profileId);
|
|
20852
|
+
const normalizedUid = requireText(uid, "account scope uid");
|
|
20853
|
+
const accountOwnerKey = `${normalizedEnvironment}:${normalizedProfileId}:${normalizedUid}`;
|
|
20854
|
+
const scope = {
|
|
20855
|
+
accountOwnerKey,
|
|
20856
|
+
environment: normalizedEnvironment,
|
|
20857
|
+
generation: normalizedGeneration,
|
|
20858
|
+
key: `${accountOwnerKey}:${normalizedGeneration}`,
|
|
20859
|
+
profileId: normalizedProfileId,
|
|
20860
|
+
uid: normalizedUid,
|
|
20861
|
+
isCurrent: () => isCurrent() === true,
|
|
20862
|
+
assertCurrent() {
|
|
20863
|
+
if (!scope.isCurrent())
|
|
20864
|
+
throw staleAccountScopeError(scope);
|
|
20865
|
+
return scope;
|
|
20866
|
+
}
|
|
20867
|
+
};
|
|
20868
|
+
ACCOUNT_SCOPE_CAPABILITIES.add(scope);
|
|
20869
|
+
return Object.freeze(scope);
|
|
20870
|
+
}
|
|
20871
|
+
function isAccountScope(value) {
|
|
20872
|
+
return !!value && typeof value === "object" && ACCOUNT_SCOPE_CAPABILITIES.has(value);
|
|
20873
|
+
}
|
|
20874
|
+
|
|
20875
|
+
// ../../core/chat/messages/maintenance.js
|
|
20876
|
+
var STALE_MAINTENANCE_CODE = "chat-maintenance/stale";
|
|
20877
|
+
function staleError() {
|
|
20878
|
+
const error = new Error("chat maintenance owner is no longer current");
|
|
20879
|
+
error.code = STALE_MAINTENANCE_CODE;
|
|
20880
|
+
return error;
|
|
20881
|
+
}
|
|
20882
|
+
function cleanKey(value) {
|
|
20883
|
+
return typeof value === "string" ? value.trim() : "";
|
|
20884
|
+
}
|
|
20885
|
+
function isStaleMessageMaintenance(error) {
|
|
20886
|
+
return error?.code === STALE_MAINTENANCE_CODE;
|
|
20887
|
+
}
|
|
20888
|
+
function createAccountMessageMaintenance({ isAccountCurrent = null } = {}) {
|
|
20889
|
+
const claims = new Set;
|
|
20890
|
+
const tasks = new Set;
|
|
20891
|
+
let generation = 0;
|
|
20892
|
+
let open = true;
|
|
20893
|
+
let scope = null;
|
|
20894
|
+
const accountCurrent = () => scope?.isCurrent?.() === true || !scope && (typeof isAccountCurrent !== "function" || isAccountCurrent() === true);
|
|
20895
|
+
const isCurrent = (expectedGeneration = generation) => open && expectedGeneration === generation && accountCurrent();
|
|
20896
|
+
const assertCurrent = (expectedGeneration = generation) => {
|
|
20897
|
+
if (!isCurrent(expectedGeneration))
|
|
20898
|
+
throw staleError();
|
|
20899
|
+
return true;
|
|
20900
|
+
};
|
|
20901
|
+
const owner = {
|
|
20902
|
+
assertCurrent,
|
|
20903
|
+
isCurrent,
|
|
20904
|
+
bindScope(nextScope) {
|
|
20905
|
+
if (!isAccountScope(nextScope)) {
|
|
20906
|
+
throw new Error("account message maintenance scope required");
|
|
20907
|
+
}
|
|
20908
|
+
if (scope && scope !== nextScope) {
|
|
20909
|
+
throw new Error("account message maintenance scope already bound");
|
|
20910
|
+
}
|
|
20911
|
+
scope = nextScope;
|
|
20912
|
+
return scope;
|
|
20913
|
+
},
|
|
20914
|
+
open() {
|
|
20915
|
+
if (open)
|
|
20916
|
+
return generation;
|
|
20917
|
+
generation += 1;
|
|
20918
|
+
open = true;
|
|
20919
|
+
claims.clear();
|
|
20920
|
+
return generation;
|
|
20921
|
+
},
|
|
20922
|
+
claim(kind, value) {
|
|
20923
|
+
const type = cleanKey(kind);
|
|
20924
|
+
const key = cleanKey(value);
|
|
20925
|
+
if (!type || !key)
|
|
20926
|
+
throw new Error("chat maintenance claim key required");
|
|
20927
|
+
assertCurrent();
|
|
20928
|
+
const claimKey = `${type}:${key}`;
|
|
20929
|
+
if (claims.has(claimKey))
|
|
20930
|
+
return null;
|
|
20931
|
+
const claimGeneration = generation;
|
|
20932
|
+
claims.add(claimKey);
|
|
20933
|
+
let released = false;
|
|
20934
|
+
return Object.freeze({
|
|
20935
|
+
assertCurrent: () => {
|
|
20936
|
+
if (released || !claims.has(claimKey))
|
|
20937
|
+
throw staleError();
|
|
20938
|
+
return assertCurrent(claimGeneration);
|
|
20939
|
+
},
|
|
20940
|
+
isCurrent: () => !released && claims.has(claimKey) && isCurrent(claimGeneration),
|
|
20941
|
+
release() {
|
|
20942
|
+
if (released)
|
|
20943
|
+
return false;
|
|
20944
|
+
released = true;
|
|
20945
|
+
claims.delete(claimKey);
|
|
20946
|
+
return true;
|
|
20947
|
+
}
|
|
20948
|
+
});
|
|
20949
|
+
},
|
|
20950
|
+
track(value) {
|
|
20951
|
+
const taskGeneration = generation;
|
|
20952
|
+
assertCurrent(taskGeneration);
|
|
20953
|
+
const task = Promise.resolve(value).finally(() => tasks.delete(task));
|
|
20954
|
+
tasks.add(task);
|
|
20955
|
+
return task;
|
|
20956
|
+
},
|
|
20957
|
+
close() {
|
|
20958
|
+
if (open) {
|
|
20959
|
+
open = false;
|
|
20960
|
+
generation += 1;
|
|
20961
|
+
claims.clear();
|
|
20962
|
+
}
|
|
20963
|
+
return Promise.allSettled([...tasks]);
|
|
20964
|
+
},
|
|
20965
|
+
drain: () => Promise.allSettled([...tasks])
|
|
20966
|
+
};
|
|
20967
|
+
return Object.freeze(owner);
|
|
20968
|
+
}
|
|
20969
|
+
|
|
20196
20970
|
// ../../core/chat/messages/batches/cleanup.js
|
|
20197
20971
|
function isDenied(error) {
|
|
20198
20972
|
return error?.code === "permission-denied";
|
|
20199
20973
|
}
|
|
20200
|
-
function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, chatPK, localCache, deleteMessageDocs, diag, getCurrentEntry, notify }) {
|
|
20974
|
+
function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, chatPK, localCache, maintenance, deleteMessageDocs, diag, getCurrentEntry, notify }) {
|
|
20201
20975
|
if (chatBanned || !isActive || !ownsMessageCompaction(entry?.memberChatPKs, chatPK) || !entry?.chatId || !entry.peerChatPK || !entry.ready || entry.exists === false || !entry.messages?.length || typeof deleteMessageDocs !== "function") {
|
|
20202
20976
|
return null;
|
|
20203
20977
|
}
|
|
@@ -20209,18 +20983,25 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20209
20983
|
const peerChatPK = entry.peerChatPK;
|
|
20210
20984
|
const generation = entry.generation;
|
|
20211
20985
|
const scheduledAt = Date.now();
|
|
20986
|
+
if (!maintenance?.claim)
|
|
20987
|
+
throw new Error("account message maintenance required");
|
|
20988
|
+
const lease = maintenance.claim("batch-cleanup", `${chatId}:${generation}`);
|
|
20989
|
+
if (!lease)
|
|
20990
|
+
return null;
|
|
20212
20991
|
markDiag(diag, "chat.message.cleanup.schedule", {
|
|
20213
20992
|
chatId,
|
|
20214
20993
|
compactControls,
|
|
20215
20994
|
idle: options.idle !== false,
|
|
20216
20995
|
messages: entry.messages?.length || 0
|
|
20217
20996
|
});
|
|
20218
|
-
|
|
20997
|
+
const operation = Promise.resolve().then(async () => {
|
|
20998
|
+
lease.assertCurrent();
|
|
20219
20999
|
if (options.idle !== false) {
|
|
20220
21000
|
await waitForIdle({
|
|
20221
21001
|
timeout: CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS,
|
|
20222
21002
|
delay: CHAT_BATCH_CLEANUP_IDLE_DELAY_MS
|
|
20223
21003
|
});
|
|
21004
|
+
lease.assertCurrent();
|
|
20224
21005
|
}
|
|
20225
21006
|
let current = getCurrentEntry?.(chatId);
|
|
20226
21007
|
if (current !== entry || current.generation !== generation || current.route) {
|
|
@@ -20243,8 +21024,10 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20243
21024
|
return;
|
|
20244
21025
|
}
|
|
20245
21026
|
if (compactControls) {
|
|
21027
|
+
lease.assertCurrent();
|
|
20246
21028
|
const compacted = await compactMessages({
|
|
20247
21029
|
chatId,
|
|
21030
|
+
maintenance,
|
|
20248
21031
|
messages: current.messages.slice(),
|
|
20249
21032
|
deletedKeys,
|
|
20250
21033
|
protectedKeys: compactProtectedKeys,
|
|
@@ -20255,6 +21038,7 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20255
21038
|
if (compacted.length) {
|
|
20256
21039
|
dropped.push(...compacted);
|
|
20257
21040
|
}
|
|
21041
|
+
lease.assertCurrent();
|
|
20258
21042
|
}
|
|
20259
21043
|
if (dropped.length) {
|
|
20260
21044
|
const cacheStartedAt = Date.now();
|
|
@@ -20267,7 +21051,12 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20267
21051
|
startMs: entry.startMs,
|
|
20268
21052
|
messages: dropped,
|
|
20269
21053
|
mode: "confirmed"
|
|
21054
|
+
}).then((result) => {
|
|
21055
|
+
lease.assertCurrent();
|
|
21056
|
+
return result;
|
|
20270
21057
|
}).catch((error) => {
|
|
21058
|
+
if (isStaleMessageMaintenance(error))
|
|
21059
|
+
throw error;
|
|
20271
21060
|
markError(diag, "chat.message.cleanup.cache", cacheStartedAt, error, {
|
|
20272
21061
|
droppedCount: dropped.length
|
|
20273
21062
|
});
|
|
@@ -20289,14 +21078,16 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20289
21078
|
if (current !== entry || current.generation !== generation || current.route || !dropped.length) {
|
|
20290
21079
|
return;
|
|
20291
21080
|
}
|
|
21081
|
+
lease.assertCurrent();
|
|
20292
21082
|
notify(current);
|
|
20293
21083
|
}).catch((error) => {
|
|
20294
|
-
if (!isDenied(error)) {
|
|
21084
|
+
if (!isDenied(error) && !isStaleMessageMaintenance(error)) {
|
|
20295
21085
|
markError(diag, "chat.message.cleanup", Date.now(), error, {
|
|
20296
21086
|
compactControls
|
|
20297
21087
|
});
|
|
20298
21088
|
}
|
|
20299
|
-
});
|
|
21089
|
+
}).finally(() => lease.release());
|
|
21090
|
+
return maintenance.track(operation);
|
|
20300
21091
|
}
|
|
20301
21092
|
|
|
20302
21093
|
// ../../core/chat/messages/epochhistory.js
|
|
@@ -20862,6 +21653,7 @@ function runBatchCleanup(owner, entry, options = {}) {
|
|
|
20862
21653
|
isActive: sources.isActive,
|
|
20863
21654
|
chatPK: sources.chatPK,
|
|
20864
21655
|
localCache: sources.localCache,
|
|
21656
|
+
maintenance: sources.maintenance,
|
|
20865
21657
|
deleteMessageDocs: sources.deleteMessageDocs,
|
|
20866
21658
|
diag: sources.diag,
|
|
20867
21659
|
getCurrentEntry: (chatId) => owner.batches.get(chatId),
|
|
@@ -21821,6 +22613,9 @@ function messageBatchSources(input = {}) {
|
|
|
21821
22613
|
}
|
|
21822
22614
|
function createChatMessageBatches(input) {
|
|
21823
22615
|
let sources = messageBatchSources(input);
|
|
22616
|
+
if (!sources.maintenance?.claim) {
|
|
22617
|
+
throw new Error("message batches require account maintenance");
|
|
22618
|
+
}
|
|
21824
22619
|
let warming = null;
|
|
21825
22620
|
const routeMemory = createRouteMemory(MESSAGE_VIEW_CACHE_SIZE);
|
|
21826
22621
|
const collection = createMessageBatchCollection({
|
|
@@ -21841,7 +22636,11 @@ function createChatMessageBatches(input) {
|
|
|
21841
22636
|
collection.clear();
|
|
21842
22637
|
};
|
|
21843
22638
|
const setSources = (next) => {
|
|
21844
|
-
|
|
22639
|
+
const nextSources = messageBatchSources(next);
|
|
22640
|
+
if (nextSources.maintenance !== sources.maintenance) {
|
|
22641
|
+
throw new Error("message batch maintenance owner cannot change");
|
|
22642
|
+
}
|
|
22643
|
+
sources = nextSources;
|
|
21845
22644
|
warming.sync();
|
|
21846
22645
|
};
|
|
21847
22646
|
return Object.freeze({
|
|
@@ -22128,7 +22927,7 @@ function epochMessageReadContext(epochState) {
|
|
|
22128
22927
|
async function readEntry(cloud, uid, chatPrivateKey, chatId) {
|
|
22129
22928
|
const entryId = ownChatEntryId(chatPrivateKey, chatId);
|
|
22130
22929
|
const record = await cloud.user.chats.read(uid, entryId);
|
|
22131
|
-
const entry = record ? await
|
|
22930
|
+
const entry = record ? await openOwnChatRecord(chatPrivateKey, entryId, record).catch(() => null) : null;
|
|
22132
22931
|
if (entry && entry.ownerRevision !== record?.revision)
|
|
22133
22932
|
throw new Error("chat owner revision mismatch");
|
|
22134
22933
|
return { entryId, entry };
|
|
@@ -22168,6 +22967,50 @@ function cachedInboxRead(cache, key, read) {
|
|
|
22168
22967
|
function hasBlockedMembers(manifest, blockedUids) {
|
|
22169
22968
|
return blockedUids instanceof Set && blockedUids.size > 0 && manifest.members.some((member) => blockedUids.has(member.uid));
|
|
22170
22969
|
}
|
|
22970
|
+
function publicIncomingChatMember(member) {
|
|
22971
|
+
return Object.freeze({
|
|
22972
|
+
uid: cleanText(member?.uid),
|
|
22973
|
+
chatPK: cleanText(member?.chatPK),
|
|
22974
|
+
chatSigningPK: cleanText(member?.chatSigningPK),
|
|
22975
|
+
notificationPK: cleanText(member?.notificationPK)
|
|
22976
|
+
});
|
|
22977
|
+
}
|
|
22978
|
+
function incomingChatAdmissionContext(context, epochState) {
|
|
22979
|
+
const sender = context.senderProfile || {};
|
|
22980
|
+
return Object.freeze({
|
|
22981
|
+
sender: Object.freeze({
|
|
22982
|
+
uid: cleanText(sender.uid),
|
|
22983
|
+
username: cleanText(sender.username),
|
|
22984
|
+
chatPK: cleanText(sender.chatPK),
|
|
22985
|
+
chatSigningPK: cleanText(sender.chatSigningPK),
|
|
22986
|
+
notificationPK: cleanText(sender.notificationPK)
|
|
22987
|
+
}),
|
|
22988
|
+
chat: Object.freeze({
|
|
22989
|
+
id: cleanText(epochState?.manifest?.chatId),
|
|
22990
|
+
lineage: cleanText(epochState?.manifest?.lineage),
|
|
22991
|
+
members: Object.freeze((epochState?.manifest?.members || []).map(publicIncomingChatMember))
|
|
22992
|
+
}),
|
|
22993
|
+
self: Object.freeze({
|
|
22994
|
+
uid: cleanText(context.identity?.uid),
|
|
22995
|
+
chatPK: cleanText(context.identity?.chatPK)
|
|
22996
|
+
})
|
|
22997
|
+
});
|
|
22998
|
+
}
|
|
22999
|
+
async function decideIncomingChat(context, epochState) {
|
|
23000
|
+
const resolution = context.options.chatRequestResolutions?.get?.(context.options.requestToken);
|
|
23001
|
+
if (resolution?.decision)
|
|
23002
|
+
return normalizeChatAdmissionDecision(resolution.decision);
|
|
23003
|
+
const policyDecision = incomingChatAdmissionDecision(context.identity.chatPrivateKey, context.identity.chatPK, context.senderProfile, context.options.chatAdmission, epochState?.manifest?.lineage);
|
|
23004
|
+
if (typeof context.options.incomingChatDecision !== "function")
|
|
23005
|
+
return policyDecision;
|
|
23006
|
+
try {
|
|
23007
|
+
const harnessDecision = normalizeChatAdmissionDecision(await context.options.incomingChatDecision(incomingChatAdmissionContext(context, epochState)));
|
|
23008
|
+
return strictestChatAdmissionDecision(policyDecision, harnessDecision);
|
|
23009
|
+
} catch {
|
|
23010
|
+
markDiag(context.options.diag, "chat.inbox.admission.failed", {});
|
|
23011
|
+
return CHAT_ADMISSION_DECISIONS.REJECT;
|
|
23012
|
+
}
|
|
23013
|
+
}
|
|
22171
23014
|
function inboxLeaseError() {
|
|
22172
23015
|
const error = new Error("chat inbox session changed");
|
|
22173
23016
|
error.code = "chat/session-stale";
|
|
@@ -22377,27 +23220,16 @@ function routesForManifest(entry, epochState) {
|
|
|
22377
23220
|
}];
|
|
22378
23221
|
}));
|
|
22379
23222
|
}
|
|
22380
|
-
function
|
|
22381
|
-
return
|
|
23223
|
+
function epochDeliveryRegistration(identity, epochState, notificationMode) {
|
|
23224
|
+
return chatDeliveryRegistration(epochState.stateCapability, {
|
|
23225
|
+
attentionSecret: epochState.epochSecret,
|
|
22382
23226
|
chatId: epochState.manifest.chatId,
|
|
22383
23227
|
recipientChatPK: identity.chatPK,
|
|
22384
|
-
generation: epochState.manifest.epochVersion
|
|
23228
|
+
generation: epochState.manifest.epochVersion,
|
|
23229
|
+
manifest: epochState.manifest,
|
|
23230
|
+
notificationMode
|
|
22385
23231
|
});
|
|
22386
23232
|
}
|
|
22387
|
-
async function registerEpochDelivery(cloud, identity, epochState, previousState, wasRegistered, options) {
|
|
22388
|
-
const capability = epochDeliveryCapability(identity, epochState);
|
|
22389
|
-
assertInboxLease(options);
|
|
22390
|
-
await cloud.delivery.register(capability);
|
|
22391
|
-
if (previousState && wasRegistered) {
|
|
22392
|
-
const oldCapability = deriveChatDeliveryCapability(previousState.stateCapability, {
|
|
22393
|
-
chatId: previousState.manifest.chatId,
|
|
22394
|
-
recipientChatPK: identity.chatPK,
|
|
22395
|
-
generation: previousState.manifest.epochVersion
|
|
22396
|
-
});
|
|
22397
|
-
await cloud.delivery.revoke(oldCapability).catch(() => false);
|
|
22398
|
-
}
|
|
22399
|
-
return true;
|
|
22400
|
-
}
|
|
22401
23233
|
async function readPingMessage(cloud, payload, epochState) {
|
|
22402
23234
|
const messageId = cleanText(payload.messageId);
|
|
22403
23235
|
if (!messageId)
|
|
@@ -22484,7 +23316,7 @@ function sameEntryEpoch(left, right) {
|
|
|
22484
23316
|
return left?.current?.manifest?.epochId === right?.current?.manifest?.epochId && left?.current?.settings?.digest === right?.current?.settings?.digest;
|
|
22485
23317
|
}
|
|
22486
23318
|
function sameEntryRegistration(left, right) {
|
|
22487
|
-
return left?.deliveryRegistered === right?.deliveryRegistered && left?.notificationTag === right?.notificationTag;
|
|
23319
|
+
return left?.deliveryRegistered === right?.deliveryRegistered && left?.attentionRegistrationVersion === right?.attentionRegistrationVersion && left?.notificationMode === right?.notificationMode && left?.notificationTag === right?.notificationTag;
|
|
22488
23320
|
}
|
|
22489
23321
|
function sameEntryRetirement(left, right) {
|
|
22490
23322
|
return left?.retirement?.kind === right?.retirement?.kind && left?.retirement?.epochVersion === right?.retirement?.epochVersion;
|
|
@@ -22516,6 +23348,8 @@ function projectOwnChatEntry(entry, entryId, userChatPK, ts, activity = {}) {
|
|
|
22516
23348
|
settings,
|
|
22517
23349
|
routes: entry.routes,
|
|
22518
23350
|
deliveryRegistered: entry.deliveryRegistered === true,
|
|
23351
|
+
attentionRegistrationVersion: entry.attentionRegistrationVersion,
|
|
23352
|
+
notificationMode: entry.notificationMode,
|
|
22519
23353
|
notificationTag: entry.notificationTag,
|
|
22520
23354
|
epochState: entry.current,
|
|
22521
23355
|
ownEntry: entry,
|
|
@@ -22634,14 +23468,22 @@ async function verifyOpenedPingEpoch(context) {
|
|
|
22634
23468
|
if (epochState.manifest.epochId !== payload.epochId || epochState.manifest.epochVersion !== payload.epochVersion) {
|
|
22635
23469
|
throw new Error("ping epoch mismatch");
|
|
22636
23470
|
}
|
|
23471
|
+
context.epochState = epochState;
|
|
22637
23472
|
if (hasBlockedMembers(epochState.manifest, options.blockedUids)) {
|
|
22638
23473
|
if (context.entry) {
|
|
22639
23474
|
await unlinkBlockedChat(cloud, uid, identity, context.entry, options);
|
|
22640
23475
|
return "deleted";
|
|
22641
23476
|
}
|
|
22642
|
-
|
|
23477
|
+
return "blocked";
|
|
23478
|
+
}
|
|
23479
|
+
const incomingMembership = payload.kind === "welcome" && (!context.entry || context.restoringMembership);
|
|
23480
|
+
if (incomingMembership) {
|
|
23481
|
+
const decision = await decideIncomingChat(context, epochState);
|
|
23482
|
+
if (decision === CHAT_ADMISSION_DECISIONS.REJECT)
|
|
23483
|
+
return "rejected";
|
|
23484
|
+
if (decision === CHAT_ADMISSION_DECISIONS.REQUEST)
|
|
23485
|
+
return "request";
|
|
22643
23486
|
}
|
|
22644
|
-
context.epochState = epochState;
|
|
22645
23487
|
return null;
|
|
22646
23488
|
}
|
|
22647
23489
|
function startOpenedWelcomeHydration(context) {
|
|
@@ -22649,7 +23491,6 @@ function startOpenedWelcomeHydration(context) {
|
|
|
22649
23491
|
if (!membershipWelcome)
|
|
22650
23492
|
return;
|
|
22651
23493
|
context.prefetchedMessage = captureAsync(readPingMessage(context.cloud, context.payload, context.epochState));
|
|
22652
|
-
context.deliveryRegistration = captureAsync(!context.entry || context.entry?.deliveryRegistered === true ? true : registerEpochDelivery(context.cloud, context.identity, context.epochState, context.entry?.current || null, context.entry?.deliveryRegistered === true, context.options));
|
|
22653
23494
|
}
|
|
22654
23495
|
async function applyOpenedPingMessage(context) {
|
|
22655
23496
|
const { cloud, identity, options, payload, uid } = context;
|
|
@@ -22695,19 +23536,12 @@ async function applyOpenedPingMessage(context) {
|
|
|
22695
23536
|
return null;
|
|
22696
23537
|
}
|
|
22697
23538
|
async function prepareOpenedPingOwner(context) {
|
|
22698
|
-
const {
|
|
23539
|
+
const { identity, payload, senderProfile } = context;
|
|
22699
23540
|
const routes = withSenderRoute(context.entry, payload, senderProfile, context.epochState);
|
|
22700
23541
|
const nowMs2 = timestampMs(payload.ts, Date.now()) ?? Date.now();
|
|
22701
23542
|
const membershipWelcome = (!context.entry || context.restoringMembership) && payload.kind === "welcome";
|
|
22702
|
-
const
|
|
22703
|
-
|
|
22704
|
-
if (initialDeliveryCapability)
|
|
22705
|
-
deliveryRegistered = true;
|
|
22706
|
-
if (!deliveryRegistered && context.deliveryRegistration) {
|
|
22707
|
-
deliveryRegistered = await unwrapAsync(context.deliveryRegistration);
|
|
22708
|
-
}
|
|
22709
|
-
if (!deliveryRegistered)
|
|
22710
|
-
deliveryRegistered = await registerEpochDelivery(cloud, identity, context.epochState, null, context.entry?.deliveryRegistered === true, context.options);
|
|
23543
|
+
const initialDeliveryRegistration = membershipWelcome ? epochDeliveryRegistration(identity, context.epochState) : null;
|
|
23544
|
+
const deliveryRegistered = true;
|
|
22711
23545
|
const nextEntry = context.entry ? {
|
|
22712
23546
|
...context.entry,
|
|
22713
23547
|
current: {
|
|
@@ -22721,6 +23555,8 @@ async function prepareOpenedPingOwner(context) {
|
|
|
22721
23555
|
},
|
|
22722
23556
|
routes,
|
|
22723
23557
|
deliveryRegistered,
|
|
23558
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
23559
|
+
notificationMode: normalizeChatNotificationMode(context.entry.notificationMode, context.epochState.manifest),
|
|
22724
23560
|
notificationTag: notificationChatTag(context.epochState.stateCapability, payload.chatId, identity.chatPK),
|
|
22725
23561
|
retirement: null
|
|
22726
23562
|
} : makeOwnChatEntry(context.epochState, {
|
|
@@ -22731,7 +23567,7 @@ async function prepareOpenedPingOwner(context) {
|
|
|
22731
23567
|
});
|
|
22732
23568
|
const welcomeMlsState = membershipWelcome && context.epochState.mlsSnapshot ? await prepareOwnerChatMlsSnapshot(identity, context.entryId, context.epochState, context.epochState.mlsSnapshot) : null;
|
|
22733
23569
|
Object.assign(context, {
|
|
22734
|
-
|
|
23570
|
+
initialDeliveryRegistration,
|
|
22735
23571
|
membershipWelcome,
|
|
22736
23572
|
nextEntry,
|
|
22737
23573
|
nowMs: nowMs2,
|
|
@@ -22745,7 +23581,7 @@ async function commitOpenedPingOwner(context) {
|
|
|
22745
23581
|
const {
|
|
22746
23582
|
cloud,
|
|
22747
23583
|
identity,
|
|
22748
|
-
|
|
23584
|
+
initialDeliveryRegistration,
|
|
22749
23585
|
membershipWelcome,
|
|
22750
23586
|
nextEntry,
|
|
22751
23587
|
nowMs: nowMs2,
|
|
@@ -22759,7 +23595,7 @@ async function commitOpenedPingOwner(context) {
|
|
|
22759
23595
|
return {
|
|
22760
23596
|
entry: nextEntry,
|
|
22761
23597
|
tsMs: nowMs2,
|
|
22762
|
-
deliveryRegistration:
|
|
23598
|
+
deliveryRegistration: initialDeliveryRegistration,
|
|
22763
23599
|
...welcomeMlsState ? { mlsWrites: [{ id: welcomeMlsState.stateId, value: welcomeMlsState.value }] } : {}
|
|
22764
23600
|
};
|
|
22765
23601
|
}
|
|
@@ -22774,13 +23610,18 @@ async function commitOpenedPingOwner(context) {
|
|
|
22774
23610
|
current: nextEntry.current,
|
|
22775
23611
|
routes: withSenderRoute(current, payload, senderProfile, context.epochState),
|
|
22776
23612
|
deliveryRegistered: current.deliveryRegistered || nextEntry.deliveryRegistered,
|
|
23613
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
23614
|
+
notificationMode: normalizeChatNotificationMode(current.notificationMode, context.epochState.manifest),
|
|
22777
23615
|
notificationTag: nextEntry.notificationTag,
|
|
22778
23616
|
retirement: membershipWelcome ? null : current.retirement
|
|
22779
23617
|
};
|
|
22780
23618
|
if (sameStableEntry(current, candidate) && !welcomeMlsState)
|
|
22781
23619
|
return { result: current };
|
|
23620
|
+
const registrationMatchesEpoch = current.current.manifest.epochId === context.epochState.manifest.epochId && hasCurrentChatAttentionRegistration(current);
|
|
23621
|
+
const deliveryRegistration = registrationMatchesEpoch ? null : epochDeliveryRegistration(identity, context.epochState, candidate.notificationMode);
|
|
22782
23622
|
return {
|
|
22783
23623
|
entry: candidate,
|
|
23624
|
+
deliveryRegistration,
|
|
22784
23625
|
...membershipWelcome ? { tsMs: nowMs2 } : {},
|
|
22785
23626
|
...welcomeMlsState ? { mlsWrites: [{ id: welcomeMlsState.stateId, value: welcomeMlsState.value }] } : {}
|
|
22786
23627
|
};
|
|
@@ -22792,10 +23633,18 @@ async function commitOpenedPingOwner(context) {
|
|
|
22792
23633
|
}
|
|
22793
23634
|
async function cleanupOpenedPing(context) {
|
|
22794
23635
|
const { cloud, identity, options } = context;
|
|
22795
|
-
|
|
23636
|
+
const membershipWelcome = context.payload.kind === "welcome" && (!context.entry || context.restoringMembership);
|
|
23637
|
+
if (!membershipWelcome || !context.epochState?.mlsSnapshot || context.epochState.mlsKeyPackageLastResort)
|
|
23638
|
+
return;
|
|
23639
|
+
const keyPackageId = cleanText(context.epochState.mlsKeyPackageId);
|
|
23640
|
+
if (!keyPackageId)
|
|
22796
23641
|
return;
|
|
23642
|
+
const consumed = options.consumedMlsKeyPackageIds;
|
|
23643
|
+
if (consumed?.has(keyPackageId))
|
|
23644
|
+
return;
|
|
23645
|
+
consumed?.add(keyPackageId);
|
|
22797
23646
|
assertInboxLease(options);
|
|
22798
|
-
await cloud.user.mls.keyPackages.deleteState(identity.uid,
|
|
23647
|
+
await cloud.user.mls.keyPackages.deleteState(identity.uid, keyPackageId).catch(() => false);
|
|
22799
23648
|
options.onMlsKeyPackageConsumed?.();
|
|
22800
23649
|
}
|
|
22801
23650
|
function openedPingProjection(context, entry) {
|
|
@@ -22862,9 +23711,13 @@ async function persistOpenedPingOwner(context) {
|
|
|
22862
23711
|
});
|
|
22863
23712
|
}
|
|
22864
23713
|
async function settleOpenedPing(prepared, onAuthoritative) {
|
|
22865
|
-
if (prepared.result)
|
|
22866
|
-
return prepared.result;
|
|
22867
23714
|
const { context } = prepared;
|
|
23715
|
+
if (prepared.result === "request")
|
|
23716
|
+
return "request";
|
|
23717
|
+
if (prepared.result) {
|
|
23718
|
+
await cleanupOpenedPing(context);
|
|
23719
|
+
return prepared.result;
|
|
23720
|
+
}
|
|
22868
23721
|
startOpenedWelcomeHydration(context);
|
|
22869
23722
|
const createsMembership = !context.entry && context.payload.kind === "welcome";
|
|
22870
23723
|
const ownerPersistence = createsMembership ? captureAsync(persistOpenedPingOwner(context)) : null;
|
|
@@ -22889,6 +23742,15 @@ async function settleOpenedPing(prepared, onAuthoritative) {
|
|
|
22889
23742
|
});
|
|
22890
23743
|
return true;
|
|
22891
23744
|
}
|
|
23745
|
+
async function hydrateOpenedChatRequest(context) {
|
|
23746
|
+
const { message, record } = await readPingMessage(context.cloud, context.payload, context.epochState);
|
|
23747
|
+
if (!message || !record || !cleanText(context.payload.messageId)) {
|
|
23748
|
+
throw new Error("chat request message required");
|
|
23749
|
+
}
|
|
23750
|
+
context.message = message;
|
|
23751
|
+
context.record = record;
|
|
23752
|
+
context.preview = nextPreview(null, record, message, context.payload);
|
|
23753
|
+
}
|
|
22892
23754
|
function senderProfilePromise(cache, cloud, payload) {
|
|
22893
23755
|
const key = `${cleanText(payload?.senderUid)}:${cleanText(payload?.senderChatPK)}`;
|
|
22894
23756
|
return cachedInboxRead(cache, key, () => resolveSenderProfile(cloud, payload));
|
|
@@ -22961,13 +23823,15 @@ function welcomeAnnouncementFromPreparedPing(authenticated, prepared, document)
|
|
|
22961
23823
|
members: manifest.members,
|
|
22962
23824
|
settings,
|
|
22963
23825
|
messageId: payload.messageId || null,
|
|
23826
|
+
messageRequest: prepared?.opened?.result === "request",
|
|
23827
|
+
preview: prepared?.opened?.context?.preview || null,
|
|
22964
23828
|
at: ts
|
|
22965
23829
|
};
|
|
22966
23830
|
}
|
|
22967
23831
|
function announcePreparedWelcome(context, authenticated, prepared, document, startedAt) {
|
|
22968
23832
|
const { blockedUids, options, state } = context;
|
|
22969
23833
|
const payload = authenticated?.ping?.payload;
|
|
22970
|
-
if (payload?.kind !== "welcome" || state.currentChats.some((chat) => chat?.id === payload.chatId) || blockedUids.has(payload.senderUid)) {
|
|
23834
|
+
if (prepared?.opened?.result && prepared.opened.result !== "request" || payload?.kind !== "welcome" || state.currentChats.some((chat) => chat?.id === payload.chatId) || blockedUids.has(payload.senderUid)) {
|
|
22971
23835
|
return () => {};
|
|
22972
23836
|
}
|
|
22973
23837
|
markDiag(options.diag, "chat.inbox.chat.announced", {
|
|
@@ -23119,12 +23983,11 @@ async function commitRemovedEpochTransition(context) {
|
|
|
23119
23983
|
return { removed: true, chatId: parent.chatId, entry: retiredEntry };
|
|
23120
23984
|
}
|
|
23121
23985
|
async function prepareReceivedEpochOwner(context) {
|
|
23122
|
-
const {
|
|
23986
|
+
const { entry, identity, options, parent } = context;
|
|
23123
23987
|
if (hasBlockedMembers(context.epochState.manifest, options.blockedUids)) {
|
|
23124
|
-
context.terminal = await unlinkBlockedChat(cloud, context.uid, identity, entry, options);
|
|
23988
|
+
context.terminal = await unlinkBlockedChat(context.cloud, context.uid, identity, entry, options);
|
|
23125
23989
|
return;
|
|
23126
23990
|
}
|
|
23127
|
-
const deliveryRegistered = await registerEpochDelivery(cloud, identity, context.epochState, entry.current, entry.deliveryRegistered === true, options);
|
|
23128
23991
|
context.nextEntry = {
|
|
23129
23992
|
...entry,
|
|
23130
23993
|
current: {
|
|
@@ -23137,7 +24000,9 @@ async function prepareReceivedEpochOwner(context) {
|
|
|
23137
24000
|
settings: context.epochState.settings
|
|
23138
24001
|
},
|
|
23139
24002
|
routes: routesForManifest(entry, context.epochState),
|
|
23140
|
-
deliveryRegistered,
|
|
24003
|
+
deliveryRegistered: true,
|
|
24004
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
24005
|
+
notificationMode: normalizeChatNotificationMode(entry.notificationMode, context.epochState.manifest),
|
|
23141
24006
|
notificationTag: notificationChatTag(context.epochState.stateCapability, parent.chatId, identity.chatPK)
|
|
23142
24007
|
};
|
|
23143
24008
|
context.historyId = ownEpochEntryId(identity.chatPrivateKey, parent.chatId, parent.epochId);
|
|
@@ -23166,8 +24031,11 @@ async function commitReceivedEpochOwner(context) {
|
|
|
23166
24031
|
current: nextEntry.current,
|
|
23167
24032
|
routes: routesForManifest(current, epochState),
|
|
23168
24033
|
deliveryRegistered: current.deliveryRegistered || nextEntry.deliveryRegistered,
|
|
24034
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
24035
|
+
notificationMode: normalizeChatNotificationMode(current.notificationMode, epochState.manifest),
|
|
23169
24036
|
notificationTag: nextEntry.notificationTag
|
|
23170
24037
|
},
|
|
24038
|
+
deliveryRegistration: epochDeliveryRegistration(identity, epochState, current.notificationMode),
|
|
23171
24039
|
touchTs: true,
|
|
23172
24040
|
epochs: [{
|
|
23173
24041
|
id: historyId,
|
|
@@ -23181,8 +24049,17 @@ async function commitReceivedEpochOwner(context) {
|
|
|
23181
24049
|
};
|
|
23182
24050
|
});
|
|
23183
24051
|
}
|
|
23184
|
-
function publishReceivedEpochTransition(context) {
|
|
24052
|
+
async function publishReceivedEpochTransition(context) {
|
|
23185
24053
|
assertInboxLease(context.options);
|
|
24054
|
+
if (context.entry.deliveryRegistered) {
|
|
24055
|
+
const oldCapability = deriveChatDeliveryCapability(context.entry.current.stateCapability, {
|
|
24056
|
+
chatId: context.parent.chatId,
|
|
24057
|
+
recipientChatPK: context.identity.chatPK,
|
|
24058
|
+
generation: context.parent.epochVersion
|
|
24059
|
+
});
|
|
24060
|
+
await context.cloud.delivery.revoke(oldCapability).catch(() => false);
|
|
24061
|
+
assertInboxLease(context.options);
|
|
24062
|
+
}
|
|
23186
24063
|
context.options.onTransition?.(projectOwnChatEntry(context.committedEntry, context.entryId, context.identity.chatPK, context.nowMs), {
|
|
23187
24064
|
...context.action,
|
|
23188
24065
|
membershipCommitted: true
|
|
@@ -23445,12 +24322,17 @@ async function prepareInboxWork(context, prepared, processingOptions = {}) {
|
|
|
23445
24322
|
currentChats: state.currentChats,
|
|
23446
24323
|
discoveryStartedAt: prepared.startedAt,
|
|
23447
24324
|
mlsKeyPackageStateCache: context.mlsKeyPackageStateCache,
|
|
24325
|
+
consumedMlsKeyPackageIds: context.consumedMlsKeyPackageIds,
|
|
23448
24326
|
mlsPackageCache: context.mlsPackageCache,
|
|
23449
24327
|
settingsRecordCache: context.settingsRecordCache,
|
|
23450
24328
|
onInboxCommit: state.commitOwner,
|
|
23451
24329
|
onTransition: state.publishTransition,
|
|
23452
|
-
onInboxDelete: state.publishDelete
|
|
24330
|
+
onInboxDelete: state.publishDelete,
|
|
24331
|
+
requestToken: `${document?.slot === true ? "slot" : "ping"}:${cleanText(document?.id)}`
|
|
23453
24332
|
});
|
|
24333
|
+
if (ping.opened?.result === "request") {
|
|
24334
|
+
await hydrateOpenedChatRequest(ping.opened.context);
|
|
24335
|
+
}
|
|
23454
24336
|
announcement = announcePreparedWelcome(context, authenticated, ping, document, prepared.startedAt);
|
|
23455
24337
|
return { announcement, document, ping, prepared, processingOptions, status: null };
|
|
23456
24338
|
} catch (error) {
|
|
@@ -23475,6 +24357,11 @@ async function settleInboxWork(context, work, onAuthoritative) {
|
|
|
23475
24357
|
result: result === true ? "applied" : cleanText(result) || "ignored",
|
|
23476
24358
|
slot: document?.slot === true
|
|
23477
24359
|
});
|
|
24360
|
+
if (result === "request") {
|
|
24361
|
+
options.onInboxRequest?.(work.announcement, document);
|
|
24362
|
+
options.onPingSettled?.(document, result);
|
|
24363
|
+
return "request";
|
|
24364
|
+
}
|
|
23478
24365
|
if (result !== "duplicate")
|
|
23479
24366
|
state.processed += 1;
|
|
23480
24367
|
if (!document.slot || result === "deleted") {
|
|
@@ -23778,6 +24665,7 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
|
|
|
23778
24665
|
const context = {
|
|
23779
24666
|
blockedUids,
|
|
23780
24667
|
cloud,
|
|
24668
|
+
consumedMlsKeyPackageIds: new Set,
|
|
23781
24669
|
discoveryPool: createInboxWorkPool(CHAT_INBOX_DISCOVERY_PARALLEL_CHATS),
|
|
23782
24670
|
identity,
|
|
23783
24671
|
mlsKeyPackageStateCache: new Map,
|
|
@@ -23948,6 +24836,8 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
23948
24836
|
let inboxRequestedRetryDelayMs = null;
|
|
23949
24837
|
const priorityInboxDocuments = new Map;
|
|
23950
24838
|
const activeInboxDocuments = new Map;
|
|
24839
|
+
const pendingChatRequests = new Map;
|
|
24840
|
+
const chatRequestResolutions = new Map;
|
|
23951
24841
|
const inboxPendingAttempts = new Map;
|
|
23952
24842
|
const leaveProposalObservedAt = new Map;
|
|
23953
24843
|
let closed = false;
|
|
@@ -23976,8 +24866,17 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
23976
24866
|
const revokeInboxAnnouncement = (chatId, token) => {
|
|
23977
24867
|
if (closed)
|
|
23978
24868
|
return false;
|
|
24869
|
+
if (pendingChatRequests.get(chatId)?.announcement?.token === token) {
|
|
24870
|
+
pendingChatRequests.delete(chatId);
|
|
24871
|
+
}
|
|
23979
24872
|
return options.onInboxRevoke?.(chatId, token) ?? false;
|
|
23980
24873
|
};
|
|
24874
|
+
const retainInboxRequest = (announcement, document) => {
|
|
24875
|
+
if (closed || !announcement?.messageRequest || !announcement?.chatId || !announcement?.token)
|
|
24876
|
+
return false;
|
|
24877
|
+
pendingChatRequests.set(announcement.chatId, { announcement, document });
|
|
24878
|
+
return true;
|
|
24879
|
+
};
|
|
23981
24880
|
const publishInboxTransition = (chat, message) => {
|
|
23982
24881
|
if (closed)
|
|
23983
24882
|
return false;
|
|
@@ -24055,6 +24954,10 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24055
24954
|
notificationPrivateKey: options.notificationPrivateKey,
|
|
24056
24955
|
mls: options.mls,
|
|
24057
24956
|
blockedUids: options.blockedUids,
|
|
24957
|
+
chatAdmission: options.chatAdmission,
|
|
24958
|
+
incomingChatDecision: options.incomingChatDecision,
|
|
24959
|
+
chatRequestResolutions,
|
|
24960
|
+
onInboxRequest: retainInboxRequest,
|
|
24058
24961
|
sinceMs: inboxSinceMs,
|
|
24059
24962
|
priorityDocuments,
|
|
24060
24963
|
settlementController,
|
|
@@ -24100,7 +25003,7 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24100
25003
|
retryRequested = true;
|
|
24101
25004
|
return true;
|
|
24102
25005
|
},
|
|
24103
|
-
onPingSettled: (doc) => {
|
|
25006
|
+
onPingSettled: (doc, result) => {
|
|
24104
25007
|
const key = inboxRetryKey(doc);
|
|
24105
25008
|
if (key)
|
|
24106
25009
|
inboxPendingAttempts.delete(key);
|
|
@@ -24108,6 +25011,12 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24108
25011
|
if (priorityKey && chatDocDataEqual(priorityInboxDocuments.get(priorityKey), doc)) {
|
|
24109
25012
|
priorityInboxDocuments.delete(priorityKey);
|
|
24110
25013
|
}
|
|
25014
|
+
const resolution = chatRequestResolutions.get(priorityKey);
|
|
25015
|
+
if (resolution && result !== "request") {
|
|
25016
|
+
chatRequestResolutions.delete(priorityKey);
|
|
25017
|
+
pendingChatRequests.delete(resolution.chatId);
|
|
25018
|
+
resolution.resolve(result === true || result === "rejected");
|
|
25019
|
+
}
|
|
24111
25020
|
},
|
|
24112
25021
|
onPingStarted: (doc) => {
|
|
24113
25022
|
const key = inboxDocumentKey2(doc);
|
|
@@ -24183,6 +25092,27 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24183
25092
|
setTimeout(processQueuedInbox, 0);
|
|
24184
25093
|
}
|
|
24185
25094
|
};
|
|
25095
|
+
const resolveChatRequest = (chatId, decision) => {
|
|
25096
|
+
const request = pendingChatRequests.get(chatId);
|
|
25097
|
+
if (!request?.document || !["accept", "reject"].includes(decision))
|
|
25098
|
+
return Promise.resolve(false);
|
|
25099
|
+
const token = request.announcement.token;
|
|
25100
|
+
const existing = chatRequestResolutions.get(token);
|
|
25101
|
+
if (existing)
|
|
25102
|
+
return existing.promise;
|
|
25103
|
+
let resolve;
|
|
25104
|
+
let reject;
|
|
25105
|
+
const promise = new Promise((resolveValue, rejectValue) => {
|
|
25106
|
+
resolve = resolveValue;
|
|
25107
|
+
reject = rejectValue;
|
|
25108
|
+
});
|
|
25109
|
+
chatRequestResolutions.set(token, { chatId, decision, promise, reject, resolve });
|
|
25110
|
+
priorityInboxDocuments.set(token, request.document);
|
|
25111
|
+
inboxQueued = true;
|
|
25112
|
+
if (!inboxProcessing)
|
|
25113
|
+
setTimeout(processQueuedInbox, 0);
|
|
25114
|
+
return promise;
|
|
25115
|
+
};
|
|
24186
25116
|
const schedule = (delayMs) => {
|
|
24187
25117
|
if (timer || processing || closed) {
|
|
24188
25118
|
return;
|
|
@@ -24242,13 +25172,19 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24242
25172
|
inboxQueued = false;
|
|
24243
25173
|
priorityInboxDocuments.clear();
|
|
24244
25174
|
activeInboxDocuments.clear();
|
|
25175
|
+
for (const resolution of chatRequestResolutions.values()) {
|
|
25176
|
+
resolution.reject(new Error("chat inbox closed"));
|
|
25177
|
+
}
|
|
25178
|
+
chatRequestResolutions.clear();
|
|
25179
|
+
pendingChatRequests.clear();
|
|
24245
25180
|
unsub?.();
|
|
24246
25181
|
tokenUnsub?.();
|
|
24247
25182
|
inboxUnsub?.();
|
|
24248
25183
|
};
|
|
24249
25184
|
return Object.freeze({
|
|
24250
25185
|
close,
|
|
24251
|
-
prioritizeChat: (chatId) => settlementController.prioritizeChat(chatId)
|
|
25186
|
+
prioritizeChat: (chatId) => settlementController.prioritizeChat(chatId),
|
|
25187
|
+
resolveChatRequest
|
|
24252
25188
|
});
|
|
24253
25189
|
}
|
|
24254
25190
|
async function loadMoreChats(cloud, uid, userChatPK, userPrivKey, afterChat, pageSize) {
|
|
@@ -24330,7 +25266,7 @@ async function getChat(cloud, uid, chatId, userChatPK, userPrivKey) {
|
|
|
24330
25266
|
}
|
|
24331
25267
|
async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
|
|
24332
25268
|
const data = entryRecord;
|
|
24333
|
-
const entry = await
|
|
25269
|
+
const entry = await openOwnChatRecord(userPrivKey, entryRecord.id, data);
|
|
24334
25270
|
if (entry.ownerRevision !== data?.revision) {
|
|
24335
25271
|
throw new Error("chat owner revision mismatch");
|
|
24336
25272
|
}
|
|
@@ -24368,6 +25304,8 @@ async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
|
|
|
24368
25304
|
readMs: null,
|
|
24369
25305
|
startMs,
|
|
24370
25306
|
deliveryRegistered: entry.deliveryRegistered === true,
|
|
25307
|
+
attentionRegistrationVersion: entry.attentionRegistrationVersion,
|
|
25308
|
+
notificationMode: entry.notificationMode,
|
|
24371
25309
|
routes: entry.routes,
|
|
24372
25310
|
peerDeliveryCapability: directPeer ? entry.routes?.[directPeer.chatPK]?.deliveryCapability || null : null,
|
|
24373
25311
|
peerNotificationPK: directPeer?.notificationPK || null,
|
|
@@ -24645,12 +25583,14 @@ function announcementRow(announcement, selfChatPK) {
|
|
|
24645
25583
|
peerChatPK: directPeer?.chatPK || null,
|
|
24646
25584
|
peerUid: directPeer?.uid || null,
|
|
24647
25585
|
settings: announcement.settings || {},
|
|
24648
|
-
preview: null,
|
|
25586
|
+
preview: announcement.preview || null,
|
|
24649
25587
|
readMs: null,
|
|
24650
25588
|
inboxMessageId: announcement.messageId || null,
|
|
24651
25589
|
inboxMessageAt: at,
|
|
24652
25590
|
ts: at,
|
|
24653
|
-
unseen:
|
|
25591
|
+
unseen: announcement.messageRequest === true,
|
|
25592
|
+
messageRequest: announcement.messageRequest === true,
|
|
25593
|
+
requestToken: announcement.token
|
|
24654
25594
|
};
|
|
24655
25595
|
}
|
|
24656
25596
|
function sourceEntry() {
|
|
@@ -25012,6 +25952,8 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
25012
25952
|
settings: retiredEntry.current.settings.values,
|
|
25013
25953
|
routes: retiredEntry.routes,
|
|
25014
25954
|
deliveryRegistered: retiredEntry.deliveryRegistered === true,
|
|
25955
|
+
attentionRegistrationVersion: retiredEntry.attentionRegistrationVersion,
|
|
25956
|
+
notificationMode: retiredEntry.notificationMode,
|
|
25015
25957
|
notificationTag: retiredEntry.notificationTag || null,
|
|
25016
25958
|
epochState: retiredEntry.current,
|
|
25017
25959
|
ownEntry: retiredEntry,
|
|
@@ -25098,6 +26040,8 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
25098
26040
|
peerSigningPublicKey: owner?.signingKeysByChatKey?.[peerChatPK] || "",
|
|
25099
26041
|
signerShared: owner?.signerShared === true,
|
|
25100
26042
|
deliveryRegistered: owner?.deliveryRegistered === true,
|
|
26043
|
+
attentionRegistrationVersion: owner?.attentionRegistrationVersion || 0,
|
|
26044
|
+
notificationMode: owner?.notificationMode || null,
|
|
25101
26045
|
peerDeliveryCapability: owner?.peerDeliveryCapability || "",
|
|
25102
26046
|
peerNotificationPK: owner?.peerNotificationPK || "",
|
|
25103
26047
|
notificationTag: owner?.notificationTag || "",
|
|
@@ -25179,12 +26123,14 @@ function createChatListListener({
|
|
|
25179
26123
|
let listening = false;
|
|
25180
26124
|
let unsubscribeChats = null;
|
|
25181
26125
|
let prioritizeInboxChat = null;
|
|
26126
|
+
let resolveInboxChatRequest = null;
|
|
25182
26127
|
let retryTimer = null;
|
|
25183
26128
|
let updateSequence = 0;
|
|
25184
26129
|
const stop = () => {
|
|
25185
26130
|
unsubscribeChats?.();
|
|
25186
26131
|
unsubscribeChats = null;
|
|
25187
26132
|
prioritizeInboxChat = null;
|
|
26133
|
+
resolveInboxChatRequest = null;
|
|
25188
26134
|
};
|
|
25189
26135
|
const retainAfterError = (error) => {
|
|
25190
26136
|
const sources = getSources();
|
|
@@ -25343,6 +26289,8 @@ function createChatListListener({
|
|
|
25343
26289
|
inboxTransport: sources.inboxTransport,
|
|
25344
26290
|
mls: sources.mls,
|
|
25345
26291
|
blockedUids: sources.blockedUidSet,
|
|
26292
|
+
chatAdmission: sources.chatAdmission,
|
|
26293
|
+
incomingChatDecision: sources.incomingChatDecision,
|
|
25346
26294
|
localCache: sources.localCache,
|
|
25347
26295
|
diag: sources.diag,
|
|
25348
26296
|
getCurrentChat: index.getOwnerChat,
|
|
@@ -25353,6 +26301,7 @@ function createChatListListener({
|
|
|
25353
26301
|
throw new Error("chat list subscription lifecycle required");
|
|
25354
26302
|
}
|
|
25355
26303
|
prioritizeInboxChat = subscription.prioritizeChat;
|
|
26304
|
+
resolveInboxChatRequest = subscription.resolveChatRequest;
|
|
25356
26305
|
unsubscribeChats = () => {
|
|
25357
26306
|
cancelled = true;
|
|
25358
26307
|
markDiag(getSources().diag, "chat.provider.listen.stop", { elapsedMs: Date.now() - listenStartedAt });
|
|
@@ -25370,6 +26319,7 @@ function createChatListListener({
|
|
|
25370
26319
|
},
|
|
25371
26320
|
isListening: () => listening,
|
|
25372
26321
|
prioritizeChat: (chatId) => prioritizeInboxChat?.(chatId) || Promise.resolve(false),
|
|
26322
|
+
resolveChatRequest: (chatId, decision) => resolveInboxChatRequest?.(chatId, decision) || Promise.resolve(false),
|
|
25373
26323
|
start: () => {
|
|
25374
26324
|
if (listening)
|
|
25375
26325
|
return;
|
|
@@ -25970,6 +26920,7 @@ function createChatList(initialSources) {
|
|
|
25970
26920
|
getRow: index.getRow,
|
|
25971
26921
|
markChatRead: index.markChatRead,
|
|
25972
26922
|
reconcileChatEpoch: epoch.reconcileChatEpoch,
|
|
26923
|
+
resolveChatRequest: listener.resolveChatRequest,
|
|
25973
26924
|
sendOptionsForPeer: index.sendOptionsForPeer,
|
|
25974
26925
|
hasChat: paging.hasChat
|
|
25975
26926
|
};
|
|
@@ -26538,20 +27489,24 @@ function memberFromChatIdentity(value) {
|
|
|
26538
27489
|
uid: identity.uid,
|
|
26539
27490
|
chatPK: identity.chatPK,
|
|
26540
27491
|
chatSigningPK: identity.chatSigningPK,
|
|
26541
|
-
notificationPK: identity.notificationPK
|
|
27492
|
+
notificationPK: identity.notificationPK,
|
|
27493
|
+
...value?.admissionCapability ? { admissionCapability: cleanText(value.admissionCapability).toLowerCase() } : {}
|
|
26542
27494
|
};
|
|
26543
27495
|
const mlsLeafId = cleanText(value?.mlsLeafId).toLowerCase();
|
|
26544
27496
|
return /^[0-9a-f]{64}$/u.test(mlsLeafId) ? { ...member, mlsLeafId } : member;
|
|
26545
27497
|
}
|
|
26546
27498
|
async function registerInitialDelivery(cloud, identity, epochState) {
|
|
26547
|
-
const
|
|
27499
|
+
const registration = chatDeliveryRegistration(epochState.stateCapability, {
|
|
27500
|
+
attentionSecret: epochState.epochSecret,
|
|
26548
27501
|
chatId: epochState.manifest.chatId,
|
|
26549
27502
|
recipientChatPK: identity.chatPK,
|
|
26550
|
-
generation: epochState.manifest.epochVersion
|
|
27503
|
+
generation: epochState.manifest.epochVersion,
|
|
27504
|
+
manifest: epochState.manifest
|
|
26551
27505
|
});
|
|
26552
|
-
return cloud.delivery.register(
|
|
27506
|
+
return cloud.delivery.register(registration).then(() => true, () => false);
|
|
26553
27507
|
}
|
|
26554
|
-
function initialRoutes(manifest, stateCapability, ownerChatPK) {
|
|
27508
|
+
function initialRoutes(manifest, stateCapability, ownerChatPK, memberValues = []) {
|
|
27509
|
+
const privateByChatPK = new Map(memberValues.map((member) => [member.chatPK, member]));
|
|
26555
27510
|
return Object.fromEntries(manifest.members.map((member) => [member.chatPK, {
|
|
26556
27511
|
uid: member.uid,
|
|
26557
27512
|
deliveryCapability: manifest.lineage === CHAT_LINEAGES.GROUP || member.chatPK === ownerChatPK ? deriveChatDeliveryCapability(stateCapability, {
|
|
@@ -26560,13 +27515,14 @@ function initialRoutes(manifest, stateCapability, ownerChatPK) {
|
|
|
26560
27515
|
generation: manifest.epochVersion
|
|
26561
27516
|
}) : null,
|
|
26562
27517
|
notificationPK: member.notificationPK,
|
|
27518
|
+
admissionCapability: privateByChatPK.get(member.chatPK)?.admissionCapability || null,
|
|
26563
27519
|
generation: manifest.epochVersion
|
|
26564
27520
|
}]));
|
|
26565
27521
|
}
|
|
26566
27522
|
async function prepareInitialOwner(identity, epochState, fields = {}) {
|
|
26567
27523
|
const entryId = ownChatEntryId(identity.chatPrivateKey, epochState.manifest.chatId);
|
|
26568
27524
|
const entry = makeOwnChatEntry(epochState, {
|
|
26569
|
-
routes: initialRoutes(epochState.manifest, epochState.stateCapability, identity.chatPK),
|
|
27525
|
+
routes: initialRoutes(epochState.manifest, epochState.stateCapability, identity.chatPK, fields.members),
|
|
26570
27526
|
startMs: Date.now(),
|
|
26571
27527
|
deliveryRegistered: fields.deliveryRegistered === true,
|
|
26572
27528
|
notificationTag: notificationChatTag(epochState.stateCapability, epochState.manifest.chatId, identity.chatPK)
|
|
@@ -26581,6 +27537,7 @@ async function prepareInitialOwner(identity, epochState, fields = {}) {
|
|
|
26581
27537
|
entryId,
|
|
26582
27538
|
record: {
|
|
26583
27539
|
body: await sealOwnChatEntry(identity.chatPrivateKey, entryId, entry),
|
|
27540
|
+
notificationBody: await sealOwnChatNotificationPreference(identity.chatPrivateKey, entryId, entry),
|
|
26584
27541
|
revision: entry.ownerRevision,
|
|
26585
27542
|
tsMs
|
|
26586
27543
|
}
|
|
@@ -26680,7 +27637,8 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
26680
27637
|
const stateId = chatMlsStateId(identity.chatPrivateKey, chatId, initial.transitionCommitment);
|
|
26681
27638
|
const ownerMlsState = await sealChatMlsState(identity.chatPrivateKey, stateId, { v: 1, chatId, transitionCommitment: initial.transitionCommitment }, mlsGroup.snapshot);
|
|
26682
27639
|
const owner = await prepareInitialOwner(identity, initialState, {
|
|
26683
|
-
deliveryRegistered: true
|
|
27640
|
+
deliveryRegistered: true,
|
|
27641
|
+
members
|
|
26684
27642
|
});
|
|
26685
27643
|
let stateError = null;
|
|
26686
27644
|
try {
|
|
@@ -26794,7 +27752,7 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
26794
27752
|
|
|
26795
27753
|
// ../../core/chat/direct.js
|
|
26796
27754
|
"use client";
|
|
26797
|
-
function
|
|
27755
|
+
function orderedChatKeys2(first, second) {
|
|
26798
27756
|
return [
|
|
26799
27757
|
cleanChatHex(first, "chat public key"),
|
|
26800
27758
|
cleanChatHex(second, "peer chat public key")
|
|
@@ -26816,7 +27774,7 @@ function deriveDirectRouteId(chatPrivateKey, chatPK, peerChatPK) {
|
|
|
26816
27774
|
try {
|
|
26817
27775
|
peerKey = fromHex(otherChatPK, "peer chat public key");
|
|
26818
27776
|
shared = x25519.getSharedSecret(toBytes32(chatPrivateKey, "chat private key"), peerKey);
|
|
26819
|
-
routeId = deriveKey(shared, "direct-route-id-v3",
|
|
27777
|
+
routeId = deriveKey(shared, "direct-route-id-v3", orderedChatKeys2(ownChatPK, otherChatPK));
|
|
26820
27778
|
return toHex(routeId);
|
|
26821
27779
|
} finally {
|
|
26822
27780
|
cleanBytes(peerKey, shared, routeId);
|
|
@@ -26966,6 +27924,9 @@ function createChatSessionLaunches({
|
|
|
26966
27924
|
setSelectedChat(existingChatId);
|
|
26967
27925
|
return existingChatId;
|
|
26968
27926
|
}
|
|
27927
|
+
if (directAdmissionForPeer(operation.identity.chatPrivateKey, chatPK, profile) === CHAT_ADMISSION_DECISIONS.REJECT) {
|
|
27928
|
+
throw new Error("peer is not accepting direct chats");
|
|
27929
|
+
}
|
|
26969
27930
|
const routeId = directRouteIdForPeer(member.chatPK, operation);
|
|
26970
27931
|
if (operation.pendingOwner.getPendingLaunch()?.id === routeId)
|
|
26971
27932
|
return routeId;
|
|
@@ -27034,6 +27995,13 @@ function createChatSessionLaunches({
|
|
|
27034
27995
|
const profileList = Array.isArray(profiles) ? profiles : [profiles];
|
|
27035
27996
|
const blockedSet = new Set(blocked);
|
|
27036
27997
|
const requestedMembers = profileList.map(profileMember);
|
|
27998
|
+
if (profileList.length === 1) {
|
|
27999
|
+
if (directAdmissionForPeer(chatPrivateKey, chatPK, profileList[0]) === CHAT_ADMISSION_DECISIONS.REJECT) {
|
|
28000
|
+
throw new Error("peer is not accepting direct chats");
|
|
28001
|
+
}
|
|
28002
|
+
} else if (profileList.length > 1 && profileList.some((profile) => !canInviteToGroup(profile))) {
|
|
28003
|
+
throw new Error("peer is not accepting group invitations");
|
|
28004
|
+
}
|
|
27037
28005
|
for (const member of requestedMembers) {
|
|
27038
28006
|
if (member.chatPK === chatPK)
|
|
27039
28007
|
throw new Error("current user is already a chat member");
|
|
@@ -27162,6 +28130,9 @@ function createChatSessionLaunches({
|
|
|
27162
28130
|
directOpens.delete(routeId);
|
|
27163
28131
|
return runExistingDirectLifecycle(existingChatId, lifecycle, operation);
|
|
27164
28132
|
}
|
|
28133
|
+
if (directAdmissionForPeer(operation.identity.chatPrivateKey, chatPK, profile) === CHAT_ADMISSION_DECISIONS.REJECT) {
|
|
28134
|
+
throw new Error("peer is not accepting direct chats");
|
|
28135
|
+
}
|
|
27165
28136
|
const resolution = startDirectResolution(routeId, member.chatPK, operation);
|
|
27166
28137
|
const state = { operation, promise: null };
|
|
27167
28138
|
state.promise = resolution.promise.then((resolvedChatId) => {
|
|
@@ -27243,6 +28214,7 @@ function createChatSessionPending({
|
|
|
27243
28214
|
setSelectedChat,
|
|
27244
28215
|
publish,
|
|
27245
28216
|
getOwnerChat,
|
|
28217
|
+
getChatRow,
|
|
27246
28218
|
ensureChat,
|
|
27247
28219
|
profileMember,
|
|
27248
28220
|
transitionIdentity,
|
|
@@ -27607,7 +28579,7 @@ function createChatSessionPending({
|
|
|
27607
28579
|
setSelectedChat(chatId);
|
|
27608
28580
|
return true;
|
|
27609
28581
|
};
|
|
27610
|
-
if (pendingMatches || getOwnerChat(chatId))
|
|
28582
|
+
if (pendingMatches || getOwnerChat(chatId) || getChatRow(chatId)?.messageRequest)
|
|
27611
28583
|
return selectResolved();
|
|
27612
28584
|
return Promise.resolve(ensureChat(chatId)).then((ready) => ready === true && selectResolved());
|
|
27613
28585
|
},
|
|
@@ -27680,11 +28652,13 @@ function createChatSessionMembership({
|
|
|
27680
28652
|
if (!profile?.uid || !profile?.chatPK || !profile?.chatSigningPK || !profile?.notificationPK) {
|
|
27681
28653
|
throw new Error("complete peer chat identity required");
|
|
27682
28654
|
}
|
|
28655
|
+
const identity = getIdentity();
|
|
27683
28656
|
return {
|
|
27684
28657
|
uid: profile.uid,
|
|
27685
28658
|
chatPK: profile.chatPK,
|
|
27686
28659
|
chatSigningPK: profile.chatSigningPK,
|
|
27687
|
-
notificationPK: profile.notificationPK
|
|
28660
|
+
notificationPK: profile.notificationPK,
|
|
28661
|
+
admissionCapability: deriveDirectAdmissionCapability(identity.chatPrivateKey, identity.chatPK, profile.chatPK, profile.chatPK)
|
|
27688
28662
|
};
|
|
27689
28663
|
};
|
|
27690
28664
|
const prepareMlsAdditions = async (profiles, operation = operationContext()) => {
|
|
@@ -27928,7 +28902,12 @@ function createChatSessionMembership({
|
|
|
27928
28902
|
return state.promise;
|
|
27929
28903
|
};
|
|
27930
28904
|
const addChatMembers = (chatId, profiles) => {
|
|
27931
|
-
const
|
|
28905
|
+
const profileList = Array.isArray(profiles) ? profiles : [profiles];
|
|
28906
|
+
for (const profile of profileList) {
|
|
28907
|
+
if (!canInviteToGroup(profile))
|
|
28908
|
+
throw new Error("peer is not accepting group invitations");
|
|
28909
|
+
}
|
|
28910
|
+
const additions = profileList.map(profileMember);
|
|
27932
28911
|
const blockedSet = new Set(getIdentity().blocked);
|
|
27933
28912
|
for (const member of additions) {
|
|
27934
28913
|
if (blockedSet.has(member.uid))
|
|
@@ -27986,7 +28965,7 @@ function createChatSessionMembership({
|
|
|
27986
28965
|
|
|
27987
28966
|
// ../../core/chat/deletiondelivery.js
|
|
27988
28967
|
"use client";
|
|
27989
|
-
async function deliverChatDeletedPings(cloud, identityValue, epochState, members = [], eventIdValue = "") {
|
|
28968
|
+
async function deliverChatDeletedPings(cloud, identityValue, epochState, members = [], eventIdValue = "", routes = {}) {
|
|
27990
28969
|
const identity = {
|
|
27991
28970
|
...identityValue,
|
|
27992
28971
|
uid: cleanText(identityValue?.uid),
|
|
@@ -28024,6 +29003,7 @@ async function deliverChatDeletedPings(cloud, identityValue, epochState, members
|
|
|
28024
29003
|
deliveries.push({
|
|
28025
29004
|
member,
|
|
28026
29005
|
promise: pushCriticalInbox(cloud, member.uid, ping, {
|
|
29006
|
+
admissionCapability: routes?.[member.chatPK]?.admissionCapability || null,
|
|
28027
29007
|
descriptor,
|
|
28028
29008
|
routeTag: notificationRouteTag(descriptor),
|
|
28029
29009
|
notify: true
|
|
@@ -28227,6 +29207,7 @@ function createChatDelete({
|
|
|
28227
29207
|
stateCapability: serverChat?.epochState?.stateCapability,
|
|
28228
29208
|
epochVersion: serverChat?.epochVersion,
|
|
28229
29209
|
members: serverChat?.members || [],
|
|
29210
|
+
routes: sourceChat?.routes || {},
|
|
28230
29211
|
transitionCommitment: serverChat?.epochState?.transitionCommitment,
|
|
28231
29212
|
ownEntry: sourceChat?.ownEntry || null,
|
|
28232
29213
|
membershipRemoved: sourceChat?.membershipRemoved === true,
|
|
@@ -28273,7 +29254,7 @@ function createChatDelete({
|
|
|
28273
29254
|
},
|
|
28274
29255
|
stateCapability: target.stateCapability,
|
|
28275
29256
|
transitionCommitment: target.transitionCommitment
|
|
28276
|
-
}, target.members, target.expectedEpochId);
|
|
29257
|
+
}, target.members, target.expectedEpochId, target.routes);
|
|
28277
29258
|
return result.deliveredCount;
|
|
28278
29259
|
};
|
|
28279
29260
|
const deleteCurrentChat = async (input, options = {}) => {
|
|
@@ -29404,6 +30385,188 @@ function createChatSessionSettings({
|
|
|
29404
30385
|
};
|
|
29405
30386
|
}
|
|
29406
30387
|
|
|
30388
|
+
// ../../core/chat/sessionnotifications.js
|
|
30389
|
+
var CHAT_ATTENTION_REFRESH_CONCURRENCY = 4;
|
|
30390
|
+
function currentNotificationRegistration(identity, entry, notificationMode = entry?.notificationMode) {
|
|
30391
|
+
const manifest = entry?.current?.manifest;
|
|
30392
|
+
if (!manifest || !identity?.chatPK)
|
|
30393
|
+
throw makeChatUnavailableError();
|
|
30394
|
+
const mode = normalizeChatNotificationMode(notificationMode, manifest);
|
|
30395
|
+
return {
|
|
30396
|
+
deliveryRegistration: chatDeliveryRegistration(entry.current.stateCapability, {
|
|
30397
|
+
attentionSecret: entry.current.epochSecret,
|
|
30398
|
+
chatId: manifest.chatId,
|
|
30399
|
+
recipientChatPK: identity.chatPK,
|
|
30400
|
+
generation: manifest.epochVersion,
|
|
30401
|
+
manifest,
|
|
30402
|
+
notificationMode: mode
|
|
30403
|
+
}),
|
|
30404
|
+
notificationMode: mode
|
|
30405
|
+
};
|
|
30406
|
+
}
|
|
30407
|
+
function entryWithCurrentNotificationRegistration(entry, notificationMode) {
|
|
30408
|
+
return {
|
|
30409
|
+
...entry,
|
|
30410
|
+
deliveryRegistered: true,
|
|
30411
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
30412
|
+
notificationMode
|
|
30413
|
+
};
|
|
30414
|
+
}
|
|
30415
|
+
async function setOwnChatNotificationMode(cloud, identity, entryId, entry, value) {
|
|
30416
|
+
if (!entryId || !entry?.current || entry.retirement)
|
|
30417
|
+
throw makeChatUnavailableError();
|
|
30418
|
+
return mutateOwnChatEntry(cloud, identity, entryId, entry, (current) => {
|
|
30419
|
+
if (!current?.current || current.retirement)
|
|
30420
|
+
throw makeChatUnavailableError();
|
|
30421
|
+
const manifest = current.current.manifest;
|
|
30422
|
+
if (manifest.lineage !== "group")
|
|
30423
|
+
throw new Error("group chat required");
|
|
30424
|
+
const requestedMode = cleanText(value);
|
|
30425
|
+
const notificationMode = normalizeChatNotificationMode(requestedMode, manifest);
|
|
30426
|
+
if (notificationMode !== requestedMode) {
|
|
30427
|
+
throw new Error("all notifications are unavailable in large groups");
|
|
30428
|
+
}
|
|
30429
|
+
if (current.notificationMode === notificationMode && hasCurrentChatAttentionRegistration(current)) {
|
|
30430
|
+
return { result: current };
|
|
30431
|
+
}
|
|
30432
|
+
const { deliveryRegistration } = currentNotificationRegistration(identity, current, notificationMode);
|
|
30433
|
+
return {
|
|
30434
|
+
entry: entryWithCurrentNotificationRegistration(current, notificationMode),
|
|
30435
|
+
deliveryRegistration
|
|
30436
|
+
};
|
|
30437
|
+
});
|
|
30438
|
+
}
|
|
30439
|
+
async function refreshOwnChatAttentionRegistration(cloud, identity, entryId, entry) {
|
|
30440
|
+
if (!entryId || !entry?.current || entry.retirement)
|
|
30441
|
+
throw makeChatUnavailableError();
|
|
30442
|
+
return mutateOwnChatEntry(cloud, identity, entryId, entry, (current) => {
|
|
30443
|
+
if (!current?.current || current.retirement)
|
|
30444
|
+
throw makeChatUnavailableError();
|
|
30445
|
+
if (hasCurrentChatAttentionRegistration(current))
|
|
30446
|
+
return { result: current };
|
|
30447
|
+
const { deliveryRegistration, notificationMode } = currentNotificationRegistration(identity, current);
|
|
30448
|
+
return {
|
|
30449
|
+
entry: entryWithCurrentNotificationRegistration(current, notificationMode),
|
|
30450
|
+
deliveryRegistration
|
|
30451
|
+
};
|
|
30452
|
+
});
|
|
30453
|
+
}
|
|
30454
|
+
function createChatSessionNotifications({
|
|
30455
|
+
cloud,
|
|
30456
|
+
getGeneration,
|
|
30457
|
+
getIdentity,
|
|
30458
|
+
getOwnerChat,
|
|
30459
|
+
getMutationGate,
|
|
30460
|
+
getChatListOwner,
|
|
30461
|
+
loadOwnerChats = async () => [],
|
|
30462
|
+
writeMode = setOwnChatNotificationMode,
|
|
30463
|
+
writeRegistration = refreshOwnChatAttentionRegistration
|
|
30464
|
+
}) {
|
|
30465
|
+
let refreshState = null;
|
|
30466
|
+
let refreshedGeneration = null;
|
|
30467
|
+
let refreshedListOwner = null;
|
|
30468
|
+
const operationContext = () => ({
|
|
30469
|
+
generation: getGeneration(),
|
|
30470
|
+
identity: getIdentity(),
|
|
30471
|
+
listOwner: getChatListOwner(),
|
|
30472
|
+
mutationGate: getMutationGate()
|
|
30473
|
+
});
|
|
30474
|
+
const assertCurrent = (operation) => {
|
|
30475
|
+
if (getGeneration() !== operation.generation || getChatListOwner() !== operation.listOwner) {
|
|
30476
|
+
throw makeChatUnavailableError();
|
|
30477
|
+
}
|
|
30478
|
+
};
|
|
30479
|
+
const requireOwnerChat = (chatId, operation, { group = false } = {}) => {
|
|
30480
|
+
assertCurrent(operation);
|
|
30481
|
+
const chat = getOwnerChat(chatId);
|
|
30482
|
+
if (operation.identity.chatBanned || group && chat?.lineage !== "group" || !chat.ownEntry || chat.ownEntry.retirement || !chat.entryId) {
|
|
30483
|
+
throw makeChatUnavailableError();
|
|
30484
|
+
}
|
|
30485
|
+
return chat;
|
|
30486
|
+
};
|
|
30487
|
+
const setChatNotificationMode = async (chatId, mode) => {
|
|
30488
|
+
const operation = operationContext();
|
|
30489
|
+
requireOwnerChat(chatId, operation, { group: true });
|
|
30490
|
+
const releaseMutation = await operation.mutationGate.acquireExclusive(chatId);
|
|
30491
|
+
try {
|
|
30492
|
+
const chat = requireOwnerChat(chatId, operation, { group: true });
|
|
30493
|
+
const committed = await writeMode(cloud, operation.identity, chat.entryId, chat.ownEntry, mode);
|
|
30494
|
+
assertCurrent(operation);
|
|
30495
|
+
const projected = projectOwnChatEntry(committed, chat.entryId, operation.identity.chatPK, chat.ts);
|
|
30496
|
+
operation.listOwner.commitOwner(projected);
|
|
30497
|
+
return projected.notificationMode;
|
|
30498
|
+
} finally {
|
|
30499
|
+
releaseMutation();
|
|
30500
|
+
}
|
|
30501
|
+
};
|
|
30502
|
+
const refreshLoadedChat = async (chat, operation) => {
|
|
30503
|
+
assertCurrent(operation);
|
|
30504
|
+
if (!chat?.id || !chat.entryId || !chat.ownEntry?.current || chat.ownEntry.retirement || hasCurrentChatAttentionRegistration(chat.ownEntry)) {
|
|
30505
|
+
return false;
|
|
30506
|
+
}
|
|
30507
|
+
const releaseMutation = await operation.mutationGate.acquireExclusive(chat.id);
|
|
30508
|
+
try {
|
|
30509
|
+
assertCurrent(operation);
|
|
30510
|
+
const committed = await writeRegistration(cloud, operation.identity, chat.entryId, chat.ownEntry);
|
|
30511
|
+
assertCurrent(operation);
|
|
30512
|
+
const projected = projectOwnChatEntry(committed, chat.entryId, operation.identity.chatPK, chat.ts);
|
|
30513
|
+
operation.listOwner.commitOwner(projected);
|
|
30514
|
+
return true;
|
|
30515
|
+
} finally {
|
|
30516
|
+
releaseMutation();
|
|
30517
|
+
}
|
|
30518
|
+
};
|
|
30519
|
+
const startAttentionRegistrationRefresh = (operation) => {
|
|
30520
|
+
const state = { operation, promise: null };
|
|
30521
|
+
state.promise = (async () => {
|
|
30522
|
+
const { chatBanned, chatPK, chatPrivateKey, uid } = operation.identity;
|
|
30523
|
+
if (chatBanned || !uid || !chatPK || !chatPrivateKey)
|
|
30524
|
+
throw makeChatUnavailableError();
|
|
30525
|
+
const chats = await loadOwnerChats(cloud, uid, chatPK, chatPrivateKey);
|
|
30526
|
+
assertCurrent(operation);
|
|
30527
|
+
const pending = (chats || []).filter((chat) => chat?.id && chat.entryId && chat.ownEntry?.current && !chat.ownEntry.retirement && !hasCurrentChatAttentionRegistration(chat.ownEntry));
|
|
30528
|
+
let cursor = 0;
|
|
30529
|
+
let refreshed = 0;
|
|
30530
|
+
const failures = [];
|
|
30531
|
+
const worker = async () => {
|
|
30532
|
+
while (cursor < pending.length) {
|
|
30533
|
+
const chat = pending[cursor];
|
|
30534
|
+
cursor += 1;
|
|
30535
|
+
try {
|
|
30536
|
+
if (await refreshLoadedChat(chat, operation))
|
|
30537
|
+
refreshed += 1;
|
|
30538
|
+
} catch (error) {
|
|
30539
|
+
failures.push(error);
|
|
30540
|
+
}
|
|
30541
|
+
}
|
|
30542
|
+
};
|
|
30543
|
+
await Promise.all(Array.from({ length: Math.min(CHAT_ATTENTION_REFRESH_CONCURRENCY, pending.length) }, worker));
|
|
30544
|
+
assertCurrent(operation);
|
|
30545
|
+
if (failures.length)
|
|
30546
|
+
throw failures[0];
|
|
30547
|
+
refreshedGeneration = operation.generation;
|
|
30548
|
+
refreshedListOwner = operation.listOwner;
|
|
30549
|
+
return refreshed;
|
|
30550
|
+
})().finally(() => {
|
|
30551
|
+
if (refreshState === state)
|
|
30552
|
+
refreshState = null;
|
|
30553
|
+
});
|
|
30554
|
+
refreshState = state;
|
|
30555
|
+
return state.promise;
|
|
30556
|
+
};
|
|
30557
|
+
const refreshChatAttentionRegistrations = () => {
|
|
30558
|
+
const operation = operationContext();
|
|
30559
|
+
if (refreshedGeneration === operation.generation && refreshedListOwner === operation.listOwner) {
|
|
30560
|
+
return Promise.resolve(0);
|
|
30561
|
+
}
|
|
30562
|
+
if (refreshState && refreshState.operation.generation === operation.generation && refreshState.operation.listOwner === operation.listOwner) {
|
|
30563
|
+
return refreshState.promise;
|
|
30564
|
+
}
|
|
30565
|
+
return startAttentionRegistrationRefresh(operation);
|
|
30566
|
+
};
|
|
30567
|
+
return { refreshChatAttentionRegistrations, setChatNotificationMode };
|
|
30568
|
+
}
|
|
30569
|
+
|
|
29407
30570
|
// ../../core/chat/sessionsources.js
|
|
29408
30571
|
function normalizeChatSessionSources(sources = {}) {
|
|
29409
30572
|
return {
|
|
@@ -29417,11 +30580,12 @@ function normalizeChatSessionSources(sources = {}) {
|
|
|
29417
30580
|
chatSigningSecret: sources.chatSigningSecret || null,
|
|
29418
30581
|
notificationPK: sources.notificationPK || "",
|
|
29419
30582
|
notificationPrivateKey: sources.notificationPrivateKey || "",
|
|
30583
|
+
chatAdmission: sources.chatAdmission || null,
|
|
29420
30584
|
localCache: sources.localCache || null
|
|
29421
30585
|
};
|
|
29422
30586
|
}
|
|
29423
30587
|
function chatSessionAuthChanged(previous, next) {
|
|
29424
|
-
return previous.uid !== next.uid || previous.chatPK !== next.chatPK || previous.chatBanned !== next.chatBanned || previous.chatPrivateKey !== next.chatPrivateKey || previous.chatSigningPK !== next.chatSigningPK || previous.chatSigningSecret !== next.chatSigningSecret || previous.notificationPK !== next.notificationPK || previous.notificationPrivateKey !== next.notificationPrivateKey || previous.localCache !== next.localCache;
|
|
30588
|
+
return previous.uid !== next.uid || previous.chatPK !== next.chatPK || previous.chatBanned !== next.chatBanned || previous.chatPrivateKey !== next.chatPrivateKey || previous.chatSigningPK !== next.chatSigningPK || previous.chatSigningSecret !== next.chatSigningSecret || previous.notificationPK !== next.notificationPK || previous.notificationPrivateKey !== next.notificationPrivateKey || previous.chatAdmission !== next.chatAdmission || previous.localCache !== next.localCache;
|
|
29425
30589
|
}
|
|
29426
30590
|
|
|
29427
30591
|
// ../../core/chat/session.js
|
|
@@ -29451,15 +30615,21 @@ function createChatSession({
|
|
|
29451
30615
|
chatWarming = false,
|
|
29452
30616
|
preloadMessageMedia,
|
|
29453
30617
|
adoptLocalMessageMedia,
|
|
30618
|
+
refreshAttentionRegistrationsOnStart = false,
|
|
29454
30619
|
chatCrypto = null,
|
|
29455
30620
|
mls = null,
|
|
29456
30621
|
live = null,
|
|
30622
|
+
maintenance,
|
|
30623
|
+
incomingChatDecision = null,
|
|
29457
30624
|
diag = null,
|
|
29458
30625
|
loadOwnerChats = loadAllChats
|
|
29459
30626
|
}, initialSources = {}) {
|
|
29460
30627
|
if (!cloud) {
|
|
29461
30628
|
throw new Error("createChatSession requires cloud");
|
|
29462
30629
|
}
|
|
30630
|
+
if (!maintenance?.claim || !maintenance?.open || !maintenance?.close) {
|
|
30631
|
+
throw new Error("createChatSession requires account message maintenance");
|
|
30632
|
+
}
|
|
29463
30633
|
let {
|
|
29464
30634
|
uid = "",
|
|
29465
30635
|
blocked = [],
|
|
@@ -29471,6 +30641,7 @@ function createChatSession({
|
|
|
29471
30641
|
chatPrivateKey = "",
|
|
29472
30642
|
notificationPK = "",
|
|
29473
30643
|
notificationPrivateKey = "",
|
|
30644
|
+
chatAdmission = null,
|
|
29474
30645
|
localCache = null
|
|
29475
30646
|
} = initialSources;
|
|
29476
30647
|
let isActive = isForegroundAppState(appState?.currentState);
|
|
@@ -29483,6 +30654,9 @@ function createChatSession({
|
|
|
29483
30654
|
let mlsPoolLastCheckedAt = 0;
|
|
29484
30655
|
let mlsPoolGeneration = 0;
|
|
29485
30656
|
let authGeneration = 0;
|
|
30657
|
+
let attentionRegistrationPreparation = null;
|
|
30658
|
+
let attentionRegistrationReadyGeneration = null;
|
|
30659
|
+
let attentionRegistrationReadyOwner = null;
|
|
29486
30660
|
const inboxSource = () => ({
|
|
29487
30661
|
uid,
|
|
29488
30662
|
chatPK,
|
|
@@ -29514,6 +30688,7 @@ function createChatSession({
|
|
|
29514
30688
|
let pendingOwner = null;
|
|
29515
30689
|
let membershipOwner = null;
|
|
29516
30690
|
let settingsOwner = null;
|
|
30691
|
+
let notificationsOwner = null;
|
|
29517
30692
|
let chatListOwner = null;
|
|
29518
30693
|
let unsubscribeChatList = null;
|
|
29519
30694
|
let selectionIntent = 0;
|
|
@@ -29696,11 +30871,48 @@ function createChatSession({
|
|
|
29696
30871
|
diag
|
|
29697
30872
|
});
|
|
29698
30873
|
const drainMembershipOutbox = () => {
|
|
29699
|
-
if (!uid || !chatPK || !chatPrivateKey || !chatSigningPK || !chatSigningSecret)
|
|
29700
|
-
return;
|
|
29701
|
-
|
|
30874
|
+
if (!uid || !chatPK || !chatPrivateKey || !chatSigningPK || !chatSigningSecret) {
|
|
30875
|
+
return Promise.resolve(0);
|
|
30876
|
+
}
|
|
30877
|
+
return drainChatMembershipOutbox(cloud, transitionIdentity()).catch((error) => {
|
|
29702
30878
|
markDiag(diag, "chat.membership.outbox.failed", { message: error?.message || String(error) });
|
|
30879
|
+
return 0;
|
|
30880
|
+
});
|
|
30881
|
+
};
|
|
30882
|
+
const refreshAttentionRegistrations = () => {
|
|
30883
|
+
if (!refreshAttentionRegistrationsOnStart || !isActive || chatBanned || !notificationsOwner)
|
|
30884
|
+
return null;
|
|
30885
|
+
const generation = authGeneration;
|
|
30886
|
+
const owner = notificationsOwner;
|
|
30887
|
+
if (attentionRegistrationReadyGeneration === generation && attentionRegistrationReadyOwner === owner) {
|
|
30888
|
+
return Promise.resolve(0);
|
|
30889
|
+
}
|
|
30890
|
+
if (attentionRegistrationPreparation?.generation === generation && attentionRegistrationPreparation.owner === owner) {
|
|
30891
|
+
return attentionRegistrationPreparation.promise;
|
|
30892
|
+
}
|
|
30893
|
+
const state = { generation, owner, promise: null };
|
|
30894
|
+
state.promise = owner.refreshChatAttentionRegistrations().then((refreshed) => {
|
|
30895
|
+
if (authGeneration !== generation || notificationsOwner !== owner || !isActive)
|
|
30896
|
+
return 0;
|
|
30897
|
+
attentionRegistrationReadyGeneration = generation;
|
|
30898
|
+
attentionRegistrationReadyOwner = owner;
|
|
30899
|
+
if (refreshed > 0) {
|
|
30900
|
+
markDiag(diag, "chat.notifications.routes.refreshed", { count: refreshed });
|
|
30901
|
+
}
|
|
30902
|
+
return refreshed;
|
|
30903
|
+
}).catch((error) => {
|
|
30904
|
+
if (authGeneration === generation && notificationsOwner === owner) {
|
|
30905
|
+
markDiag(diag, "chat.notifications.routes.failed", {
|
|
30906
|
+
message: error?.message || String(error)
|
|
30907
|
+
});
|
|
30908
|
+
}
|
|
30909
|
+
return 0;
|
|
30910
|
+
}).finally(() => {
|
|
30911
|
+
if (attentionRegistrationPreparation === state)
|
|
30912
|
+
attentionRegistrationPreparation = null;
|
|
29703
30913
|
});
|
|
30914
|
+
attentionRegistrationPreparation = state;
|
|
30915
|
+
return state.promise;
|
|
29704
30916
|
};
|
|
29705
30917
|
const replenishMlsPool = ({ force = false } = {}) => {
|
|
29706
30918
|
if (!uid || !chatPK || !chatPrivateKey || !chatSigningPK || !chatSigningSecret)
|
|
@@ -29779,6 +30991,20 @@ function createChatSession({
|
|
|
29779
30991
|
getChatListOwner: () => chatListOwner,
|
|
29780
30992
|
setLocalByChat
|
|
29781
30993
|
});
|
|
30994
|
+
notificationsOwner = createChatSessionNotifications({
|
|
30995
|
+
cloud,
|
|
30996
|
+
getGeneration: () => authGeneration,
|
|
30997
|
+
getIdentity: () => ({
|
|
30998
|
+
uid,
|
|
30999
|
+
chatPK,
|
|
31000
|
+
chatPrivateKey,
|
|
31001
|
+
chatBanned
|
|
31002
|
+
}),
|
|
31003
|
+
getOwnerChat,
|
|
31004
|
+
getMutationGate: () => actionOwner.mutationGate,
|
|
31005
|
+
getChatListOwner: () => chatListOwner,
|
|
31006
|
+
loadOwnerChats
|
|
31007
|
+
});
|
|
29782
31008
|
pendingOwner = createChatSessionPending({
|
|
29783
31009
|
beginSelection: beginChatSelection,
|
|
29784
31010
|
getIdentity: () => ({ uid, blocked, chatPK, chatPrivateKey, chatBanned }),
|
|
@@ -29786,6 +31012,7 @@ function createChatSession({
|
|
|
29786
31012
|
setSelectedChat,
|
|
29787
31013
|
publish,
|
|
29788
31014
|
getOwnerChat,
|
|
31015
|
+
getChatRow: (chatId) => chatListOwner.getRow(chatId),
|
|
29789
31016
|
ensureChat,
|
|
29790
31017
|
profileMember: membershipOwner.profileMember,
|
|
29791
31018
|
transitionIdentity,
|
|
@@ -29817,12 +31044,16 @@ function createChatSession({
|
|
|
29817
31044
|
const selectChat = (chatId) => pendingOwner.selectChat(chatId, {
|
|
29818
31045
|
flushPrevious: (previousChatId) => actionOwner.seenActions.flushChatRead(previousChatId)
|
|
29819
31046
|
});
|
|
31047
|
+
const resolveChatRequest = (chatId, decision) => chatListOwner.resolveChatRequest(chatId, decision);
|
|
29820
31048
|
const getPeerChatId = (...args) => launchOwner.getPeerChatId(...args);
|
|
29821
31049
|
const resolvePeerChatId = (...args) => launchOwner.resolvePeerChatId(...args);
|
|
29822
31050
|
const selectPeerChat = (...args) => launchOwner.selectPeerChat(...args);
|
|
29823
31051
|
const openDirectChat = (...args) => launchOwner.openDirectChat(...args);
|
|
29824
31052
|
const openNotesChat = (...args) => launchOwner.openNotesChat(...args);
|
|
29825
31053
|
const openNewChat = (...args) => pendingOwner.openNewChat(...args);
|
|
31054
|
+
const directAdmissionForPeer2 = (profile) => directAdmissionForPeer(chatPrivateKey, chatPK, profile);
|
|
31055
|
+
const canStartDirectChat2 = (profile) => canStartDirectChat(chatPrivateKey, chatPK, profile);
|
|
31056
|
+
const canInviteToGroup2 = (profile) => canInviteToGroup(profile);
|
|
29826
31057
|
const createGroupChat2 = (...args) => launchOwner.createGroupChat(...args);
|
|
29827
31058
|
const selectLocalChat = (...args) => launchOwner.selectLocalChat(...args);
|
|
29828
31059
|
const addChatMembers = (chatId, profiles) => {
|
|
@@ -29840,6 +31071,7 @@ function createChatSession({
|
|
|
29840
31071
|
const leaveOwnedChat = (...args) => membershipOwner.leaveOwnedChat(...args);
|
|
29841
31072
|
const updateChatSettings2 = gateChatMutation((...args) => settingsOwner.updateChatSettings(...args));
|
|
29842
31073
|
const updateChatAvatar = gateChatMutation((...args) => settingsOwner.updateChatAvatar(...args));
|
|
31074
|
+
const setChatNotificationMode = gateChatMutation((...args) => notificationsOwner.setChatNotificationMode(...args));
|
|
29843
31075
|
const updateMessage = (chatId, msgId, newMessage, peerChatPK) => {
|
|
29844
31076
|
if (chatBanned)
|
|
29845
31077
|
throw makeChatUnavailableError();
|
|
@@ -29962,6 +31194,8 @@ function createChatSession({
|
|
|
29962
31194
|
notificationPrivateKey: identity.notificationPrivateKey,
|
|
29963
31195
|
mls,
|
|
29964
31196
|
blockedUids: new Set(identity.blocked),
|
|
31197
|
+
chatAdmission,
|
|
31198
|
+
incomingChatDecision,
|
|
29965
31199
|
scanAllSlots: true,
|
|
29966
31200
|
requireComplete: true
|
|
29967
31201
|
});
|
|
@@ -30165,7 +31399,8 @@ function createChatSession({
|
|
|
30165
31399
|
getChatPreviewKey: getChatPreviewKey2,
|
|
30166
31400
|
writeChatPreview,
|
|
30167
31401
|
chatCrypto,
|
|
30168
|
-
diag
|
|
31402
|
+
diag,
|
|
31403
|
+
maintenance
|
|
30169
31404
|
};
|
|
30170
31405
|
const batchSources = () => ({
|
|
30171
31406
|
cloud,
|
|
@@ -30179,6 +31414,7 @@ function createChatSession({
|
|
|
30179
31414
|
chatBanned,
|
|
30180
31415
|
isActive,
|
|
30181
31416
|
localCache,
|
|
31417
|
+
maintenance,
|
|
30182
31418
|
listRef: lastServerChatsRef,
|
|
30183
31419
|
pendingDeleteIdsRef: actionOwner.deleteActions.pendingDeleteIdsRef,
|
|
30184
31420
|
config: chatWarming,
|
|
@@ -30205,6 +31441,8 @@ function createChatSession({
|
|
|
30205
31441
|
localCache,
|
|
30206
31442
|
isActive,
|
|
30207
31443
|
inboxTransport,
|
|
31444
|
+
chatAdmission,
|
|
31445
|
+
incomingChatDecision,
|
|
30208
31446
|
diag,
|
|
30209
31447
|
selectedChatId: stateOwner.getSelectedChatId(),
|
|
30210
31448
|
selectedChatIdRef,
|
|
@@ -30260,18 +31498,23 @@ function createChatSession({
|
|
|
30260
31498
|
stateOwner.setActions({
|
|
30261
31499
|
loadMoreChats: loadMoreChats2,
|
|
30262
31500
|
selectChat,
|
|
31501
|
+
resolveChatRequest,
|
|
30263
31502
|
getPeerChatId,
|
|
30264
31503
|
resolvePeerChatId,
|
|
30265
31504
|
selectPeerChat,
|
|
30266
31505
|
openDirectChat,
|
|
30267
31506
|
openNotesChat,
|
|
30268
31507
|
openNewChat,
|
|
31508
|
+
directAdmissionForPeer: directAdmissionForPeer2,
|
|
31509
|
+
canStartDirectChat: canStartDirectChat2,
|
|
31510
|
+
canInviteToGroup: canInviteToGroup2,
|
|
30269
31511
|
createGroupChat: createGroupChat2,
|
|
30270
31512
|
addChatMembers,
|
|
30271
31513
|
kickChatMember,
|
|
30272
31514
|
leaveChat,
|
|
30273
31515
|
updateChatSettings: updateChatSettings2,
|
|
30274
31516
|
updateChatAvatar,
|
|
31517
|
+
setChatNotificationMode,
|
|
30275
31518
|
dropChat,
|
|
30276
31519
|
dropUnavailableChat,
|
|
30277
31520
|
deleteChat,
|
|
@@ -30393,6 +31636,7 @@ function createChatSession({
|
|
|
30393
31636
|
if (value) {
|
|
30394
31637
|
if (selectedChatIdRef.current)
|
|
30395
31638
|
enterChatActivity(selectedChatIdRef.current);
|
|
31639
|
+
refreshAttentionRegistrations();
|
|
30396
31640
|
drainMembershipOutbox();
|
|
30397
31641
|
replenishMlsPool();
|
|
30398
31642
|
}
|
|
@@ -30406,7 +31650,7 @@ function createChatSession({
|
|
|
30406
31650
|
};
|
|
30407
31651
|
const setSources = (nextSources = {}) => {
|
|
30408
31652
|
const previousBlockedKey = [...blocked].sort().join("|");
|
|
30409
|
-
const current = { uid, chatPK, chatBanned, chatPrivateKey, chatSigningPK, chatSigningSecret, notificationPK, notificationPrivateKey, localCache };
|
|
31653
|
+
const current = { uid, chatPK, chatBanned, chatPrivateKey, chatSigningPK, chatSigningSecret, notificationPK, notificationPrivateKey, chatAdmission, localCache };
|
|
30410
31654
|
const next = normalizeChatSessionSources(nextSources);
|
|
30411
31655
|
const authChanged = chatSessionAuthChanged(current, next);
|
|
30412
31656
|
if (authChanged) {
|
|
@@ -30424,6 +31668,7 @@ function createChatSession({
|
|
|
30424
31668
|
chatSigningSecret,
|
|
30425
31669
|
notificationPK,
|
|
30426
31670
|
notificationPrivateKey,
|
|
31671
|
+
chatAdmission,
|
|
30427
31672
|
localCache
|
|
30428
31673
|
} = next);
|
|
30429
31674
|
inboxTransport.setSource(inboxSource());
|
|
@@ -30441,6 +31686,7 @@ function createChatSession({
|
|
|
30441
31686
|
}
|
|
30442
31687
|
publish();
|
|
30443
31688
|
if (authChanged && isActive) {
|
|
31689
|
+
refreshAttentionRegistrations();
|
|
30444
31690
|
drainMembershipOutbox();
|
|
30445
31691
|
replenishMlsPool({ force: true });
|
|
30446
31692
|
}
|
|
@@ -30454,10 +31700,13 @@ function createChatSession({
|
|
|
30454
31700
|
const start = () => {
|
|
30455
31701
|
if (started)
|
|
30456
31702
|
return;
|
|
31703
|
+
maintenance.open();
|
|
30457
31704
|
started = true;
|
|
30458
31705
|
unsubscribeChatList = chatListOwner.subscribe(() => {
|
|
30459
31706
|
stateOwner.setListSnapshot(chatListOwner.getSnapshot());
|
|
30460
31707
|
publish();
|
|
31708
|
+
if (serverChatsReadyRef.current)
|
|
31709
|
+
refreshAttentionRegistrations();
|
|
30461
31710
|
});
|
|
30462
31711
|
stateOwner.setListSnapshot(chatListOwner.getSnapshot());
|
|
30463
31712
|
publish();
|
|
@@ -30468,6 +31717,7 @@ function createChatSession({
|
|
|
30468
31717
|
markDiag(diag, "chat.block.retire.failed", { message: error?.message || String(error) });
|
|
30469
31718
|
});
|
|
30470
31719
|
}
|
|
31720
|
+
refreshAttentionRegistrations();
|
|
30471
31721
|
drainMembershipOutbox();
|
|
30472
31722
|
replenishMlsPool({ force: true });
|
|
30473
31723
|
if (appState?.addEventListener) {
|
|
@@ -30476,6 +31726,7 @@ function createChatSession({
|
|
|
30476
31726
|
};
|
|
30477
31727
|
const close = () => {
|
|
30478
31728
|
authGeneration += 1;
|
|
31729
|
+
maintenance.close();
|
|
30479
31730
|
settingsOwner.reset();
|
|
30480
31731
|
mls?.close?.();
|
|
30481
31732
|
if (!started)
|
|
@@ -33228,18 +34479,261 @@ function isPasswordStrengthAcceptable(feedback) {
|
|
|
33228
34479
|
return feedback?.version === PASSWORD_STRENGTH_VERSION && feedback.acceptable === true;
|
|
33229
34480
|
}
|
|
33230
34481
|
|
|
33231
|
-
// ../../core/
|
|
33232
|
-
var
|
|
33233
|
-
var
|
|
33234
|
-
|
|
33235
|
-
|
|
33236
|
-
|
|
34482
|
+
// ../../core/wallet/invoice.js
|
|
34483
|
+
var import_bech322 = __toESM(require_dist(), 1);
|
|
34484
|
+
var externalInvoiceType = Object.freeze({
|
|
34485
|
+
lightning: "lightning",
|
|
34486
|
+
spark: "spark"
|
|
34487
|
+
});
|
|
34488
|
+
function lightningValue(value) {
|
|
34489
|
+
const raw = cleanText(typeof value === "string" ? value : value?.invoice ?? value?.encodedInvoice ?? value?.bolt11);
|
|
34490
|
+
const lower = lowerText(raw);
|
|
34491
|
+
if (!lower)
|
|
34492
|
+
return null;
|
|
34493
|
+
const invoice = lower.startsWith("lightning://") ? raw.slice(12).trim() : lower.startsWith("lightning:") ? raw.slice(10).trim() : raw;
|
|
34494
|
+
return /^ln[a-z0-9]+$/.test(lowerText(invoice)) ? invoice : null;
|
|
33237
34495
|
}
|
|
33238
|
-
function
|
|
33239
|
-
return
|
|
34496
|
+
function ceilDiv(value, divisor) {
|
|
34497
|
+
return (value + divisor - 1n) / divisor;
|
|
33240
34498
|
}
|
|
33241
|
-
function
|
|
33242
|
-
|
|
34499
|
+
function wordsValue(words) {
|
|
34500
|
+
let value = 0;
|
|
34501
|
+
for (const word of words) {
|
|
34502
|
+
value = value * 32 + word;
|
|
34503
|
+
if (!Number.isSafeInteger(value))
|
|
34504
|
+
return null;
|
|
34505
|
+
}
|
|
34506
|
+
return value;
|
|
34507
|
+
}
|
|
34508
|
+
function lightningFields(value) {
|
|
34509
|
+
const invoice = lightningValue(value);
|
|
34510
|
+
if (!invoice)
|
|
34511
|
+
return null;
|
|
34512
|
+
try {
|
|
34513
|
+
const decoded = import_bech322.bech32.decode(lowerText(invoice), 5000);
|
|
34514
|
+
if (decoded.words.length < 7 + 104)
|
|
34515
|
+
return null;
|
|
34516
|
+
const timestampSeconds = wordsValue(decoded.words.slice(0, 7));
|
|
34517
|
+
if (timestampSeconds == null)
|
|
34518
|
+
return null;
|
|
34519
|
+
return {
|
|
34520
|
+
invoice,
|
|
34521
|
+
timestampSeconds,
|
|
34522
|
+
taggedFields: decoded.words.slice(7, -104)
|
|
34523
|
+
};
|
|
34524
|
+
} catch {
|
|
34525
|
+
return null;
|
|
34526
|
+
}
|
|
34527
|
+
}
|
|
34528
|
+
function readTaggedField(words, expectedTag) {
|
|
34529
|
+
let offset = 0;
|
|
34530
|
+
while (offset + 3 <= words.length) {
|
|
34531
|
+
const tag = words[offset];
|
|
34532
|
+
const length = words[offset + 1] * 32 + words[offset + 2];
|
|
34533
|
+
const start = offset + 3;
|
|
34534
|
+
const end = start + length;
|
|
34535
|
+
if (end > words.length)
|
|
34536
|
+
return null;
|
|
34537
|
+
if (tag === expectedTag)
|
|
34538
|
+
return words.slice(start, end);
|
|
34539
|
+
offset = end;
|
|
34540
|
+
}
|
|
34541
|
+
return;
|
|
34542
|
+
}
|
|
34543
|
+
function getLightningInvoiceAmountMsats(invoice) {
|
|
34544
|
+
const value = lowerText(invoice);
|
|
34545
|
+
const match = value.match(/^ln(?:bcrt|bc|tb|sb|tbs)(\d+[munp]?)?1/);
|
|
34546
|
+
const amount = match?.[1];
|
|
34547
|
+
if (!amount)
|
|
34548
|
+
return null;
|
|
34549
|
+
const unit = /[munp]$/.test(amount) ? amount.slice(-1) : "";
|
|
34550
|
+
const raw = unit ? amount.slice(0, -1) : amount;
|
|
34551
|
+
if (!/^\d+$/.test(raw))
|
|
34552
|
+
return null;
|
|
34553
|
+
const n = BigInt(raw);
|
|
34554
|
+
switch (unit) {
|
|
34555
|
+
case "m":
|
|
34556
|
+
return n * 100000000n;
|
|
34557
|
+
case "u":
|
|
34558
|
+
return n * 100000n;
|
|
34559
|
+
case "n":
|
|
34560
|
+
return n * 100n;
|
|
34561
|
+
case "p":
|
|
34562
|
+
return n % 10n === 0n ? n / 10n : null;
|
|
34563
|
+
default:
|
|
34564
|
+
return n * 100000000000n;
|
|
34565
|
+
}
|
|
34566
|
+
}
|
|
34567
|
+
function lightningAmountSats(invoice) {
|
|
34568
|
+
const amountMsats = getLightningInvoiceAmountMsats(invoice);
|
|
34569
|
+
return amountMsats == null ? null : ceilDiv(amountMsats, 1000n);
|
|
34570
|
+
}
|
|
34571
|
+
function getLightningInvoiceExpiresAtMs(value) {
|
|
34572
|
+
const fields = lightningFields(value);
|
|
34573
|
+
if (!fields)
|
|
34574
|
+
return null;
|
|
34575
|
+
const expiryWords = readTaggedField(fields.taggedFields, 6);
|
|
34576
|
+
if (expiryWords === null)
|
|
34577
|
+
return null;
|
|
34578
|
+
const expirySeconds = expiryWords === undefined ? 3600 : wordsValue(expiryWords);
|
|
34579
|
+
if (expirySeconds == null)
|
|
34580
|
+
return null;
|
|
34581
|
+
const expiresAtMs = (fields.timestampSeconds + expirySeconds) * 1000;
|
|
34582
|
+
return Number.isSafeInteger(expiresAtMs) ? expiresAtMs : null;
|
|
34583
|
+
}
|
|
34584
|
+
function readLightningInvoice(value) {
|
|
34585
|
+
const invoice = lightningValue(value);
|
|
34586
|
+
if (!invoice)
|
|
34587
|
+
return null;
|
|
34588
|
+
const amountSats = lightningAmountSats(invoice);
|
|
34589
|
+
return {
|
|
34590
|
+
type: externalInvoiceType.lightning,
|
|
34591
|
+
invoice,
|
|
34592
|
+
...amountSats != null && amountSats > 0n ? { amount: amountSats.toString() } : {}
|
|
34593
|
+
};
|
|
34594
|
+
}
|
|
34595
|
+
function getLightningInvoiceNetwork(value) {
|
|
34596
|
+
const lower = lowerText(readLightningInvoice(value)?.invoice);
|
|
34597
|
+
if (!lower)
|
|
34598
|
+
return null;
|
|
34599
|
+
if (lower.startsWith("lnbcrt"))
|
|
34600
|
+
return "REGTEST";
|
|
34601
|
+
if (lower.startsWith("lntbs") || lower.startsWith("lnsb"))
|
|
34602
|
+
return "SIGNET";
|
|
34603
|
+
if (lower.startsWith("lntb"))
|
|
34604
|
+
return "TESTNET";
|
|
34605
|
+
if (lower.startsWith("lnbc"))
|
|
34606
|
+
return "MAINNET";
|
|
34607
|
+
return null;
|
|
34608
|
+
}
|
|
34609
|
+
|
|
34610
|
+
// ../../core/paymentrequest.js
|
|
34611
|
+
"use client";
|
|
34612
|
+
var PUBLIC_PAYMENT_REQUEST_VERSION = "1";
|
|
34613
|
+
var PUBLIC_PAYMENT_REQUEST_PATH = "/pay/";
|
|
34614
|
+
var PUBLIC_PAYMENT_REQUEST_EXPIRY_SECONDS = 24 * 60 * 60;
|
|
34615
|
+
var PUBLIC_PAYMENT_REQUEST_MAX_EXPIRY_SECONDS = 7 * 24 * 60 * 60;
|
|
34616
|
+
var REQUEST_ID_RE = /^[0-9a-f]{64}$/u;
|
|
34617
|
+
var UID_RE = /^[A-Za-z0-9_-]{1,128}$/u;
|
|
34618
|
+
var WALLET_PK_RE = /^(02|03)[0-9a-f]{64}$/u;
|
|
34619
|
+
var SIGNING_PK_RE = /^[0-9a-f]{64}$/u;
|
|
34620
|
+
var SIGNATURE_RE2 = /^[0-9a-f]{128}$/u;
|
|
34621
|
+
var INTEGER_RE = /^[1-9][0-9]*$/u;
|
|
34622
|
+
var ALLOWED_HOSTS = new Set([
|
|
34623
|
+
new URL(origins.veyl).hostname,
|
|
34624
|
+
new URL(origins.veylDev).hostname
|
|
34625
|
+
]);
|
|
34626
|
+
function cleanString(value) {
|
|
34627
|
+
return typeof value === "string" ? value.trim() : "";
|
|
34628
|
+
}
|
|
34629
|
+
function cleanHex(value) {
|
|
34630
|
+
return cleanString(value).toLowerCase();
|
|
34631
|
+
}
|
|
34632
|
+
function cleanIntegerString(value) {
|
|
34633
|
+
const text = cleanString(typeof value === "bigint" || typeof value === "number" ? String(value) : value);
|
|
34634
|
+
if (!INTEGER_RE.test(text))
|
|
34635
|
+
return null;
|
|
34636
|
+
const number = Number(text);
|
|
34637
|
+
return Number.isSafeInteger(number) && number > 0 ? text : null;
|
|
34638
|
+
}
|
|
34639
|
+
function cleanRequestData(value = {}) {
|
|
34640
|
+
const v = cleanString(value.v);
|
|
34641
|
+
const id = cleanHex(value.id);
|
|
34642
|
+
const uid = cleanString(value.uid);
|
|
34643
|
+
const username = normalizeUsername(value.username);
|
|
34644
|
+
const walletPK = cleanHex(value.walletPK);
|
|
34645
|
+
const signingPK = cleanHex(value.signingPK);
|
|
34646
|
+
const amount = cleanIntegerString(value.amount);
|
|
34647
|
+
const invoice = readLightningInvoice(value.invoice)?.invoice?.toLowerCase() || "";
|
|
34648
|
+
const expiresAt = cleanIntegerString(value.expiresAt);
|
|
34649
|
+
let network;
|
|
34650
|
+
try {
|
|
34651
|
+
network = normalizeWalletNetwork(value.network);
|
|
34652
|
+
} catch {
|
|
34653
|
+
return null;
|
|
34654
|
+
}
|
|
34655
|
+
if (v !== PUBLIC_PAYMENT_REQUEST_VERSION || !REQUEST_ID_RE.test(id) || !UID_RE.test(uid) || !isUsername(username) || !WALLET_PK_RE.test(walletPK) || !SIGNING_PK_RE.test(signingPK) || !amount || !invoice || !expiresAt || getLightningInvoiceNetwork(invoice) !== network || readLightningInvoice(invoice)?.amount !== amount || String(Math.floor((getLightningInvoiceExpiresAtMs(invoice) || 0) / 1000)) !== expiresAt) {
|
|
34656
|
+
return null;
|
|
34657
|
+
}
|
|
34658
|
+
return {
|
|
34659
|
+
v,
|
|
34660
|
+
id,
|
|
34661
|
+
uid,
|
|
34662
|
+
username,
|
|
34663
|
+
walletPK,
|
|
34664
|
+
signingPK,
|
|
34665
|
+
network,
|
|
34666
|
+
amount,
|
|
34667
|
+
invoice,
|
|
34668
|
+
expiresAt
|
|
34669
|
+
};
|
|
34670
|
+
}
|
|
34671
|
+
function publicPaymentRequestSigningBytes(value) {
|
|
34672
|
+
const data = cleanRequestData(value);
|
|
34673
|
+
if (!data)
|
|
34674
|
+
throw new Error("invalid public payment request");
|
|
34675
|
+
return encoder.encode([
|
|
34676
|
+
"veyl-public-payment-request-v1",
|
|
34677
|
+
data.id,
|
|
34678
|
+
data.uid,
|
|
34679
|
+
data.username,
|
|
34680
|
+
data.walletPK,
|
|
34681
|
+
data.signingPK,
|
|
34682
|
+
data.network,
|
|
34683
|
+
data.amount,
|
|
34684
|
+
data.invoice,
|
|
34685
|
+
data.expiresAt
|
|
34686
|
+
].join(`
|
|
34687
|
+
`));
|
|
34688
|
+
}
|
|
34689
|
+
function createPublicPaymentRequest(value = {}) {
|
|
34690
|
+
const signingKey = value.signingKey;
|
|
34691
|
+
const signingPK = cleanHex(signingKey?.publicKey);
|
|
34692
|
+
const invoiceExpiryMs = getLightningInvoiceExpiresAtMs(value.invoice);
|
|
34693
|
+
const data = cleanRequestData({
|
|
34694
|
+
v: PUBLIC_PAYMENT_REQUEST_VERSION,
|
|
34695
|
+
id: value.id || toHex(randomBytes3(32)),
|
|
34696
|
+
uid: value.uid,
|
|
34697
|
+
username: value.username,
|
|
34698
|
+
walletPK: value.walletPK,
|
|
34699
|
+
signingPK,
|
|
34700
|
+
network: value.network,
|
|
34701
|
+
amount: value.amountSats,
|
|
34702
|
+
invoice: value.invoice,
|
|
34703
|
+
expiresAt: invoiceExpiryMs ? String(Math.floor(invoiceExpiryMs / 1000)) : ""
|
|
34704
|
+
});
|
|
34705
|
+
if (!data)
|
|
34706
|
+
throw new Error("invalid public payment request");
|
|
34707
|
+
if (Number(data.expiresAt) * 1000 <= Number(value.nowMs ?? Date.now())) {
|
|
34708
|
+
throw new Error("public payment request invoice expired");
|
|
34709
|
+
}
|
|
34710
|
+
return Object.freeze({
|
|
34711
|
+
...data,
|
|
34712
|
+
sig: signChatBytes(signingKey, publicPaymentRequestSigningBytes(data))
|
|
34713
|
+
});
|
|
34714
|
+
}
|
|
34715
|
+
function verifyPublicPaymentRequest(value) {
|
|
34716
|
+
const data = cleanRequestData(value);
|
|
34717
|
+
const sig = cleanHex(value?.sig);
|
|
34718
|
+
if (!data || !SIGNATURE_RE2.test(sig))
|
|
34719
|
+
return null;
|
|
34720
|
+
if (!verifyChatBytes(data.signingPK, sig, publicPaymentRequestSigningBytes(data)))
|
|
34721
|
+
return null;
|
|
34722
|
+
return Object.freeze({ ...data, sig });
|
|
34723
|
+
}
|
|
34724
|
+
function encodePublicPaymentRequest(value) {
|
|
34725
|
+
const request = verifyPublicPaymentRequest(value);
|
|
34726
|
+
if (!request)
|
|
34727
|
+
throw new Error("verified public payment request required");
|
|
34728
|
+
return bytesBase64Url(encoder.encode(JSON.stringify(request)));
|
|
34729
|
+
}
|
|
34730
|
+
function makePublicPaymentLink(value, options = {}) {
|
|
34731
|
+
const origin = cleanString(options.origin) || origins.veyl;
|
|
34732
|
+
const parsedOrigin = new URL(origin);
|
|
34733
|
+
if (parsedOrigin.protocol !== "https:" || !ALLOWED_HOSTS.has(parsedOrigin.hostname)) {
|
|
34734
|
+
throw new Error("invalid public payment origin");
|
|
34735
|
+
}
|
|
34736
|
+
return `${parsedOrigin.origin}${PUBLIC_PAYMENT_REQUEST_PATH}${encodePublicPaymentRequest(value)}`;
|
|
33243
34737
|
}
|
|
33244
34738
|
|
|
33245
34739
|
// ../../core/wallet/bitcoin.js
|
|
@@ -34152,7 +35646,7 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
|
|
|
34152
35646
|
if (!uid || expectedVersion == null || typeof avatarCache?.read !== "function")
|
|
34153
35647
|
return null;
|
|
34154
35648
|
try {
|
|
34155
|
-
const cached = await avatarCache.read(uid);
|
|
35649
|
+
const cached = await avatarCache.read(uid, expectedVersion);
|
|
34156
35650
|
const version = readAvatarVersion(cached?.version);
|
|
34157
35651
|
const url = typeof cached?.url === "string" && cached.url ? cached.url : typeof cached?.source === "string" && cached.source ? cached.source : null;
|
|
34158
35652
|
return version === expectedVersion && url ? url : null;
|
|
@@ -41653,8 +43147,6 @@ function requirePorts(options) {
|
|
|
41653
43147
|
throw new Error("account cloud port required");
|
|
41654
43148
|
if (!options?.defaultNetwork)
|
|
41655
43149
|
throw new Error("account default network required");
|
|
41656
|
-
if (typeof options?.setNetwork !== "function")
|
|
41657
|
-
throw new Error("account network port required");
|
|
41658
43150
|
if (!options?.vaultCrypto)
|
|
41659
43151
|
throw new Error("account vault crypto port required");
|
|
41660
43152
|
if (typeof options?.bootWallet !== "function")
|
|
@@ -41690,8 +43182,7 @@ function openAccount(options = {}) {
|
|
|
41690
43182
|
vaultCrypto,
|
|
41691
43183
|
bootWallet,
|
|
41692
43184
|
bootChat,
|
|
41693
|
-
openLocalCache
|
|
41694
|
-
setNetwork: writeNetwork
|
|
43185
|
+
openLocalCache
|
|
41695
43186
|
} = ports;
|
|
41696
43187
|
let closed = false;
|
|
41697
43188
|
let attempt = 0;
|
|
@@ -41708,6 +43199,9 @@ function openAccount(options = {}) {
|
|
|
41708
43199
|
let activeWriteTail = Promise.resolve();
|
|
41709
43200
|
const sessionCloseWork = new Set;
|
|
41710
43201
|
const operationBarrier = createAccountOperationCloseBarrier();
|
|
43202
|
+
const messageMaintenance = createAccountMessageMaintenance({
|
|
43203
|
+
isAccountCurrent: () => !closed
|
|
43204
|
+
});
|
|
41711
43205
|
function revokeSession(details = {}) {
|
|
41712
43206
|
const { uid = null } = details;
|
|
41713
43207
|
const accountUid = uid || state?.uid || null;
|
|
@@ -41736,7 +43230,8 @@ function openAccount(options = {}) {
|
|
|
41736
43230
|
const chat = createChatSession({
|
|
41737
43231
|
...options.chat || {},
|
|
41738
43232
|
cloud,
|
|
41739
|
-
diag: options.chat?.diag || diag
|
|
43233
|
+
diag: options.chat?.diag || diag,
|
|
43234
|
+
maintenance: messageMaintenance
|
|
41740
43235
|
}, chatSources());
|
|
41741
43236
|
const wallet = openWallet({
|
|
41742
43237
|
...options.wallet || {},
|
|
@@ -41765,6 +43260,7 @@ function openAccount(options = {}) {
|
|
|
41765
43260
|
chatPrivateKey: session?.chatPrivateKey || "",
|
|
41766
43261
|
notificationPK: session?.notificationPK || "",
|
|
41767
43262
|
notificationPrivateKey: session?.notificationPrivateKey || "",
|
|
43263
|
+
chatAdmission: currentUser.chatAdmission,
|
|
41768
43264
|
localCache: session?.localCache || null
|
|
41769
43265
|
};
|
|
41770
43266
|
}
|
|
@@ -41844,6 +43340,15 @@ function openAccount(options = {}) {
|
|
|
41844
43340
|
await cloud.user.profile.avatar.set(uid, null);
|
|
41845
43341
|
user.getSnapshot().clearAvatar?.();
|
|
41846
43342
|
return { deleted: true };
|
|
43343
|
+
},
|
|
43344
|
+
async setChatAdmission(options2 = {}) {
|
|
43345
|
+
const uid = profileUid();
|
|
43346
|
+
const session = state.session;
|
|
43347
|
+
if (!session?.chatPrivateKey || !session?.chatPK)
|
|
43348
|
+
throw new Error("vault must be unlocked");
|
|
43349
|
+
const chatAdmission = makeChatAdmission(session.chatPrivateKey, session.chatPK, options2);
|
|
43350
|
+
await cloud.user.profile.admission.set(uid, chatAdmission);
|
|
43351
|
+
return chatAdmission;
|
|
41847
43352
|
}
|
|
41848
43353
|
});
|
|
41849
43354
|
const push = Object.freeze({
|
|
@@ -41856,6 +43361,56 @@ function openAccount(options = {}) {
|
|
|
41856
43361
|
return cloud.user.push.drop(payload);
|
|
41857
43362
|
}
|
|
41858
43363
|
});
|
|
43364
|
+
const payment = Object.freeze({
|
|
43365
|
+
async createRequest(options2 = {}) {
|
|
43366
|
+
const amountSats = toSafeSats(options2.amountSats);
|
|
43367
|
+
if (BigInt(amountSats) > REQUEST_MONEY_MAX_SATS) {
|
|
43368
|
+
throw new Error("payment request amount too large");
|
|
43369
|
+
}
|
|
43370
|
+
const expirySeconds = options2.expirySeconds == null ? PUBLIC_PAYMENT_REQUEST_EXPIRY_SECONDS : Number(options2.expirySeconds);
|
|
43371
|
+
if (!Number.isSafeInteger(expirySeconds) || expirySeconds < 60 || expirySeconds > PUBLIC_PAYMENT_REQUEST_MAX_EXPIRY_SECONDS) {
|
|
43372
|
+
throw new Error("payment request expiry must be between one minute and seven days");
|
|
43373
|
+
}
|
|
43374
|
+
const uid = profileUid();
|
|
43375
|
+
const session = state.session;
|
|
43376
|
+
const username = normalizeUsername(state.user?.username);
|
|
43377
|
+
const walletPK = session?.walletPK;
|
|
43378
|
+
const signingKey = session?.chatSigningSecret && session?.chatSigningPK ? { secret: session.chatSigningSecret, publicKey: session.chatSigningPK } : null;
|
|
43379
|
+
if (state.lockState !== "unlocked" || !session || session.closed) {
|
|
43380
|
+
throw new Error("unlocked vault required");
|
|
43381
|
+
}
|
|
43382
|
+
if (!isUsername(username) || !walletPK || !signingKey) {
|
|
43383
|
+
throw new Error("payment request identity unavailable");
|
|
43384
|
+
}
|
|
43385
|
+
const createInvoice = wallet.getSnapshot().value.createLightningInvoice;
|
|
43386
|
+
const result = await createInvoice({
|
|
43387
|
+
amountSats,
|
|
43388
|
+
expirySeconds,
|
|
43389
|
+
includeSparkInvoice: true
|
|
43390
|
+
});
|
|
43391
|
+
if (closed || operationBarrier.blocksOperations || state.uid !== uid || state.session !== session || session.closed) {
|
|
43392
|
+
throw new Error("account changed during payment request");
|
|
43393
|
+
}
|
|
43394
|
+
if (!result?.success || !result.invoice?.encodedInvoice) {
|
|
43395
|
+
throw result?.error || new Error("lightning invoice unavailable");
|
|
43396
|
+
}
|
|
43397
|
+
const request = createPublicPaymentRequest({
|
|
43398
|
+
uid,
|
|
43399
|
+
username,
|
|
43400
|
+
walletPK,
|
|
43401
|
+
signingKey,
|
|
43402
|
+
network: session.network || state.network,
|
|
43403
|
+
amountSats,
|
|
43404
|
+
invoice: result.invoice.encodedInvoice
|
|
43405
|
+
});
|
|
43406
|
+
const origin = cloud.environment === "dev" ? origins.veylDev : origins.veyl;
|
|
43407
|
+
return Object.freeze({
|
|
43408
|
+
request,
|
|
43409
|
+
link: makePublicPaymentLink(request, { origin }),
|
|
43410
|
+
receiveRequest: result.invoice
|
|
43411
|
+
});
|
|
43412
|
+
}
|
|
43413
|
+
});
|
|
41859
43414
|
function emit2(patch) {
|
|
41860
43415
|
if (closed || operationBarrier.suppressPublications)
|
|
41861
43416
|
return state;
|
|
@@ -41913,7 +43468,13 @@ function openAccount(options = {}) {
|
|
|
41913
43468
|
chatSession = null;
|
|
41914
43469
|
closeAccountSession(session);
|
|
41915
43470
|
if (session && typeof options.onSessionClosed === "function") {
|
|
41916
|
-
|
|
43471
|
+
let result;
|
|
43472
|
+
try {
|
|
43473
|
+
result = options.onSessionClosed({ session, uid: state.uid });
|
|
43474
|
+
} catch (error) {
|
|
43475
|
+
result = Promise.reject(error);
|
|
43476
|
+
}
|
|
43477
|
+
const work = Promise.resolve(result).catch((error) => {
|
|
41917
43478
|
diag?.("account.session.closed.error", {
|
|
41918
43479
|
code: error?.code || "",
|
|
41919
43480
|
message: error?.message || String(error)
|
|
@@ -41939,13 +43500,17 @@ function openAccount(options = {}) {
|
|
|
41939
43500
|
}
|
|
41940
43501
|
return session;
|
|
41941
43502
|
}
|
|
43503
|
+
async function drainSessionCloseWork() {
|
|
43504
|
+
while (sessionCloseWork.size) {
|
|
43505
|
+
await Promise.allSettled([...sessionCloseWork]);
|
|
43506
|
+
}
|
|
43507
|
+
}
|
|
41942
43508
|
function lock() {
|
|
41943
43509
|
const session = closeSession();
|
|
41944
43510
|
setActive(false);
|
|
41945
43511
|
return session;
|
|
41946
43512
|
}
|
|
41947
43513
|
async function applyNetwork(network) {
|
|
41948
|
-
await writeNetwork(network);
|
|
41949
43514
|
user.setNetwork(network);
|
|
41950
43515
|
emitDomains({ network });
|
|
41951
43516
|
peers.refreshNetwork();
|
|
@@ -42028,7 +43593,6 @@ function openAccount(options = {}) {
|
|
|
42028
43593
|
writeActive(previousUid, false);
|
|
42029
43594
|
active = { uid: null, value: false };
|
|
42030
43595
|
user.setNetwork(defaultNetwork);
|
|
42031
|
-
Promise.resolve(writeNetwork(defaultNetwork)).catch(() => {});
|
|
42032
43596
|
const currentUser = user.getSnapshot();
|
|
42033
43597
|
const currentUid = currentUser.uid || null;
|
|
42034
43598
|
state = {
|
|
@@ -42046,6 +43610,11 @@ function openAccount(options = {}) {
|
|
|
42046
43610
|
const stopUser = user.subscribe(syncUser);
|
|
42047
43611
|
watchVault(state.uid);
|
|
42048
43612
|
async function unlock(password, unlockOptions = {}) {
|
|
43613
|
+
if (closed)
|
|
43614
|
+
throw new Error("account closed");
|
|
43615
|
+
if (state.lockState !== "locked")
|
|
43616
|
+
throw new Error("unlock in progress");
|
|
43617
|
+
await drainSessionCloseWork();
|
|
42049
43618
|
if (closed)
|
|
42050
43619
|
throw new Error("account closed");
|
|
42051
43620
|
if (state.lockState !== "locked")
|
|
@@ -42389,10 +43958,12 @@ function openAccount(options = {}) {
|
|
|
42389
43958
|
user,
|
|
42390
43959
|
profile,
|
|
42391
43960
|
push,
|
|
43961
|
+
payment,
|
|
42392
43962
|
support,
|
|
42393
43963
|
chat,
|
|
42394
43964
|
wallet,
|
|
42395
43965
|
bitcoin,
|
|
43966
|
+
messageMaintenance,
|
|
42396
43967
|
peers,
|
|
42397
43968
|
closeBarrier: Object.freeze({
|
|
42398
43969
|
acquire: operationBarrier.acquireClose,
|