@glyphteck/veyl 0.66.3 → 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 +2114 -461
- package/dist/accountprofiles.js +160 -10
- package/dist/auth.js +3 -3
- package/dist/cli.js +4340 -2179
- package/dist/index.js +4334 -1116
- 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;
|
|
@@ -7281,7 +7368,7 @@ function closeSessionResources({ wallet, chatPK, chatPrivateKey, chatSigningSecr
|
|
|
7281
7368
|
close(vaultAccess);
|
|
7282
7369
|
close(vaultSigner);
|
|
7283
7370
|
close(localCache);
|
|
7284
|
-
|
|
7371
|
+
lockWallet(wallet);
|
|
7285
7372
|
lockChat(chatPrivateKey, chatPK);
|
|
7286
7373
|
cleanBytes(chatSigningSecret, notificationPrivateKey, notificationPublicKey);
|
|
7287
7374
|
return Promise.allSettled(pending).then(() => {
|
|
@@ -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),
|
|
@@ -7857,6 +8251,8 @@ function settingsState(settings = defaultSettings) {
|
|
|
7857
8251
|
var defaultUser = {
|
|
7858
8252
|
uid: null,
|
|
7859
8253
|
authReady: false,
|
|
8254
|
+
authSessionReady: false,
|
|
8255
|
+
authSessionActive: false,
|
|
7860
8256
|
authCredential: null,
|
|
7861
8257
|
profileReady: false,
|
|
7862
8258
|
username: null,
|
|
@@ -7870,6 +8266,7 @@ var defaultUser = {
|
|
|
7870
8266
|
chatPK: null,
|
|
7871
8267
|
chatSigningPK: null,
|
|
7872
8268
|
notificationPK: null,
|
|
8269
|
+
chatAdmission: normalizeChatAdmission(null),
|
|
7873
8270
|
active: false,
|
|
7874
8271
|
banned: null,
|
|
7875
8272
|
agreement: null,
|
|
@@ -7878,12 +8275,16 @@ var defaultUser = {
|
|
|
7878
8275
|
settingsReady: false,
|
|
7879
8276
|
settings: settingsState()
|
|
7880
8277
|
};
|
|
8278
|
+
function isAccountAuthenticationError(error) {
|
|
8279
|
+
const code = typeof error?.code === "string" ? error.code.toLowerCase() : "";
|
|
8280
|
+
return code === "permission-denied" || code.endsWith("/permission-denied") || code === "unauthenticated" || code.endsWith("/unauthenticated");
|
|
8281
|
+
}
|
|
7881
8282
|
function createUser({ cloud, network, avatarCache = null, diag = null, onSessionRevoked = null }) {
|
|
7882
8283
|
if (!cloud) {
|
|
7883
8284
|
throw new Error("createUser requires { cloud }");
|
|
7884
8285
|
}
|
|
7885
|
-
if (avatarCache && [avatarCache.read, avatarCache.write, avatarCache.remove
|
|
7886
|
-
throw new Error("avatarCache requires read, write,
|
|
8286
|
+
if (avatarCache && [avatarCache.read, avatarCache.write, avatarCache.remove].some((method) => typeof method !== "function")) {
|
|
8287
|
+
throw new Error("avatarCache requires read, write, and remove");
|
|
7887
8288
|
}
|
|
7888
8289
|
let activeNetwork = network;
|
|
7889
8290
|
let state = defaultUser;
|
|
@@ -7980,7 +8381,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
7980
8381
|
if (!uid || !avatarCache)
|
|
7981
8382
|
return null;
|
|
7982
8383
|
try {
|
|
7983
|
-
const cached = await avatarCache.read(uid);
|
|
8384
|
+
const cached = await avatarCache.read(uid, expectedVersion);
|
|
7984
8385
|
const version = readAvatarVersion(cached?.version);
|
|
7985
8386
|
if (expectedVersion != null && version !== expectedVersion) {
|
|
7986
8387
|
return null;
|
|
@@ -8021,9 +8422,6 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8021
8422
|
return;
|
|
8022
8423
|
runCacheTask("user.avatar.cache.remove", () => avatarCache.remove(uid));
|
|
8023
8424
|
}
|
|
8024
|
-
function clearCachedAvatars() {
|
|
8025
|
-
runCacheTask("user.avatar.cache.clear", () => avatarCache.removeAll());
|
|
8026
|
-
}
|
|
8027
8425
|
async function fetchAvatar(uid, { version = null, force = false, clear = false, persist = true } = {}) {
|
|
8028
8426
|
if (!uid)
|
|
8029
8427
|
return;
|
|
@@ -8106,6 +8504,54 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8106
8504
|
function isCurrentUser(uid, session) {
|
|
8107
8505
|
return started && authSession === session && userUid === uid;
|
|
8108
8506
|
}
|
|
8507
|
+
function revokeAuthentication(authUser, {
|
|
8508
|
+
code = "",
|
|
8509
|
+
local = false,
|
|
8510
|
+
reason = "session-inactive"
|
|
8511
|
+
} = {}) {
|
|
8512
|
+
if (revokingSession)
|
|
8513
|
+
return;
|
|
8514
|
+
revokingSession = true;
|
|
8515
|
+
let callback;
|
|
8516
|
+
try {
|
|
8517
|
+
callback = onSessionRevoked?.({
|
|
8518
|
+
uid: authUser.uid,
|
|
8519
|
+
code,
|
|
8520
|
+
reason
|
|
8521
|
+
});
|
|
8522
|
+
} catch (error) {
|
|
8523
|
+
callback = Promise.reject(error);
|
|
8524
|
+
}
|
|
8525
|
+
markDiag(diag, "user.auth.revoked", {
|
|
8526
|
+
code,
|
|
8527
|
+
local,
|
|
8528
|
+
reason
|
|
8529
|
+
});
|
|
8530
|
+
setState((user) => ({
|
|
8531
|
+
...user,
|
|
8532
|
+
authSessionReady: true,
|
|
8533
|
+
authSessionActive: false,
|
|
8534
|
+
authCredential: null
|
|
8535
|
+
}));
|
|
8536
|
+
let operation = Promise.resolve(callback).catch((error) => markError(diag, "user.session.revoke.local", Date.now(), error));
|
|
8537
|
+
if (!local) {
|
|
8538
|
+
operation = operation.then(() => cloud.auth.logout()).catch((error) => markError(diag, "user.session.revoke.auth", Date.now(), error));
|
|
8539
|
+
}
|
|
8540
|
+
operation.finally(() => {
|
|
8541
|
+
if (userUid === authUser.uid) {
|
|
8542
|
+
revokingSession = false;
|
|
8543
|
+
}
|
|
8544
|
+
});
|
|
8545
|
+
}
|
|
8546
|
+
function revokeAuthenticationError(authUser, error, reason) {
|
|
8547
|
+
if (!isAccountAuthenticationError(error))
|
|
8548
|
+
return false;
|
|
8549
|
+
revokeAuthentication(authUser, {
|
|
8550
|
+
code: error?.code || "",
|
|
8551
|
+
reason
|
|
8552
|
+
});
|
|
8553
|
+
return true;
|
|
8554
|
+
}
|
|
8109
8555
|
function watchUser(authUser) {
|
|
8110
8556
|
if (!started)
|
|
8111
8557
|
return;
|
|
@@ -8115,7 +8561,6 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8115
8561
|
closeUserWatches();
|
|
8116
8562
|
markDiag(diag, "user.auth.state", { signedIn: !!authUser });
|
|
8117
8563
|
if (!authUser) {
|
|
8118
|
-
const signedOutUid = userUid;
|
|
8119
8564
|
revokingSession = false;
|
|
8120
8565
|
userUid = null;
|
|
8121
8566
|
clearSettingsKey(settingsKey);
|
|
@@ -8124,17 +8569,11 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8124
8569
|
agreementStored = null;
|
|
8125
8570
|
agreementOverride = null;
|
|
8126
8571
|
agreementAcceptance = null;
|
|
8127
|
-
if (signedOutUid) {
|
|
8128
|
-
clearCachedAvatars();
|
|
8129
|
-
}
|
|
8130
8572
|
avatarFetch = { uid: null, key: null, promise: null };
|
|
8131
8573
|
setState({ ...defaultUser, authReady: true });
|
|
8132
8574
|
return;
|
|
8133
8575
|
}
|
|
8134
8576
|
const authUidChanged = userUid !== authUser.uid;
|
|
8135
|
-
if (userUid && authUidChanged) {
|
|
8136
|
-
clearCachedAvatars();
|
|
8137
|
-
}
|
|
8138
8577
|
userUid = authUser.uid;
|
|
8139
8578
|
if (authUidChanged) {
|
|
8140
8579
|
clearSettingsKey(settingsKey);
|
|
@@ -8164,32 +8603,28 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8164
8603
|
userWatches.push(cloud.auth.session.watch(authUser.uid, current((authSessionState) => {
|
|
8165
8604
|
if (authSessionState?.active) {
|
|
8166
8605
|
const authCredential = authSessionState.authCredential;
|
|
8167
|
-
setState((user) => user.authCredential?.kind === authCredential?.kind && user.authCredential?.id === authCredential?.id ? user : {
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
revokingSession = true;
|
|
8173
|
-
if (cloud.auth.session.isLocalTermination?.(authUser.uid, authSessionState?.generation)) {
|
|
8174
|
-
Promise.resolve(onSessionRevoked?.({ uid: authUser.uid })).catch((error) => markError(diag, "user.session.revoke.local", Date.now(), error)).finally(() => {
|
|
8175
|
-
if (userUid === authUser.uid) {
|
|
8176
|
-
revokingSession = false;
|
|
8177
|
-
}
|
|
8606
|
+
setState((user) => user.authSessionReady && user.authSessionActive && user.authCredential?.kind === authCredential?.kind && user.authCredential?.id === authCredential?.id ? user : {
|
|
8607
|
+
...user,
|
|
8608
|
+
authSessionReady: true,
|
|
8609
|
+
authSessionActive: true,
|
|
8610
|
+
authCredential
|
|
8178
8611
|
});
|
|
8179
8612
|
return;
|
|
8180
8613
|
}
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
}
|
|
8614
|
+
revokeAuthentication(authUser, {
|
|
8615
|
+
local: cloud.auth.session.isLocalTermination?.(authUser.uid, authSessionState?.generation) === true,
|
|
8616
|
+
reason: "session-inactive"
|
|
8185
8617
|
});
|
|
8186
8618
|
}), current((error) => {
|
|
8187
8619
|
markError(diag, "user.session.listen", authStartedAt, error);
|
|
8620
|
+
revokeAuthenticationError(authUser, error, "session-listener");
|
|
8188
8621
|
})));
|
|
8189
8622
|
userWatches.push(cloud.user.admin.watch(authUser.uid, current((allowed) => {
|
|
8190
8623
|
setState((user) => ({ ...user, isAdmin: allowed, adminReady: true }));
|
|
8191
8624
|
}), current((error) => {
|
|
8192
8625
|
markError(diag, "user.admin.listen", authStartedAt, error);
|
|
8626
|
+
if (revokeAuthenticationError(authUser, error, "admin-listener"))
|
|
8627
|
+
return;
|
|
8193
8628
|
setState((user) => ({ ...user, isAdmin: false, adminReady: true }));
|
|
8194
8629
|
})));
|
|
8195
8630
|
userWatches.push(cloud.user.private.watch(authUser.uid, current((privateData, info = {}) => {
|
|
@@ -8213,6 +8648,8 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8213
8648
|
}));
|
|
8214
8649
|
}), current((error) => {
|
|
8215
8650
|
markError(diag, "user.settings.snapshot", authStartedAt, error);
|
|
8651
|
+
if (revokeAuthenticationError(authUser, error, "settings-listener"))
|
|
8652
|
+
return;
|
|
8216
8653
|
settingsStored = null;
|
|
8217
8654
|
agreementStored = null;
|
|
8218
8655
|
setState((user) => ({
|
|
@@ -8225,6 +8662,8 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8225
8662
|
setState((user) => ({ ...user, banned: banned ?? null }));
|
|
8226
8663
|
}), current((error) => {
|
|
8227
8664
|
markError(diag, "user.moderation.listen", authStartedAt, error);
|
|
8665
|
+
if (revokeAuthenticationError(authUser, error, "moderation-listener"))
|
|
8666
|
+
return;
|
|
8228
8667
|
setState((user) => ({ ...user, banned: null }));
|
|
8229
8668
|
})));
|
|
8230
8669
|
userWatches.push(cloud.user.blocked.watch(authUser.uid, current((blocked, info = {}) => {
|
|
@@ -8237,6 +8676,8 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8237
8676
|
});
|
|
8238
8677
|
}), current((error) => {
|
|
8239
8678
|
markError(diag, "user.blocked.listen", authStartedAt, error);
|
|
8679
|
+
if (revokeAuthenticationError(authUser, error, "blocked-listener"))
|
|
8680
|
+
return;
|
|
8240
8681
|
setState((user) => ({ ...user, blockedReady: false }));
|
|
8241
8682
|
})));
|
|
8242
8683
|
userWatches.push(cloud.user.profile.watch(authUser.uid, current((profileData, info = {}) => {
|
|
@@ -8250,14 +8691,15 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8250
8691
|
const walletPK = resolveWalletPK(profileData, activeNetwork);
|
|
8251
8692
|
const { chatPK, chatSigningPK } = readProfileChatIdentity(profileData);
|
|
8252
8693
|
const notificationPK = readProfileNotificationPK(profileData);
|
|
8694
|
+
const chatAdmission = normalizeChatAdmission(profileData.chatAdmission);
|
|
8253
8695
|
const active = profileData.active ?? false;
|
|
8254
8696
|
const hasAvatarEntry = !!info.exists && Object.prototype.hasOwnProperty.call(profileData, "avatar");
|
|
8255
8697
|
const avatarVersion2 = readAvatarVersion(profileData.avatar);
|
|
8256
8698
|
const avatar = avatarVersion2 == null ? null : user.avatar;
|
|
8257
|
-
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) {
|
|
8258
8700
|
return user;
|
|
8259
8701
|
}
|
|
8260
|
-
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 };
|
|
8261
8703
|
});
|
|
8262
8704
|
const avatarVersion = readAvatarVersion(profileData.avatar);
|
|
8263
8705
|
if (avatarVersion == null) {
|
|
@@ -8283,6 +8725,8 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8283
8725
|
}
|
|
8284
8726
|
}), current((error) => {
|
|
8285
8727
|
markError(diag, "user.profile.snapshot", authStartedAt, error);
|
|
8728
|
+
if (revokeAuthenticationError(authUser, error, "profile-listener"))
|
|
8729
|
+
return;
|
|
8286
8730
|
avatarFetch = { uid: null, key: null, promise: null };
|
|
8287
8731
|
setState((user) => ({
|
|
8288
8732
|
...user,
|
|
@@ -8294,6 +8738,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
8294
8738
|
chatPK: null,
|
|
8295
8739
|
chatSigningPK: null,
|
|
8296
8740
|
notificationPK: null,
|
|
8741
|
+
chatAdmission: normalizeChatAdmission(null),
|
|
8297
8742
|
active: false,
|
|
8298
8743
|
avatarVersion: null,
|
|
8299
8744
|
avatar: null,
|
|
@@ -8636,139 +9081,6 @@ async function pushCriticalInbox(cloud, recipientUid, ping, options = {}, retry
|
|
|
8636
9081
|
}
|
|
8637
9082
|
var criticalDeliveryInternals = Object.freeze({ retryableDeliveryError });
|
|
8638
9083
|
|
|
8639
|
-
// ../../core/chat/protocol.js
|
|
8640
|
-
"use client";
|
|
8641
|
-
var CHAT_PROTOCOL_VERSION = 3;
|
|
8642
|
-
var CHAT_MANIFEST_VERSION = 2;
|
|
8643
|
-
var CHAT_TRANSITION_VERSION = 2;
|
|
8644
|
-
var CHAT_MESSAGE_ENVELOPE_VERSION = 3;
|
|
8645
|
-
var CHAT_SETTINGS_VERSION = 1;
|
|
8646
|
-
|
|
8647
|
-
// ../../core/chat/epochs/manifest.js
|
|
8648
|
-
"use client";
|
|
8649
|
-
var CHAT_LINEAGES = Object.freeze({
|
|
8650
|
-
SELF: "self",
|
|
8651
|
-
DIRECT: "direct",
|
|
8652
|
-
GROUP: "group"
|
|
8653
|
-
});
|
|
8654
|
-
var LINEAGES = new Set(Object.values(CHAT_LINEAGES));
|
|
8655
|
-
var HEX_32_RE2 = /^[0-9a-f]{64}$/u;
|
|
8656
|
-
function cleanChatHex(value, label = "chat value") {
|
|
8657
|
-
const text = cleanText(value).toLowerCase();
|
|
8658
|
-
if (!HEX_32_RE2.test(text)) {
|
|
8659
|
-
throw new Error(`${label} required`);
|
|
8660
|
-
}
|
|
8661
|
-
return text;
|
|
8662
|
-
}
|
|
8663
|
-
function cleanUid(value) {
|
|
8664
|
-
const uid = cleanText(value);
|
|
8665
|
-
if (!uid || uid.length > 128) {
|
|
8666
|
-
throw new Error("chat member uid required");
|
|
8667
|
-
}
|
|
8668
|
-
return uid;
|
|
8669
|
-
}
|
|
8670
|
-
function cleanCreatedAt(value) {
|
|
8671
|
-
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
8672
|
-
throw new Error("chat epoch createdAt required");
|
|
8673
|
-
}
|
|
8674
|
-
return value;
|
|
8675
|
-
}
|
|
8676
|
-
function cleanEpochVersion(value) {
|
|
8677
|
-
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
8678
|
-
throw new Error("chat epoch version required");
|
|
8679
|
-
}
|
|
8680
|
-
return value;
|
|
8681
|
-
}
|
|
8682
|
-
function cleanMember(value) {
|
|
8683
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8684
|
-
throw new Error("invalid chat member");
|
|
8685
|
-
}
|
|
8686
|
-
return {
|
|
8687
|
-
uid: cleanUid(value.uid),
|
|
8688
|
-
chatPK: cleanChatHex(value.chatPK, "member chat key"),
|
|
8689
|
-
chatSigningPK: cleanChatHex(value.chatSigningPK, "member signing key"),
|
|
8690
|
-
notificationPK: cleanChatHex(value.notificationPK, "member notification key"),
|
|
8691
|
-
mlsLeafId: cleanChatHex(value.mlsLeafId, "member mls leaf id")
|
|
8692
|
-
};
|
|
8693
|
-
}
|
|
8694
|
-
function assertUniqueMembers(members) {
|
|
8695
|
-
const uids = new Set;
|
|
8696
|
-
const chatKeys = new Set;
|
|
8697
|
-
const signingKeys = new Set;
|
|
8698
|
-
const mlsLeafIds = new Set;
|
|
8699
|
-
for (const member of members) {
|
|
8700
|
-
if (uids.has(member.uid) || chatKeys.has(member.chatPK) || signingKeys.has(member.chatSigningPK) || mlsLeafIds.has(member.mlsLeafId)) {
|
|
8701
|
-
throw new Error("duplicate chat member");
|
|
8702
|
-
}
|
|
8703
|
-
uids.add(member.uid);
|
|
8704
|
-
chatKeys.add(member.chatPK);
|
|
8705
|
-
signingKeys.add(member.chatSigningPK);
|
|
8706
|
-
mlsLeafIds.add(member.mlsLeafId);
|
|
8707
|
-
}
|
|
8708
|
-
}
|
|
8709
|
-
function assertLineage(manifest, parent) {
|
|
8710
|
-
const memberCount = manifest.members.length;
|
|
8711
|
-
if (memberCount > 2 && manifest.lineage !== CHAT_LINEAGES.GROUP) {
|
|
8712
|
-
throw new Error("group lineage required");
|
|
8713
|
-
}
|
|
8714
|
-
if (!parent) {
|
|
8715
|
-
return;
|
|
8716
|
-
}
|
|
8717
|
-
if (manifest.chatId !== parent.chatId || manifest.parentEpochId !== parent.epochId || manifest.epochVersion !== parent.epochVersion + 1) {
|
|
8718
|
-
throw new Error("invalid chat epoch successor");
|
|
8719
|
-
}
|
|
8720
|
-
if (parent.lineage === CHAT_LINEAGES.GROUP && manifest.lineage !== CHAT_LINEAGES.GROUP) {
|
|
8721
|
-
throw new Error("chat group lineage is permanent");
|
|
8722
|
-
}
|
|
8723
|
-
}
|
|
8724
|
-
function normalizeEpochManifest(value, options = {}) {
|
|
8725
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
8726
|
-
throw new Error("chat manifest required");
|
|
8727
|
-
}
|
|
8728
|
-
if (value.v !== CHAT_MANIFEST_VERSION || value.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
8729
|
-
throw new Error("unsupported chat manifest");
|
|
8730
|
-
}
|
|
8731
|
-
if (!Array.isArray(value.members) || value.members.length < 1 || value.members.length > CHAT_MAX_MEMBERS) {
|
|
8732
|
-
throw new Error("invalid chat member count");
|
|
8733
|
-
}
|
|
8734
|
-
const lineage = cleanText(value.lineage);
|
|
8735
|
-
if (!LINEAGES.has(lineage)) {
|
|
8736
|
-
throw new Error("invalid chat lineage");
|
|
8737
|
-
}
|
|
8738
|
-
const members = value.members.map(cleanMember).sort((a, b) => a.chatPK.localeCompare(b.chatPK));
|
|
8739
|
-
assertUniqueMembers(members);
|
|
8740
|
-
const manifest = {
|
|
8741
|
-
v: CHAT_MANIFEST_VERSION,
|
|
8742
|
-
protocol: CHAT_PROTOCOL_VERSION,
|
|
8743
|
-
chatId: cleanChatHex(value.chatId, "chat id"),
|
|
8744
|
-
epochId: cleanChatHex(value.epochId, "chat epoch id"),
|
|
8745
|
-
epochVersion: cleanEpochVersion(value.epochVersion),
|
|
8746
|
-
parentEpochId: value.parentEpochId == null ? null : cleanChatHex(value.parentEpochId, "parent epoch id"),
|
|
8747
|
-
createdAt: cleanCreatedAt(value.createdAt),
|
|
8748
|
-
lineage,
|
|
8749
|
-
members
|
|
8750
|
-
};
|
|
8751
|
-
if (manifest.epochVersion === 1 !== (manifest.parentEpochId === null)) {
|
|
8752
|
-
throw new Error("invalid parent epoch");
|
|
8753
|
-
}
|
|
8754
|
-
const bytes = canonicalBytes(manifest, "chat manifest");
|
|
8755
|
-
if (bytes.length > CHAT_MANIFEST_MAX_BYTES) {
|
|
8756
|
-
throw new Error("chat manifest too large");
|
|
8757
|
-
}
|
|
8758
|
-
assertLineage(manifest, options.parent || null);
|
|
8759
|
-
return manifest;
|
|
8760
|
-
}
|
|
8761
|
-
function epochManifestDigest(manifest) {
|
|
8762
|
-
return toHex(sha256(canonicalBytes(normalizeEpochManifest(manifest), "chat manifest digest")));
|
|
8763
|
-
}
|
|
8764
|
-
function manifestMember(manifest, chatPK) {
|
|
8765
|
-
const key = cleanChatHex(chatPK, "member chat key");
|
|
8766
|
-
return normalizeEpochManifest(manifest).members.find((member) => member.chatPK === key) || null;
|
|
8767
|
-
}
|
|
8768
|
-
function manifestSigningKeys(manifest) {
|
|
8769
|
-
return Object.fromEntries(normalizeEpochManifest(manifest).members.map((member) => [member.chatPK, member.chatSigningPK]));
|
|
8770
|
-
}
|
|
8771
|
-
|
|
8772
9084
|
// ../../core/chat/epochs/state.js
|
|
8773
9085
|
"use client";
|
|
8774
9086
|
var CAPABILITY_WITNESS_SCOPE = "veyl-chat-state-witness-v3:";
|
|
@@ -9980,6 +10292,7 @@ function epochChatSettingsId(epochState) {
|
|
|
9980
10292
|
"use client";
|
|
9981
10293
|
var CHAT_ENTRY_VERSION = 4;
|
|
9982
10294
|
var CHAT_OWNER_EPOCH_ENTRY_VERSION = 2;
|
|
10295
|
+
var CHAT_NOTIFICATION_PREFERENCE_VERSION = 1;
|
|
9983
10296
|
var CHAT_OWNER_RETIREMENT_KINDS = Object.freeze({
|
|
9984
10297
|
LEAVE: "leave",
|
|
9985
10298
|
REMOVED: "removed"
|
|
@@ -10001,6 +10314,16 @@ function entryKey(chatPrivateKey, entryId) {
|
|
|
10001
10314
|
function entryAad(entryId) {
|
|
10002
10315
|
return canonicalBytes({ v: CHAT_ENTRY_VERSION, protocol: CHAT_PROTOCOL_VERSION, entryId }, "chat entry aad");
|
|
10003
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
|
+
}
|
|
10004
10327
|
function ownerEpochKey(chatPrivateKey, entryId, epochEntryId) {
|
|
10005
10328
|
return deriveKey(toBytes32(chatPrivateKey, "chat private key"), "user-chat-epoch-entry-v3", [entryId, epochEntryId]);
|
|
10006
10329
|
}
|
|
@@ -10036,12 +10359,13 @@ function normalizeRoutes(value) {
|
|
|
10036
10359
|
}
|
|
10037
10360
|
const uid = cleanText(route.uid);
|
|
10038
10361
|
const deliveryCapability = route.deliveryCapability == null ? null : cleanChatHex(route.deliveryCapability, "delivery capability");
|
|
10362
|
+
const admissionCapability = route.admissionCapability == null ? null : cleanChatHex(route.admissionCapability, "admission capability");
|
|
10039
10363
|
const notificationPK = route.notificationPK == null ? null : cleanChatHex(route.notificationPK, "notification key");
|
|
10040
10364
|
const generation = Number.isSafeInteger(route.generation) && route.generation > 0 ? route.generation : 1;
|
|
10041
10365
|
if (!uid || uid.length > 128) {
|
|
10042
10366
|
throw new Error("chat route uid required");
|
|
10043
10367
|
}
|
|
10044
|
-
routes[chatPK] = { uid, deliveryCapability, notificationPK, generation };
|
|
10368
|
+
routes[chatPK] = { uid, deliveryCapability, admissionCapability, notificationPK, generation };
|
|
10045
10369
|
}
|
|
10046
10370
|
return routes;
|
|
10047
10371
|
}
|
|
@@ -10098,6 +10422,32 @@ function normalizeOwnerEntry(value) {
|
|
|
10098
10422
|
retirement: normalizeOwnerRetirement(value.retirement, current)
|
|
10099
10423
|
};
|
|
10100
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
|
+
}
|
|
10101
10451
|
async function sealOwnChatEntry(chatPrivateKey, entryId, entry) {
|
|
10102
10452
|
const key = entryKey(chatPrivateKey, entryId);
|
|
10103
10453
|
try {
|
|
@@ -10111,14 +10461,14 @@ async function openOwnChatEntry(chatPrivateKey, entryId, body) {
|
|
|
10111
10461
|
const key = entryKey(chatPrivateKey, entryId);
|
|
10112
10462
|
try {
|
|
10113
10463
|
const { nonce, ct } = unpackBodyData(body);
|
|
10114
|
-
return normalizeOwnerEntry(await openJson(key, nonce, ct, entryAad(entryId)));
|
|
10464
|
+
return withNotificationPreference(normalizeOwnerEntry(await openJson(key, nonce, ct, entryAad(entryId))));
|
|
10115
10465
|
} finally {
|
|
10116
10466
|
cleanBytes(key);
|
|
10117
10467
|
}
|
|
10118
10468
|
}
|
|
10119
10469
|
function makeOwnChatEntry(epoch, fields = {}) {
|
|
10120
10470
|
const manifest = normalizeEpochManifest(epoch?.manifest);
|
|
10121
|
-
|
|
10471
|
+
const entry = normalizeOwnerEntry({
|
|
10122
10472
|
v: CHAT_ENTRY_VERSION,
|
|
10123
10473
|
protocol: CHAT_PROTOCOL_VERSION,
|
|
10124
10474
|
ownerRevision: Number.isSafeInteger(fields.ownerRevision) && fields.ownerRevision > 0 ? fields.ownerRevision : 1,
|
|
@@ -10139,6 +10489,48 @@ function makeOwnChatEntry(epoch, fields = {}) {
|
|
|
10139
10489
|
notificationTag: cleanText(fields.notificationTag) || null,
|
|
10140
10490
|
retirement: fields.retirement || null
|
|
10141
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);
|
|
10142
10534
|
}
|
|
10143
10535
|
function normalizeOwnerEpochRecord(value) {
|
|
10144
10536
|
if (!value || typeof value !== "object" || Array.isArray(value) || value.v !== CHAT_OWNER_EPOCH_ENTRY_VERSION || value.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
@@ -10210,6 +10602,7 @@ async function prepareMutation(identity, entryId, current, update) {
|
|
|
10210
10602
|
entryId,
|
|
10211
10603
|
record: {
|
|
10212
10604
|
body: await sealOwnChatEntry(identity.chatPrivateKey, entryId, entry),
|
|
10605
|
+
notificationBody: await sealOwnChatNotificationPreference(identity.chatPrivateKey, entryId, entry),
|
|
10213
10606
|
revision: entry.ownerRevision,
|
|
10214
10607
|
...Number.isFinite(prepared.tsMs) ? { tsMs: prepared.tsMs } : {},
|
|
10215
10608
|
...prepared.touchTs === true ? { touchTs: true } : {}
|
|
@@ -10237,7 +10630,7 @@ async function prepareOwnChatMutation(identity, entryId, current, update) {
|
|
|
10237
10630
|
if (!record?.body || !Number.isSafeInteger(record.revision) || record.revision < 1) {
|
|
10238
10631
|
throw new Error("current chat owner record required");
|
|
10239
10632
|
}
|
|
10240
|
-
const opened = await
|
|
10633
|
+
const opened = await openOwnChatRecord(identity.chatPrivateKey, entryId, record);
|
|
10241
10634
|
if (opened.ownerRevision !== record.revision) {
|
|
10242
10635
|
throw new Error("chat owner revision mismatch");
|
|
10243
10636
|
}
|
|
@@ -10273,6 +10666,7 @@ async function retireOwnChatEntry(cloud, identity, entryId, current, fields = {}
|
|
|
10273
10666
|
...latest,
|
|
10274
10667
|
routes: {},
|
|
10275
10668
|
deliveryRegistered: false,
|
|
10669
|
+
attentionRegistrationVersion: 0,
|
|
10276
10670
|
notificationTag: null,
|
|
10277
10671
|
retirement
|
|
10278
10672
|
},
|
|
@@ -10286,7 +10680,7 @@ async function openOwnChatMutationEntry(identity, entryId, record) {
|
|
|
10286
10680
|
return null;
|
|
10287
10681
|
if (!record?.body || !Number.isSafeInteger(record.revision) || record.revision < 1)
|
|
10288
10682
|
throw new Error("current chat owner record required");
|
|
10289
|
-
const current = await
|
|
10683
|
+
const current = await openOwnChatRecord(identity.chatPrivateKey, entryId, record);
|
|
10290
10684
|
if (current.ownerRevision !== record.revision) {
|
|
10291
10685
|
throw new Error("chat owner revision mismatch");
|
|
10292
10686
|
}
|
|
@@ -10391,6 +10785,166 @@ var MAX_REACTIONS = CHAT_MAX_REACTIONS;
|
|
|
10391
10785
|
var HOLD_VISIBLE_KEY = "__holdVisible";
|
|
10392
10786
|
var SOURCE_GONE_VISIBLE_KEY = "__sourceGoneVisible";
|
|
10393
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
|
+
|
|
10394
10948
|
// ../../core/chat/messages/text.js
|
|
10395
10949
|
var DOMAIN_LABEL_PATTERN = "[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?";
|
|
10396
10950
|
var DOMAIN_TLD_PATTERN = "(?:[a-z]{2,63}|xn--[a-z0-9-]{2,59})";
|
|
@@ -12239,7 +12793,6 @@ function getDisplayMessages(messages, selfChatPublicKey, peerChatPublicKey, opti
|
|
|
12239
12793
|
|
|
12240
12794
|
// ../../core/chat/messages/compact.js
|
|
12241
12795
|
"use client";
|
|
12242
|
-
var pendingCompactKeys = new Set;
|
|
12243
12796
|
function ownsMessageCompaction(memberChatPKs, chatPK) {
|
|
12244
12797
|
const members = [...new Set((memberChatPKs || []).filter(Boolean))].sort();
|
|
12245
12798
|
return !!chatPK && members[0] === chatPK;
|
|
@@ -12315,39 +12868,45 @@ function compactKey(chatId, message) {
|
|
|
12315
12868
|
const id = cleanText(message?.id);
|
|
12316
12869
|
return chatId && id ? `${chatId}/${id}` : "";
|
|
12317
12870
|
}
|
|
12318
|
-
function claimMessages(chatId, messages) {
|
|
12871
|
+
function claimMessages(maintenance, chatId, messages) {
|
|
12319
12872
|
const claimed = [];
|
|
12320
12873
|
for (const message of messages || []) {
|
|
12321
12874
|
const key = compactKey(chatId, message);
|
|
12322
|
-
if (!key
|
|
12875
|
+
if (!key)
|
|
12323
12876
|
continue;
|
|
12324
|
-
|
|
12325
|
-
|
|
12877
|
+
const lease = maintenance.claim("message-compaction", key);
|
|
12878
|
+
if (!lease)
|
|
12879
|
+
continue;
|
|
12880
|
+
claimed.push({ lease, message });
|
|
12326
12881
|
}
|
|
12327
12882
|
return claimed;
|
|
12328
12883
|
}
|
|
12329
|
-
function releaseMessages(
|
|
12330
|
-
for (const
|
|
12331
|
-
|
|
12332
|
-
if (key)
|
|
12333
|
-
pendingCompactKeys.delete(key);
|
|
12334
|
-
}
|
|
12884
|
+
function releaseMessages(claimed) {
|
|
12885
|
+
for (const item of claimed || [])
|
|
12886
|
+
item.lease.release();
|
|
12335
12887
|
}
|
|
12336
|
-
async function compactMessages({ chatId, messages, deletedKeys, protectedKeys, deleteMessages, scannedKeys, scanComplete }) {
|
|
12888
|
+
async function compactMessages({ chatId, maintenance, messages, deletedKeys, protectedKeys, deleteMessages, scannedKeys, scanComplete }) {
|
|
12337
12889
|
if (!chatId || typeof deleteMessages !== "function")
|
|
12338
12890
|
return [];
|
|
12339
|
-
|
|
12340
|
-
|
|
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)
|
|
12341
12895
|
return [];
|
|
12896
|
+
const targets = claimed.map((item) => item.message);
|
|
12342
12897
|
try {
|
|
12898
|
+
for (const item of claimed)
|
|
12899
|
+
item.lease.assertCurrent();
|
|
12343
12900
|
await deleteMessages(targets);
|
|
12901
|
+
for (const item of claimed)
|
|
12902
|
+
item.lease.assertCurrent();
|
|
12344
12903
|
if (deletedKeys?.add) {
|
|
12345
12904
|
for (const message of targets)
|
|
12346
12905
|
addMessageKeys(deletedKeys, message);
|
|
12347
12906
|
}
|
|
12348
12907
|
return targets;
|
|
12349
12908
|
} finally {
|
|
12350
|
-
releaseMessages(
|
|
12909
|
+
releaseMessages(claimed);
|
|
12351
12910
|
}
|
|
12352
12911
|
}
|
|
12353
12912
|
|
|
@@ -12879,20 +13438,23 @@ function ownerPreview(senderChatPK, message, messageId, head, tsMs, ttlMs) {
|
|
|
12879
13438
|
}
|
|
12880
13439
|
async function ensureDeliveryRoute(cloud, identity, epochState, entry, shouldPing) {
|
|
12881
13440
|
if (!shouldPing)
|
|
12882
|
-
return { capability: "", registered: entry
|
|
13441
|
+
return { capability: "", registered: hasCurrentChatAttentionRegistration(entry) };
|
|
12883
13442
|
if (!identity.notificationPK) {
|
|
12884
13443
|
throw new Error("notification identity required");
|
|
12885
13444
|
}
|
|
12886
|
-
const
|
|
13445
|
+
const registration = chatDeliveryRegistration(epochState.stateCapability, {
|
|
13446
|
+
attentionSecret: epochState.epochSecret,
|
|
12887
13447
|
chatId: epochState.manifest.chatId,
|
|
12888
13448
|
recipientChatPK: identity.chatPK,
|
|
12889
|
-
generation: epochState.manifest.epochVersion
|
|
13449
|
+
generation: epochState.manifest.epochVersion,
|
|
13450
|
+
manifest: epochState.manifest,
|
|
13451
|
+
notificationMode: entry?.notificationMode
|
|
12890
13452
|
});
|
|
12891
|
-
if (entry
|
|
12892
|
-
return { capability, registered: true };
|
|
13453
|
+
if (hasCurrentChatAttentionRegistration(entry)) {
|
|
13454
|
+
return { capability: registration.capability, registered: true };
|
|
12893
13455
|
}
|
|
12894
|
-
const registered = await cloud.delivery?.register?.(
|
|
12895
|
-
return { capability: registered ? capability : "", registered };
|
|
13456
|
+
const registered = await cloud.delivery?.register?.(registration).then(() => true, () => false) || false;
|
|
13457
|
+
return { capability: registered ? registration.capability : "", registered };
|
|
12896
13458
|
}
|
|
12897
13459
|
function mergedRoutes(entry, options, epochState) {
|
|
12898
13460
|
const routes = {
|
|
@@ -12906,6 +13468,7 @@ function mergedRoutes(entry, options, epochState) {
|
|
|
12906
13468
|
...routes[member.chatPK] || {},
|
|
12907
13469
|
uid: member.uid,
|
|
12908
13470
|
notificationPK: member.notificationPK,
|
|
13471
|
+
admissionCapability: routes[member.chatPK]?.admissionCapability || null,
|
|
12909
13472
|
deliveryCapability: deriveChatDeliveryCapability(epochState.stateCapability, {
|
|
12910
13473
|
chatId: manifest.chatId,
|
|
12911
13474
|
recipientChatPK: member.chatPK,
|
|
@@ -12924,6 +13487,7 @@ async function makeOwnerEntryMutation(identity, epochState, existing, routes, fi
|
|
|
12924
13487
|
...existing,
|
|
12925
13488
|
routes,
|
|
12926
13489
|
deliveryRegistered: fields.deliveryRegistered || existing.deliveryRegistered,
|
|
13490
|
+
attentionRegistrationVersion: fields.deliveryRegistered ? CHAT_ATTENTION_REGISTRATION_VERSION : existing.attentionRegistrationVersion,
|
|
12927
13491
|
notificationTag: notificationChatTag(epochState.stateCapability, chatId, identity.chatPK),
|
|
12928
13492
|
...fields.settings ? {
|
|
12929
13493
|
current: { ...existing.current, settings: normalizeChatSettingsProjection(fields.settings) }
|
|
@@ -12947,12 +13511,13 @@ async function makeOwnerEntryMutation(identity, epochState, existing, routes, fi
|
|
|
12947
13511
|
...current,
|
|
12948
13512
|
routes: { ...current.routes, ...routes },
|
|
12949
13513
|
deliveryRegistered: fields.deliveryRegistered || current.deliveryRegistered,
|
|
13514
|
+
attentionRegistrationVersion: fields.deliveryRegistered ? CHAT_ATTENTION_REGISTRATION_VERSION : current.attentionRegistrationVersion,
|
|
12950
13515
|
notificationTag: notificationChatTag(epochState.stateCapability, chatId, identity.chatPK),
|
|
12951
13516
|
...fields.settings ? {
|
|
12952
13517
|
current: { ...current.current, settings: normalizeChatSettingsProjection(fields.settings) }
|
|
12953
13518
|
} : {}
|
|
12954
13519
|
};
|
|
12955
|
-
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;
|
|
12956
13521
|
return unchanged ? { result: current } : {
|
|
12957
13522
|
entry: next,
|
|
12958
13523
|
...Number.isFinite(fields.tsMs) ? { tsMs: fields.tsMs } : {}
|
|
@@ -12992,16 +13557,20 @@ async function makeChatRecipientDeliveries(identity, epochState, routes, message
|
|
|
12992
13557
|
if (!fields.shouldPing || fields.deliverRecipients === false)
|
|
12993
13558
|
return [];
|
|
12994
13559
|
const capabilities = chatEpochCapabilities(epochState);
|
|
12995
|
-
const
|
|
12996
|
-
const
|
|
12997
|
-
|
|
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)
|
|
12998
13565
|
return [];
|
|
12999
13566
|
const deliveries = [];
|
|
13000
13567
|
for (const member of epochState.manifest.members) {
|
|
13001
13568
|
if (member.chatPK === identity.chatPK)
|
|
13002
13569
|
continue;
|
|
13003
|
-
if (!
|
|
13570
|
+
if (!rotation && !capabilities.ordinaryDelivery && !targetedRecipients.has(member.chatPK))
|
|
13004
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;
|
|
13005
13574
|
const route = routes[member.chatPK] || {};
|
|
13006
13575
|
const descriptor = await sealNotificationDescriptor(member.notificationPK, {
|
|
13007
13576
|
peerTag: notificationChatTag(epochState.stateCapability, epochState.manifest.chatId, member.chatPK),
|
|
@@ -13021,7 +13590,10 @@ async function makeChatRecipientDeliveries(identity, epochState, routes, message
|
|
|
13021
13590
|
});
|
|
13022
13591
|
deliveries.push({
|
|
13023
13592
|
recipientChatPK: member.chatPK,
|
|
13024
|
-
...route.deliveryCapability ? { capability: route.deliveryCapability } : {
|
|
13593
|
+
...route.deliveryCapability ? { capability: route.deliveryCapability } : {
|
|
13594
|
+
recipientUid: member.uid,
|
|
13595
|
+
admissionCapability: route.admissionCapability || null
|
|
13596
|
+
},
|
|
13025
13597
|
deliveryId: deriveChatInboxDeliveryId(epochState.stateCapability, {
|
|
13026
13598
|
chatId: epochState.manifest.chatId,
|
|
13027
13599
|
recipientChatPK: member.chatPK,
|
|
@@ -13030,7 +13602,14 @@ async function makeChatRecipientDeliveries(identity, epochState, routes, message
|
|
|
13030
13602
|
ping,
|
|
13031
13603
|
descriptor,
|
|
13032
13604
|
routeTag: notificationRouteTag(descriptor),
|
|
13033
|
-
|
|
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
|
|
13034
13613
|
});
|
|
13035
13614
|
}
|
|
13036
13615
|
return deliveries;
|
|
@@ -13048,9 +13627,14 @@ async function prepareMsgRecord(identity, epochState, message, options = {}) {
|
|
|
13048
13627
|
}
|
|
13049
13628
|
const chatRetention = cleanChatRetention(options.retention ?? epochState.settings.values.retention);
|
|
13050
13629
|
const tsMs = Date.now();
|
|
13051
|
-
const
|
|
13630
|
+
const retainedMessage = withMessageRetention(message, chatRetention);
|
|
13631
|
+
const messagePayload = normalizeChatMessageMentions(retainedMessage, epochState.manifest, {
|
|
13632
|
+
strict: true,
|
|
13633
|
+
senderChatPK: identity.chatPK
|
|
13634
|
+
});
|
|
13052
13635
|
const retention = getMessageRetention(messagePayload, chatRetention);
|
|
13053
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: [] };
|
|
13054
13638
|
const { head, body } = await sealMsg(epoch, messagePayload, {
|
|
13055
13639
|
op: actionOp,
|
|
13056
13640
|
target: options.actionTarget,
|
|
@@ -13065,6 +13649,7 @@ async function prepareMsgRecord(identity, epochState, message, options = {}) {
|
|
|
13065
13649
|
cid: head.cid,
|
|
13066
13650
|
record: { lane: epoch.messageLane, head, body, ttlMs },
|
|
13067
13651
|
message: ownerPreview(identity.chatPK, messagePayload, messageId, head, tsMs, ttlMs),
|
|
13652
|
+
mentionRecipientChatPKs: mentionTargets.recipientChatPKs,
|
|
13068
13653
|
tsMs
|
|
13069
13654
|
};
|
|
13070
13655
|
} finally {
|
|
@@ -13097,6 +13682,7 @@ async function sendMsg(cloud, senderChatPK, senderPrivateKey, _receiverChatPK, m
|
|
|
13097
13682
|
msgId: messageId,
|
|
13098
13683
|
record,
|
|
13099
13684
|
message: sentMessage,
|
|
13685
|
+
mentionRecipientChatPKs,
|
|
13100
13686
|
tsMs
|
|
13101
13687
|
} = prepared;
|
|
13102
13688
|
const updatePreview = options.updatePreview !== false;
|
|
@@ -13105,7 +13691,7 @@ async function sendMsg(cloud, senderChatPK, senderPrivateKey, _receiverChatPK, m
|
|
|
13105
13691
|
const routes = mergedRoutes(options.ownEntry, options, epochState);
|
|
13106
13692
|
const preview = updatePreview ? sentMessage : null;
|
|
13107
13693
|
const settingsProjection = options.settingsProjection ? normalizeChatSettingsProjection(options.settingsProjection) : null;
|
|
13108
|
-
const ownerStateChanged = !options.ownEntry || settingsProjection || shouldPing && routeState.registered && options.ownEntry
|
|
13694
|
+
const ownerStateChanged = !options.ownEntry || settingsProjection || shouldPing && routeState.registered && !hasCurrentChatAttentionRegistration(options.ownEntry);
|
|
13109
13695
|
const ownerMutation = ownerStateChanged ? await makeOwnerEntryMutation(identity, epochState, options.ownEntry, routes, {
|
|
13110
13696
|
settings: settingsProjection,
|
|
13111
13697
|
immediate: !!settingsProjection || options.ownerImmediate === true,
|
|
@@ -13118,13 +13704,18 @@ async function sendMsg(cloud, senderChatPK, senderPrivateKey, _receiverChatPK, m
|
|
|
13118
13704
|
kind: pingKind,
|
|
13119
13705
|
tsMs
|
|
13120
13706
|
});
|
|
13707
|
+
const deliveryRecipientChatPKs = [
|
|
13708
|
+
...options.deliveryRecipientChatPKs || [],
|
|
13709
|
+
...mentionRecipientChatPKs
|
|
13710
|
+
];
|
|
13121
13711
|
const deliveries = await makeChatRecipientDeliveries(identity, epochState, routes, messageId, {
|
|
13122
13712
|
shouldPing,
|
|
13123
13713
|
deliverRecipients: options.deliverRecipients !== false,
|
|
13124
13714
|
kind: pingKind,
|
|
13125
13715
|
ownDeliveryCapability: routeState.capability,
|
|
13126
|
-
|
|
13127
|
-
recipientChatPKs:
|
|
13716
|
+
silent: options.notify === false,
|
|
13717
|
+
recipientChatPKs: deliveryRecipientChatPKs,
|
|
13718
|
+
attentionRecipientChatPKs: mentionRecipientChatPKs,
|
|
13128
13719
|
tsMs
|
|
13129
13720
|
});
|
|
13130
13721
|
const write = await cloud.chat.messages.send({
|
|
@@ -13746,14 +14337,19 @@ function normalizeDelivery(value) {
|
|
|
13746
14337
|
throw new Error("invalid chat membership delivery mode");
|
|
13747
14338
|
const recipientUid = cleanText(value.recipientUid);
|
|
13748
14339
|
const capability = cleanText(value.capability).toLowerCase();
|
|
14340
|
+
const admissionCapability = cleanText(value.admissionCapability).toLowerCase();
|
|
13749
14341
|
if (mode === "critical" && !recipientUid || mode === "established" && !/^[0-9a-f]{64}$/u.test(capability)) {
|
|
13750
14342
|
throw new Error("invalid chat membership delivery target");
|
|
13751
14343
|
}
|
|
14344
|
+
if (admissionCapability && !/^[0-9a-f]{64}$/u.test(admissionCapability)) {
|
|
14345
|
+
throw new Error("invalid chat admission capability");
|
|
14346
|
+
}
|
|
13752
14347
|
return {
|
|
13753
14348
|
mode,
|
|
13754
14349
|
recipientUid,
|
|
13755
14350
|
recipientChatPK: cleanChatHex(value.recipientChatPK, "membership recipient"),
|
|
13756
14351
|
capability,
|
|
14352
|
+
admissionCapability,
|
|
13757
14353
|
deliveryId: cleanChatHex(value.deliveryId, "membership delivery id"),
|
|
13758
14354
|
ping: openPing(value.ping),
|
|
13759
14355
|
descriptor: openDescriptor(value.descriptor),
|
|
@@ -13964,9 +14560,11 @@ function successorManifest(parent, members, epochId) {
|
|
|
13964
14560
|
members
|
|
13965
14561
|
}, { parent, official: true });
|
|
13966
14562
|
}
|
|
13967
|
-
function nextRoutes(entry, manifest, stateCapability) {
|
|
14563
|
+
function nextRoutes(entry, manifest, stateCapability, privateMembers = []) {
|
|
14564
|
+
const privateByChatPK = new Map(privateMembers.map((member) => [member.chatPK, member]));
|
|
13968
14565
|
return Object.fromEntries(manifest.members.map((member) => {
|
|
13969
14566
|
const current = entry.routes?.[member.chatPK] || {};
|
|
14567
|
+
const privateMember = privateByChatPK.get(member.chatPK) || {};
|
|
13970
14568
|
return [member.chatPK, {
|
|
13971
14569
|
uid: member.uid,
|
|
13972
14570
|
deliveryCapability: deriveChatDeliveryCapability(stateCapability, {
|
|
@@ -13975,6 +14573,7 @@ function nextRoutes(entry, manifest, stateCapability) {
|
|
|
13975
14573
|
generation: manifest.epochVersion
|
|
13976
14574
|
}),
|
|
13977
14575
|
notificationPK: member.notificationPK || current.notificationPK || null,
|
|
14576
|
+
admissionCapability: privateMember.admissionCapability || current.admissionCapability || null,
|
|
13978
14577
|
generation: manifest.epochVersion
|
|
13979
14578
|
}];
|
|
13980
14579
|
}));
|
|
@@ -14023,7 +14622,11 @@ async function makeMlsWelcomeDeliveries(identity, nextState2, addedMembers, opti
|
|
|
14023
14622
|
senderUid: identity.uid,
|
|
14024
14623
|
messageId,
|
|
14025
14624
|
transitionMessageId: nextState2.transitionMessageId,
|
|
14026
|
-
deliveryCapability:
|
|
14625
|
+
deliveryCapability: deriveChatDeliveryCapability(nextState2.stateCapability, {
|
|
14626
|
+
chatId: nextState2.manifest.chatId,
|
|
14627
|
+
recipientChatPK: identity.chatPK,
|
|
14628
|
+
generation: nextState2.manifest.epochVersion
|
|
14629
|
+
}),
|
|
14027
14630
|
notificationPK: identity.notificationPK,
|
|
14028
14631
|
transitionCommitment: nextState2.transitionCommitment,
|
|
14029
14632
|
settingsId,
|
|
@@ -14044,6 +14647,7 @@ async function makeMlsWelcomeDeliveries(identity, nextState2, addedMembers, opti
|
|
|
14044
14647
|
ping,
|
|
14045
14648
|
descriptor,
|
|
14046
14649
|
routeTag,
|
|
14650
|
+
admissionCapability: member.admissionCapability || null,
|
|
14047
14651
|
member
|
|
14048
14652
|
});
|
|
14049
14653
|
}
|
|
@@ -14058,7 +14662,7 @@ async function makeTransitionWakes(identity, parentState, entry, messageId, tsMs
|
|
|
14058
14662
|
shouldPing: true,
|
|
14059
14663
|
deliverRecipients: true,
|
|
14060
14664
|
kind: "transition",
|
|
14061
|
-
deliveryClass: "
|
|
14665
|
+
deliveryClass: "rotation",
|
|
14062
14666
|
ownDeliveryCapability: "",
|
|
14063
14667
|
notify: true,
|
|
14064
14668
|
tsMs
|
|
@@ -14070,14 +14674,18 @@ async function makeTransitionWakes(identity, parentState, entry, messageId, tsMs
|
|
|
14070
14674
|
}
|
|
14071
14675
|
async function deliverMembershipRecord(cloud, delivery) {
|
|
14072
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
|
+
}
|
|
14073
14680
|
await cloud.inbox.deliver(delivery.capability, delivery.ping, {
|
|
14681
|
+
attentionProof: delivery.attentionProof,
|
|
14074
14682
|
descriptor: delivery.descriptor,
|
|
14075
|
-
routeTag: delivery.routeTag
|
|
14076
|
-
notify: true
|
|
14683
|
+
routeTag: delivery.routeTag
|
|
14077
14684
|
});
|
|
14078
14685
|
return;
|
|
14079
14686
|
}
|
|
14080
14687
|
await pushCriticalInbox(cloud, delivery.recipientUid || delivery.member?.uid, delivery.ping, {
|
|
14688
|
+
admissionCapability: delivery.admissionCapability || null,
|
|
14081
14689
|
deliveryId: delivery.deliveryId,
|
|
14082
14690
|
descriptor: delivery.descriptor,
|
|
14083
14691
|
routeTag: delivery.routeTag,
|
|
@@ -14107,6 +14715,46 @@ async function deliverQueuedMembership(cloud, identity, deliveries) {
|
|
|
14107
14715
|
deliveredMembers: delivered.map((delivery) => delivery.member).filter(Boolean)
|
|
14108
14716
|
};
|
|
14109
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
|
+
}
|
|
14110
14758
|
async function drainChatMembershipOutbox(cloud, identity, options = {}) {
|
|
14111
14759
|
const records = await cloud.user.chats.membershipOutbox.list(identity.uid, { count: options.count || 20 });
|
|
14112
14760
|
const committedPackages = new Map;
|
|
@@ -14119,13 +14767,15 @@ async function drainChatMembershipOutbox(cloud, identity, options = {}) {
|
|
|
14119
14767
|
}
|
|
14120
14768
|
const packageKey = `${opened.chatId}:${opened.packageId}:${opened.packageDigest}`;
|
|
14121
14769
|
if (!committedPackages.has(packageKey)) {
|
|
14122
|
-
const
|
|
14123
|
-
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);
|
|
14124
14772
|
}
|
|
14125
|
-
|
|
14773
|
+
const mlsPackage = committedPackages.get(packageKey);
|
|
14774
|
+
if (!mlsPackage)
|
|
14126
14775
|
continue;
|
|
14127
14776
|
try {
|
|
14128
|
-
const
|
|
14777
|
+
const deliveries = await recoverMembershipAttentionProofs(cloud, identity, opened, mlsPackage);
|
|
14778
|
+
const results = await Promise.allSettled(deliveries.map((delivery) => deliverMembershipRecord(cloud, delivery)));
|
|
14129
14779
|
delivered += results.filter((result) => result.status === "fulfilled").length;
|
|
14130
14780
|
if (results.every((result) => result.status === "fulfilled")) {
|
|
14131
14781
|
await cloud.user.chats.membershipOutbox.delete(identity.uid, record.id);
|
|
@@ -14257,13 +14907,6 @@ async function prepareMembershipTransition(context) {
|
|
|
14257
14907
|
});
|
|
14258
14908
|
if (transitionMessage.msgId !== transitionMessageId)
|
|
14259
14909
|
throw new Error("chat transition message id mismatch");
|
|
14260
|
-
runtime.ownDeliveryCapability = deriveChatDeliveryCapability(runtime.stateCapability, {
|
|
14261
|
-
chatId: parent.chatId,
|
|
14262
|
-
recipientChatPK: identity.chatPK,
|
|
14263
|
-
generation: next.epochVersion
|
|
14264
|
-
});
|
|
14265
|
-
runtime.stage = "register-delivery";
|
|
14266
|
-
await cloud.delivery.register(runtime.ownDeliveryCapability);
|
|
14267
14910
|
runtime.stage = "prepare-deliveries";
|
|
14268
14911
|
const [wakeDeliveries, welcomeDeliveries] = await Promise.all([
|
|
14269
14912
|
makeTransitionWakes(identity, parentState, entry, transitionMessage.msgId, transitionMessage.tsMs),
|
|
@@ -14290,16 +14933,26 @@ async function prepareMembershipTransition(context) {
|
|
|
14290
14933
|
if (current.current.manifest.epochId !== parent.epochId || current.current.manifest.epochVersion !== parent.epochVersion) {
|
|
14291
14934
|
throw new Error("chat owner epoch changed");
|
|
14292
14935
|
}
|
|
14936
|
+
const notificationMode = normalizeChatNotificationMode(current.notificationMode, next);
|
|
14293
14937
|
const nextEntry = makeOwnChatEntry(candidateState, {
|
|
14294
14938
|
ownerRevision: current.ownerRevision,
|
|
14295
|
-
routes: nextRoutes(current, next, runtime.stateCapability),
|
|
14939
|
+
routes: nextRoutes(current, next, runtime.stateCapability, delta.members),
|
|
14296
14940
|
saved: current.saved,
|
|
14297
14941
|
startMs: current.startMs,
|
|
14298
14942
|
deliveryRegistered: true,
|
|
14943
|
+
notificationMode,
|
|
14299
14944
|
notificationTag: notificationChatTag(runtime.stateCapability, parent.chatId, identity.chatPK)
|
|
14300
14945
|
});
|
|
14301
14946
|
return {
|
|
14302
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
|
+
}),
|
|
14303
14956
|
...Number.isFinite(fields.ownerTsMs) ? { tsMs: fields.ownerTsMs } : { touchTs: true },
|
|
14304
14957
|
epochs: [{ id: historyId, record: historyRecord }],
|
|
14305
14958
|
mlsWrites: [{ id: ownerMlsState.stateId, value: ownerMlsState.value }],
|
|
@@ -14442,7 +15095,6 @@ async function transitionChatEpoch(cloud, identityValue, entry, fields = {}) {
|
|
|
14442
15095
|
epochSecret: null,
|
|
14443
15096
|
metrics: {},
|
|
14444
15097
|
nextSnapshot: null,
|
|
14445
|
-
ownDeliveryCapability: "",
|
|
14446
15098
|
parentSnapshot: null,
|
|
14447
15099
|
stage: "read-owner-mls",
|
|
14448
15100
|
stateCapability: null
|
|
@@ -14470,9 +15122,6 @@ async function transitionChatEpoch(cloud, identityValue, entry, fields = {}) {
|
|
|
14470
15122
|
});
|
|
14471
15123
|
throw error;
|
|
14472
15124
|
} finally {
|
|
14473
|
-
if (runtime.ownDeliveryCapability && !runtime.committed) {
|
|
14474
|
-
await cloud.delivery.revoke(runtime.ownDeliveryCapability).catch(() => false);
|
|
14475
|
-
}
|
|
14476
15125
|
cleanBytes(runtime.parentSnapshot, runtime.nextSnapshot, runtime.epochSecret, runtime.stateCapability);
|
|
14477
15126
|
}
|
|
14478
15127
|
}
|
|
@@ -14865,7 +15514,7 @@ function sortedUniqueValues(items) {
|
|
|
14865
15514
|
|
|
14866
15515
|
// ../../core/chat/chats.js
|
|
14867
15516
|
"use client";
|
|
14868
|
-
var
|
|
15517
|
+
var HEX_32_RE4 = /^[0-9a-f]{32}$/i;
|
|
14869
15518
|
var HEX_64_RE = /^[0-9a-f]{64}$/i;
|
|
14870
15519
|
function sameSigningKeys(left, right) {
|
|
14871
15520
|
if (left === right)
|
|
@@ -14889,7 +15538,7 @@ function sameMembers(left, right) {
|
|
|
14889
15538
|
function sameChatShape(a, b) {
|
|
14890
15539
|
if (!a || !b)
|
|
14891
15540
|
return a === b;
|
|
14892
|
-
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);
|
|
14893
15542
|
}
|
|
14894
15543
|
function sameChats(prev, next) {
|
|
14895
15544
|
if (prev.length !== next.length)
|
|
@@ -14963,7 +15612,7 @@ function chatVersionKey(chat) {
|
|
|
14963
15612
|
return "";
|
|
14964
15613
|
}
|
|
14965
15614
|
function isHex32(value) {
|
|
14966
|
-
return
|
|
15615
|
+
return HEX_32_RE4.test(cleanText(value));
|
|
14967
15616
|
}
|
|
14968
15617
|
function isHex64(value) {
|
|
14969
15618
|
return HEX_64_RE.test(cleanText(value));
|
|
@@ -16019,7 +16668,9 @@ function serializeChat(chat) {
|
|
|
16019
16668
|
ts: timestampMs(chat.ts, null, { positive: true }) || 0,
|
|
16020
16669
|
lastUsedAt: Date.now(),
|
|
16021
16670
|
unseen: !!chat.unseen,
|
|
16022
|
-
membershipRemoved: chat.membershipRemoved === true
|
|
16671
|
+
membershipRemoved: chat.membershipRemoved === true,
|
|
16672
|
+
attentionRegistrationVersion: Number(chat.attentionRegistrationVersion) || 0,
|
|
16673
|
+
notificationMode: chat.notificationMode || null
|
|
16023
16674
|
};
|
|
16024
16675
|
}
|
|
16025
16676
|
function reviveChat(chat) {
|
|
@@ -16050,7 +16701,9 @@ function reviveChat(chat) {
|
|
|
16050
16701
|
ts: timestampMs(chat.ts, null, { positive: true }) || 0,
|
|
16051
16702
|
lastUsedAt: timestampMs(chat.lastUsedAt, null, { positive: true }) || 0,
|
|
16052
16703
|
unseen: !!chat.unseen,
|
|
16053
|
-
membershipRemoved: chat.membershipRemoved === true
|
|
16704
|
+
membershipRemoved: chat.membershipRemoved === true,
|
|
16705
|
+
attentionRegistrationVersion: Number(chat.attentionRegistrationVersion) || 0,
|
|
16706
|
+
notificationMode: chat.notificationMode || null
|
|
16054
16707
|
};
|
|
16055
16708
|
}
|
|
16056
16709
|
function isReadableCachedChat(chat) {
|
|
@@ -17453,7 +18106,7 @@ function createChatLive({
|
|
|
17453
18106
|
return sendSnapshot(entry);
|
|
17454
18107
|
}
|
|
17455
18108
|
if (!nextCompositionId) {
|
|
17456
|
-
if (!entry.typing)
|
|
18109
|
+
if (!entry.typing && !entry.compositionId)
|
|
17457
18110
|
return false;
|
|
17458
18111
|
entry.typing = false;
|
|
17459
18112
|
entry.compositionId = "";
|
|
@@ -18056,7 +18709,10 @@ ${cid}` : "";
|
|
|
18056
18709
|
}
|
|
18057
18710
|
if (adopt && !adoptedLocalMediaRef.current.has(mediaKey)) {
|
|
18058
18711
|
adoptedLocalMediaRef.current.add(mediaKey);
|
|
18059
|
-
adoptLocalMessageMedia?.(message, local, {
|
|
18712
|
+
adoptLocalMessageMedia?.(message, local, {
|
|
18713
|
+
chatId,
|
|
18714
|
+
peerChatPK: local.peerChatPK
|
|
18715
|
+
});
|
|
18060
18716
|
}
|
|
18061
18717
|
}
|
|
18062
18718
|
if (changed) {
|
|
@@ -18277,6 +18933,9 @@ ${cid}` : "";
|
|
|
18277
18933
|
const sendOptions = mergeSendOptions(sendOptionsForPeer(peerChatPK), options);
|
|
18278
18934
|
const nextMessage = withMessageRetention(message, sendOptions.retention);
|
|
18279
18935
|
if (isLongTxt(nextMessage)) {
|
|
18936
|
+
if (hasChatMessageMentions(nextMessage)) {
|
|
18937
|
+
throw new Error("messages with mentions must fit in a text message");
|
|
18938
|
+
}
|
|
18280
18939
|
const cid = makeSendCid(nextMessage);
|
|
18281
18940
|
const attachment = makeTxtFileAttachment(nextMessage);
|
|
18282
18941
|
const localMessage = makeLongTxtLocalMessage(chatPK, cid, attachment, nextMessage);
|
|
@@ -19336,9 +19995,12 @@ function normalizeDecryptedMsg(msgData, message, epoch, readContext) {
|
|
|
19336
19995
|
if (!message || typeof message !== "object")
|
|
19337
19996
|
return null;
|
|
19338
19997
|
const epochSigningKeysVersion = signingKeysFingerprint(readContext?.signingKeysByChatKey);
|
|
19998
|
+
const content = normalizeChatMessageMentions(message, epoch?.manifest, {
|
|
19999
|
+
senderChatPK: message.s || message.from
|
|
20000
|
+
});
|
|
19339
20001
|
const normalized = {
|
|
19340
|
-
...
|
|
19341
|
-
id:
|
|
20002
|
+
...content,
|
|
20003
|
+
id: content.id || msgData?.id || null,
|
|
19342
20004
|
ts: msgData?.ts ?? null,
|
|
19343
20005
|
ttl: msgData?.ttl ?? null,
|
|
19344
20006
|
...decryptedEpochFields(epoch),
|
|
@@ -20145,11 +20807,171 @@ async function readMsg({ cloud, messageId, options = {} }) {
|
|
|
20145
20807
|
deletedKeys: []
|
|
20146
20808
|
};
|
|
20147
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
|
+
|
|
20148
20970
|
// ../../core/chat/messages/batches/cleanup.js
|
|
20149
20971
|
function isDenied(error) {
|
|
20150
20972
|
return error?.code === "permission-denied";
|
|
20151
20973
|
}
|
|
20152
|
-
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 }) {
|
|
20153
20975
|
if (chatBanned || !isActive || !ownsMessageCompaction(entry?.memberChatPKs, chatPK) || !entry?.chatId || !entry.peerChatPK || !entry.ready || entry.exists === false || !entry.messages?.length || typeof deleteMessageDocs !== "function") {
|
|
20154
20976
|
return null;
|
|
20155
20977
|
}
|
|
@@ -20161,18 +20983,25 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20161
20983
|
const peerChatPK = entry.peerChatPK;
|
|
20162
20984
|
const generation = entry.generation;
|
|
20163
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;
|
|
20164
20991
|
markDiag(diag, "chat.message.cleanup.schedule", {
|
|
20165
20992
|
chatId,
|
|
20166
20993
|
compactControls,
|
|
20167
20994
|
idle: options.idle !== false,
|
|
20168
20995
|
messages: entry.messages?.length || 0
|
|
20169
20996
|
});
|
|
20170
|
-
|
|
20997
|
+
const operation = Promise.resolve().then(async () => {
|
|
20998
|
+
lease.assertCurrent();
|
|
20171
20999
|
if (options.idle !== false) {
|
|
20172
21000
|
await waitForIdle({
|
|
20173
21001
|
timeout: CHAT_BATCH_CLEANUP_IDLE_TIMEOUT_MS,
|
|
20174
21002
|
delay: CHAT_BATCH_CLEANUP_IDLE_DELAY_MS
|
|
20175
21003
|
});
|
|
21004
|
+
lease.assertCurrent();
|
|
20176
21005
|
}
|
|
20177
21006
|
let current = getCurrentEntry?.(chatId);
|
|
20178
21007
|
if (current !== entry || current.generation !== generation || current.route) {
|
|
@@ -20195,8 +21024,10 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20195
21024
|
return;
|
|
20196
21025
|
}
|
|
20197
21026
|
if (compactControls) {
|
|
21027
|
+
lease.assertCurrent();
|
|
20198
21028
|
const compacted = await compactMessages({
|
|
20199
21029
|
chatId,
|
|
21030
|
+
maintenance,
|
|
20200
21031
|
messages: current.messages.slice(),
|
|
20201
21032
|
deletedKeys,
|
|
20202
21033
|
protectedKeys: compactProtectedKeys,
|
|
@@ -20207,6 +21038,7 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20207
21038
|
if (compacted.length) {
|
|
20208
21039
|
dropped.push(...compacted);
|
|
20209
21040
|
}
|
|
21041
|
+
lease.assertCurrent();
|
|
20210
21042
|
}
|
|
20211
21043
|
if (dropped.length) {
|
|
20212
21044
|
const cacheStartedAt = Date.now();
|
|
@@ -20219,7 +21051,12 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20219
21051
|
startMs: entry.startMs,
|
|
20220
21052
|
messages: dropped,
|
|
20221
21053
|
mode: "confirmed"
|
|
21054
|
+
}).then((result) => {
|
|
21055
|
+
lease.assertCurrent();
|
|
21056
|
+
return result;
|
|
20222
21057
|
}).catch((error) => {
|
|
21058
|
+
if (isStaleMessageMaintenance(error))
|
|
21059
|
+
throw error;
|
|
20223
21060
|
markError(diag, "chat.message.cleanup.cache", cacheStartedAt, error, {
|
|
20224
21061
|
droppedCount: dropped.length
|
|
20225
21062
|
});
|
|
@@ -20241,14 +21078,16 @@ function runMessageBatchCleanup({ entry, options = {}, chatBanned, isActive, cha
|
|
|
20241
21078
|
if (current !== entry || current.generation !== generation || current.route || !dropped.length) {
|
|
20242
21079
|
return;
|
|
20243
21080
|
}
|
|
21081
|
+
lease.assertCurrent();
|
|
20244
21082
|
notify(current);
|
|
20245
21083
|
}).catch((error) => {
|
|
20246
|
-
if (!isDenied(error)) {
|
|
21084
|
+
if (!isDenied(error) && !isStaleMessageMaintenance(error)) {
|
|
20247
21085
|
markError(diag, "chat.message.cleanup", Date.now(), error, {
|
|
20248
21086
|
compactControls
|
|
20249
21087
|
});
|
|
20250
21088
|
}
|
|
20251
|
-
});
|
|
21089
|
+
}).finally(() => lease.release());
|
|
21090
|
+
return maintenance.track(operation);
|
|
20252
21091
|
}
|
|
20253
21092
|
|
|
20254
21093
|
// ../../core/chat/messages/epochhistory.js
|
|
@@ -20814,6 +21653,7 @@ function runBatchCleanup(owner, entry, options = {}) {
|
|
|
20814
21653
|
isActive: sources.isActive,
|
|
20815
21654
|
chatPK: sources.chatPK,
|
|
20816
21655
|
localCache: sources.localCache,
|
|
21656
|
+
maintenance: sources.maintenance,
|
|
20817
21657
|
deleteMessageDocs: sources.deleteMessageDocs,
|
|
20818
21658
|
diag: sources.diag,
|
|
20819
21659
|
getCurrentEntry: (chatId) => owner.batches.get(chatId),
|
|
@@ -21773,6 +22613,9 @@ function messageBatchSources(input = {}) {
|
|
|
21773
22613
|
}
|
|
21774
22614
|
function createChatMessageBatches(input) {
|
|
21775
22615
|
let sources = messageBatchSources(input);
|
|
22616
|
+
if (!sources.maintenance?.claim) {
|
|
22617
|
+
throw new Error("message batches require account maintenance");
|
|
22618
|
+
}
|
|
21776
22619
|
let warming = null;
|
|
21777
22620
|
const routeMemory = createRouteMemory(MESSAGE_VIEW_CACHE_SIZE);
|
|
21778
22621
|
const collection = createMessageBatchCollection({
|
|
@@ -21793,7 +22636,11 @@ function createChatMessageBatches(input) {
|
|
|
21793
22636
|
collection.clear();
|
|
21794
22637
|
};
|
|
21795
22638
|
const setSources = (next) => {
|
|
21796
|
-
|
|
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;
|
|
21797
22644
|
warming.sync();
|
|
21798
22645
|
};
|
|
21799
22646
|
return Object.freeze({
|
|
@@ -22080,7 +22927,7 @@ function epochMessageReadContext(epochState) {
|
|
|
22080
22927
|
async function readEntry(cloud, uid, chatPrivateKey, chatId) {
|
|
22081
22928
|
const entryId = ownChatEntryId(chatPrivateKey, chatId);
|
|
22082
22929
|
const record = await cloud.user.chats.read(uid, entryId);
|
|
22083
|
-
const entry = record ? await
|
|
22930
|
+
const entry = record ? await openOwnChatRecord(chatPrivateKey, entryId, record).catch(() => null) : null;
|
|
22084
22931
|
if (entry && entry.ownerRevision !== record?.revision)
|
|
22085
22932
|
throw new Error("chat owner revision mismatch");
|
|
22086
22933
|
return { entryId, entry };
|
|
@@ -22120,6 +22967,50 @@ function cachedInboxRead(cache, key, read) {
|
|
|
22120
22967
|
function hasBlockedMembers(manifest, blockedUids) {
|
|
22121
22968
|
return blockedUids instanceof Set && blockedUids.size > 0 && manifest.members.some((member) => blockedUids.has(member.uid));
|
|
22122
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
|
+
}
|
|
22123
23014
|
function inboxLeaseError() {
|
|
22124
23015
|
const error = new Error("chat inbox session changed");
|
|
22125
23016
|
error.code = "chat/session-stale";
|
|
@@ -22329,27 +23220,16 @@ function routesForManifest(entry, epochState) {
|
|
|
22329
23220
|
}];
|
|
22330
23221
|
}));
|
|
22331
23222
|
}
|
|
22332
|
-
function
|
|
22333
|
-
return
|
|
23223
|
+
function epochDeliveryRegistration(identity, epochState, notificationMode) {
|
|
23224
|
+
return chatDeliveryRegistration(epochState.stateCapability, {
|
|
23225
|
+
attentionSecret: epochState.epochSecret,
|
|
22334
23226
|
chatId: epochState.manifest.chatId,
|
|
22335
23227
|
recipientChatPK: identity.chatPK,
|
|
22336
|
-
generation: epochState.manifest.epochVersion
|
|
23228
|
+
generation: epochState.manifest.epochVersion,
|
|
23229
|
+
manifest: epochState.manifest,
|
|
23230
|
+
notificationMode
|
|
22337
23231
|
});
|
|
22338
23232
|
}
|
|
22339
|
-
async function registerEpochDelivery(cloud, identity, epochState, previousState, wasRegistered, options) {
|
|
22340
|
-
const capability = epochDeliveryCapability(identity, epochState);
|
|
22341
|
-
assertInboxLease(options);
|
|
22342
|
-
await cloud.delivery.register(capability);
|
|
22343
|
-
if (previousState && wasRegistered) {
|
|
22344
|
-
const oldCapability = deriveChatDeliveryCapability(previousState.stateCapability, {
|
|
22345
|
-
chatId: previousState.manifest.chatId,
|
|
22346
|
-
recipientChatPK: identity.chatPK,
|
|
22347
|
-
generation: previousState.manifest.epochVersion
|
|
22348
|
-
});
|
|
22349
|
-
await cloud.delivery.revoke(oldCapability).catch(() => false);
|
|
22350
|
-
}
|
|
22351
|
-
return true;
|
|
22352
|
-
}
|
|
22353
23233
|
async function readPingMessage(cloud, payload, epochState) {
|
|
22354
23234
|
const messageId = cleanText(payload.messageId);
|
|
22355
23235
|
if (!messageId)
|
|
@@ -22436,7 +23316,7 @@ function sameEntryEpoch(left, right) {
|
|
|
22436
23316
|
return left?.current?.manifest?.epochId === right?.current?.manifest?.epochId && left?.current?.settings?.digest === right?.current?.settings?.digest;
|
|
22437
23317
|
}
|
|
22438
23318
|
function sameEntryRegistration(left, right) {
|
|
22439
|
-
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;
|
|
22440
23320
|
}
|
|
22441
23321
|
function sameEntryRetirement(left, right) {
|
|
22442
23322
|
return left?.retirement?.kind === right?.retirement?.kind && left?.retirement?.epochVersion === right?.retirement?.epochVersion;
|
|
@@ -22468,6 +23348,8 @@ function projectOwnChatEntry(entry, entryId, userChatPK, ts, activity = {}) {
|
|
|
22468
23348
|
settings,
|
|
22469
23349
|
routes: entry.routes,
|
|
22470
23350
|
deliveryRegistered: entry.deliveryRegistered === true,
|
|
23351
|
+
attentionRegistrationVersion: entry.attentionRegistrationVersion,
|
|
23352
|
+
notificationMode: entry.notificationMode,
|
|
22471
23353
|
notificationTag: entry.notificationTag,
|
|
22472
23354
|
epochState: entry.current,
|
|
22473
23355
|
ownEntry: entry,
|
|
@@ -22586,14 +23468,22 @@ async function verifyOpenedPingEpoch(context) {
|
|
|
22586
23468
|
if (epochState.manifest.epochId !== payload.epochId || epochState.manifest.epochVersion !== payload.epochVersion) {
|
|
22587
23469
|
throw new Error("ping epoch mismatch");
|
|
22588
23470
|
}
|
|
23471
|
+
context.epochState = epochState;
|
|
22589
23472
|
if (hasBlockedMembers(epochState.manifest, options.blockedUids)) {
|
|
22590
23473
|
if (context.entry) {
|
|
22591
23474
|
await unlinkBlockedChat(cloud, uid, identity, context.entry, options);
|
|
22592
23475
|
return "deleted";
|
|
22593
23476
|
}
|
|
22594
|
-
|
|
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";
|
|
22595
23486
|
}
|
|
22596
|
-
context.epochState = epochState;
|
|
22597
23487
|
return null;
|
|
22598
23488
|
}
|
|
22599
23489
|
function startOpenedWelcomeHydration(context) {
|
|
@@ -22601,7 +23491,6 @@ function startOpenedWelcomeHydration(context) {
|
|
|
22601
23491
|
if (!membershipWelcome)
|
|
22602
23492
|
return;
|
|
22603
23493
|
context.prefetchedMessage = captureAsync(readPingMessage(context.cloud, context.payload, context.epochState));
|
|
22604
|
-
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));
|
|
22605
23494
|
}
|
|
22606
23495
|
async function applyOpenedPingMessage(context) {
|
|
22607
23496
|
const { cloud, identity, options, payload, uid } = context;
|
|
@@ -22647,19 +23536,12 @@ async function applyOpenedPingMessage(context) {
|
|
|
22647
23536
|
return null;
|
|
22648
23537
|
}
|
|
22649
23538
|
async function prepareOpenedPingOwner(context) {
|
|
22650
|
-
const {
|
|
23539
|
+
const { identity, payload, senderProfile } = context;
|
|
22651
23540
|
const routes = withSenderRoute(context.entry, payload, senderProfile, context.epochState);
|
|
22652
23541
|
const nowMs2 = timestampMs(payload.ts, Date.now()) ?? Date.now();
|
|
22653
23542
|
const membershipWelcome = (!context.entry || context.restoringMembership) && payload.kind === "welcome";
|
|
22654
|
-
const
|
|
22655
|
-
|
|
22656
|
-
if (initialDeliveryCapability)
|
|
22657
|
-
deliveryRegistered = true;
|
|
22658
|
-
if (!deliveryRegistered && context.deliveryRegistration) {
|
|
22659
|
-
deliveryRegistered = await unwrapAsync(context.deliveryRegistration);
|
|
22660
|
-
}
|
|
22661
|
-
if (!deliveryRegistered)
|
|
22662
|
-
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;
|
|
22663
23545
|
const nextEntry = context.entry ? {
|
|
22664
23546
|
...context.entry,
|
|
22665
23547
|
current: {
|
|
@@ -22673,6 +23555,8 @@ async function prepareOpenedPingOwner(context) {
|
|
|
22673
23555
|
},
|
|
22674
23556
|
routes,
|
|
22675
23557
|
deliveryRegistered,
|
|
23558
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
23559
|
+
notificationMode: normalizeChatNotificationMode(context.entry.notificationMode, context.epochState.manifest),
|
|
22676
23560
|
notificationTag: notificationChatTag(context.epochState.stateCapability, payload.chatId, identity.chatPK),
|
|
22677
23561
|
retirement: null
|
|
22678
23562
|
} : makeOwnChatEntry(context.epochState, {
|
|
@@ -22683,7 +23567,7 @@ async function prepareOpenedPingOwner(context) {
|
|
|
22683
23567
|
});
|
|
22684
23568
|
const welcomeMlsState = membershipWelcome && context.epochState.mlsSnapshot ? await prepareOwnerChatMlsSnapshot(identity, context.entryId, context.epochState, context.epochState.mlsSnapshot) : null;
|
|
22685
23569
|
Object.assign(context, {
|
|
22686
|
-
|
|
23570
|
+
initialDeliveryRegistration,
|
|
22687
23571
|
membershipWelcome,
|
|
22688
23572
|
nextEntry,
|
|
22689
23573
|
nowMs: nowMs2,
|
|
@@ -22697,7 +23581,7 @@ async function commitOpenedPingOwner(context) {
|
|
|
22697
23581
|
const {
|
|
22698
23582
|
cloud,
|
|
22699
23583
|
identity,
|
|
22700
|
-
|
|
23584
|
+
initialDeliveryRegistration,
|
|
22701
23585
|
membershipWelcome,
|
|
22702
23586
|
nextEntry,
|
|
22703
23587
|
nowMs: nowMs2,
|
|
@@ -22711,7 +23595,7 @@ async function commitOpenedPingOwner(context) {
|
|
|
22711
23595
|
return {
|
|
22712
23596
|
entry: nextEntry,
|
|
22713
23597
|
tsMs: nowMs2,
|
|
22714
|
-
deliveryRegistration:
|
|
23598
|
+
deliveryRegistration: initialDeliveryRegistration,
|
|
22715
23599
|
...welcomeMlsState ? { mlsWrites: [{ id: welcomeMlsState.stateId, value: welcomeMlsState.value }] } : {}
|
|
22716
23600
|
};
|
|
22717
23601
|
}
|
|
@@ -22726,13 +23610,18 @@ async function commitOpenedPingOwner(context) {
|
|
|
22726
23610
|
current: nextEntry.current,
|
|
22727
23611
|
routes: withSenderRoute(current, payload, senderProfile, context.epochState),
|
|
22728
23612
|
deliveryRegistered: current.deliveryRegistered || nextEntry.deliveryRegistered,
|
|
23613
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
23614
|
+
notificationMode: normalizeChatNotificationMode(current.notificationMode, context.epochState.manifest),
|
|
22729
23615
|
notificationTag: nextEntry.notificationTag,
|
|
22730
23616
|
retirement: membershipWelcome ? null : current.retirement
|
|
22731
23617
|
};
|
|
22732
23618
|
if (sameStableEntry(current, candidate) && !welcomeMlsState)
|
|
22733
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);
|
|
22734
23622
|
return {
|
|
22735
23623
|
entry: candidate,
|
|
23624
|
+
deliveryRegistration,
|
|
22736
23625
|
...membershipWelcome ? { tsMs: nowMs2 } : {},
|
|
22737
23626
|
...welcomeMlsState ? { mlsWrites: [{ id: welcomeMlsState.stateId, value: welcomeMlsState.value }] } : {}
|
|
22738
23627
|
};
|
|
@@ -22744,10 +23633,18 @@ async function commitOpenedPingOwner(context) {
|
|
|
22744
23633
|
}
|
|
22745
23634
|
async function cleanupOpenedPing(context) {
|
|
22746
23635
|
const { cloud, identity, options } = context;
|
|
22747
|
-
|
|
23636
|
+
const membershipWelcome = context.payload.kind === "welcome" && (!context.entry || context.restoringMembership);
|
|
23637
|
+
if (!membershipWelcome || !context.epochState?.mlsSnapshot || context.epochState.mlsKeyPackageLastResort)
|
|
22748
23638
|
return;
|
|
23639
|
+
const keyPackageId = cleanText(context.epochState.mlsKeyPackageId);
|
|
23640
|
+
if (!keyPackageId)
|
|
23641
|
+
return;
|
|
23642
|
+
const consumed = options.consumedMlsKeyPackageIds;
|
|
23643
|
+
if (consumed?.has(keyPackageId))
|
|
23644
|
+
return;
|
|
23645
|
+
consumed?.add(keyPackageId);
|
|
22749
23646
|
assertInboxLease(options);
|
|
22750
|
-
await cloud.user.mls.keyPackages.deleteState(identity.uid,
|
|
23647
|
+
await cloud.user.mls.keyPackages.deleteState(identity.uid, keyPackageId).catch(() => false);
|
|
22751
23648
|
options.onMlsKeyPackageConsumed?.();
|
|
22752
23649
|
}
|
|
22753
23650
|
function openedPingProjection(context, entry) {
|
|
@@ -22814,9 +23711,13 @@ async function persistOpenedPingOwner(context) {
|
|
|
22814
23711
|
});
|
|
22815
23712
|
}
|
|
22816
23713
|
async function settleOpenedPing(prepared, onAuthoritative) {
|
|
22817
|
-
if (prepared.result)
|
|
22818
|
-
return prepared.result;
|
|
22819
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
|
+
}
|
|
22820
23721
|
startOpenedWelcomeHydration(context);
|
|
22821
23722
|
const createsMembership = !context.entry && context.payload.kind === "welcome";
|
|
22822
23723
|
const ownerPersistence = createsMembership ? captureAsync(persistOpenedPingOwner(context)) : null;
|
|
@@ -22841,6 +23742,15 @@ async function settleOpenedPing(prepared, onAuthoritative) {
|
|
|
22841
23742
|
});
|
|
22842
23743
|
return true;
|
|
22843
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
|
+
}
|
|
22844
23754
|
function senderProfilePromise(cache, cloud, payload) {
|
|
22845
23755
|
const key = `${cleanText(payload?.senderUid)}:${cleanText(payload?.senderChatPK)}`;
|
|
22846
23756
|
return cachedInboxRead(cache, key, () => resolveSenderProfile(cloud, payload));
|
|
@@ -22913,13 +23823,15 @@ function welcomeAnnouncementFromPreparedPing(authenticated, prepared, document)
|
|
|
22913
23823
|
members: manifest.members,
|
|
22914
23824
|
settings,
|
|
22915
23825
|
messageId: payload.messageId || null,
|
|
23826
|
+
messageRequest: prepared?.opened?.result === "request",
|
|
23827
|
+
preview: prepared?.opened?.context?.preview || null,
|
|
22916
23828
|
at: ts
|
|
22917
23829
|
};
|
|
22918
23830
|
}
|
|
22919
23831
|
function announcePreparedWelcome(context, authenticated, prepared, document, startedAt) {
|
|
22920
23832
|
const { blockedUids, options, state } = context;
|
|
22921
23833
|
const payload = authenticated?.ping?.payload;
|
|
22922
|
-
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)) {
|
|
22923
23835
|
return () => {};
|
|
22924
23836
|
}
|
|
22925
23837
|
markDiag(options.diag, "chat.inbox.chat.announced", {
|
|
@@ -23071,12 +23983,11 @@ async function commitRemovedEpochTransition(context) {
|
|
|
23071
23983
|
return { removed: true, chatId: parent.chatId, entry: retiredEntry };
|
|
23072
23984
|
}
|
|
23073
23985
|
async function prepareReceivedEpochOwner(context) {
|
|
23074
|
-
const {
|
|
23986
|
+
const { entry, identity, options, parent } = context;
|
|
23075
23987
|
if (hasBlockedMembers(context.epochState.manifest, options.blockedUids)) {
|
|
23076
|
-
context.terminal = await unlinkBlockedChat(cloud, context.uid, identity, entry, options);
|
|
23988
|
+
context.terminal = await unlinkBlockedChat(context.cloud, context.uid, identity, entry, options);
|
|
23077
23989
|
return;
|
|
23078
23990
|
}
|
|
23079
|
-
const deliveryRegistered = await registerEpochDelivery(cloud, identity, context.epochState, entry.current, entry.deliveryRegistered === true, options);
|
|
23080
23991
|
context.nextEntry = {
|
|
23081
23992
|
...entry,
|
|
23082
23993
|
current: {
|
|
@@ -23089,7 +24000,9 @@ async function prepareReceivedEpochOwner(context) {
|
|
|
23089
24000
|
settings: context.epochState.settings
|
|
23090
24001
|
},
|
|
23091
24002
|
routes: routesForManifest(entry, context.epochState),
|
|
23092
|
-
deliveryRegistered,
|
|
24003
|
+
deliveryRegistered: true,
|
|
24004
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
24005
|
+
notificationMode: normalizeChatNotificationMode(entry.notificationMode, context.epochState.manifest),
|
|
23093
24006
|
notificationTag: notificationChatTag(context.epochState.stateCapability, parent.chatId, identity.chatPK)
|
|
23094
24007
|
};
|
|
23095
24008
|
context.historyId = ownEpochEntryId(identity.chatPrivateKey, parent.chatId, parent.epochId);
|
|
@@ -23118,8 +24031,11 @@ async function commitReceivedEpochOwner(context) {
|
|
|
23118
24031
|
current: nextEntry.current,
|
|
23119
24032
|
routes: routesForManifest(current, epochState),
|
|
23120
24033
|
deliveryRegistered: current.deliveryRegistered || nextEntry.deliveryRegistered,
|
|
24034
|
+
attentionRegistrationVersion: CHAT_ATTENTION_REGISTRATION_VERSION,
|
|
24035
|
+
notificationMode: normalizeChatNotificationMode(current.notificationMode, epochState.manifest),
|
|
23121
24036
|
notificationTag: nextEntry.notificationTag
|
|
23122
24037
|
},
|
|
24038
|
+
deliveryRegistration: epochDeliveryRegistration(identity, epochState, current.notificationMode),
|
|
23123
24039
|
touchTs: true,
|
|
23124
24040
|
epochs: [{
|
|
23125
24041
|
id: historyId,
|
|
@@ -23133,8 +24049,17 @@ async function commitReceivedEpochOwner(context) {
|
|
|
23133
24049
|
};
|
|
23134
24050
|
});
|
|
23135
24051
|
}
|
|
23136
|
-
function publishReceivedEpochTransition(context) {
|
|
24052
|
+
async function publishReceivedEpochTransition(context) {
|
|
23137
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
|
+
}
|
|
23138
24063
|
context.options.onTransition?.(projectOwnChatEntry(context.committedEntry, context.entryId, context.identity.chatPK, context.nowMs), {
|
|
23139
24064
|
...context.action,
|
|
23140
24065
|
membershipCommitted: true
|
|
@@ -23397,12 +24322,17 @@ async function prepareInboxWork(context, prepared, processingOptions = {}) {
|
|
|
23397
24322
|
currentChats: state.currentChats,
|
|
23398
24323
|
discoveryStartedAt: prepared.startedAt,
|
|
23399
24324
|
mlsKeyPackageStateCache: context.mlsKeyPackageStateCache,
|
|
24325
|
+
consumedMlsKeyPackageIds: context.consumedMlsKeyPackageIds,
|
|
23400
24326
|
mlsPackageCache: context.mlsPackageCache,
|
|
23401
24327
|
settingsRecordCache: context.settingsRecordCache,
|
|
23402
24328
|
onInboxCommit: state.commitOwner,
|
|
23403
24329
|
onTransition: state.publishTransition,
|
|
23404
|
-
onInboxDelete: state.publishDelete
|
|
24330
|
+
onInboxDelete: state.publishDelete,
|
|
24331
|
+
requestToken: `${document?.slot === true ? "slot" : "ping"}:${cleanText(document?.id)}`
|
|
23405
24332
|
});
|
|
24333
|
+
if (ping.opened?.result === "request") {
|
|
24334
|
+
await hydrateOpenedChatRequest(ping.opened.context);
|
|
24335
|
+
}
|
|
23406
24336
|
announcement = announcePreparedWelcome(context, authenticated, ping, document, prepared.startedAt);
|
|
23407
24337
|
return { announcement, document, ping, prepared, processingOptions, status: null };
|
|
23408
24338
|
} catch (error) {
|
|
@@ -23427,6 +24357,11 @@ async function settleInboxWork(context, work, onAuthoritative) {
|
|
|
23427
24357
|
result: result === true ? "applied" : cleanText(result) || "ignored",
|
|
23428
24358
|
slot: document?.slot === true
|
|
23429
24359
|
});
|
|
24360
|
+
if (result === "request") {
|
|
24361
|
+
options.onInboxRequest?.(work.announcement, document);
|
|
24362
|
+
options.onPingSettled?.(document, result);
|
|
24363
|
+
return "request";
|
|
24364
|
+
}
|
|
23430
24365
|
if (result !== "duplicate")
|
|
23431
24366
|
state.processed += 1;
|
|
23432
24367
|
if (!document.slot || result === "deleted") {
|
|
@@ -23730,6 +24665,7 @@ async function processInbox(cloud, uid, userChatPK, userPrivKey, options = {}) {
|
|
|
23730
24665
|
const context = {
|
|
23731
24666
|
blockedUids,
|
|
23732
24667
|
cloud,
|
|
24668
|
+
consumedMlsKeyPackageIds: new Set,
|
|
23733
24669
|
discoveryPool: createInboxWorkPool(CHAT_INBOX_DISCOVERY_PARALLEL_CHATS),
|
|
23734
24670
|
identity,
|
|
23735
24671
|
mlsKeyPackageStateCache: new Map,
|
|
@@ -23900,6 +24836,8 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
23900
24836
|
let inboxRequestedRetryDelayMs = null;
|
|
23901
24837
|
const priorityInboxDocuments = new Map;
|
|
23902
24838
|
const activeInboxDocuments = new Map;
|
|
24839
|
+
const pendingChatRequests = new Map;
|
|
24840
|
+
const chatRequestResolutions = new Map;
|
|
23903
24841
|
const inboxPendingAttempts = new Map;
|
|
23904
24842
|
const leaveProposalObservedAt = new Map;
|
|
23905
24843
|
let closed = false;
|
|
@@ -23928,8 +24866,17 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
23928
24866
|
const revokeInboxAnnouncement = (chatId, token) => {
|
|
23929
24867
|
if (closed)
|
|
23930
24868
|
return false;
|
|
24869
|
+
if (pendingChatRequests.get(chatId)?.announcement?.token === token) {
|
|
24870
|
+
pendingChatRequests.delete(chatId);
|
|
24871
|
+
}
|
|
23931
24872
|
return options.onInboxRevoke?.(chatId, token) ?? false;
|
|
23932
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
|
+
};
|
|
23933
24880
|
const publishInboxTransition = (chat, message) => {
|
|
23934
24881
|
if (closed)
|
|
23935
24882
|
return false;
|
|
@@ -24007,6 +24954,10 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24007
24954
|
notificationPrivateKey: options.notificationPrivateKey,
|
|
24008
24955
|
mls: options.mls,
|
|
24009
24956
|
blockedUids: options.blockedUids,
|
|
24957
|
+
chatAdmission: options.chatAdmission,
|
|
24958
|
+
incomingChatDecision: options.incomingChatDecision,
|
|
24959
|
+
chatRequestResolutions,
|
|
24960
|
+
onInboxRequest: retainInboxRequest,
|
|
24010
24961
|
sinceMs: inboxSinceMs,
|
|
24011
24962
|
priorityDocuments,
|
|
24012
24963
|
settlementController,
|
|
@@ -24052,7 +25003,7 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24052
25003
|
retryRequested = true;
|
|
24053
25004
|
return true;
|
|
24054
25005
|
},
|
|
24055
|
-
onPingSettled: (doc) => {
|
|
25006
|
+
onPingSettled: (doc, result) => {
|
|
24056
25007
|
const key = inboxRetryKey(doc);
|
|
24057
25008
|
if (key)
|
|
24058
25009
|
inboxPendingAttempts.delete(key);
|
|
@@ -24060,6 +25011,12 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24060
25011
|
if (priorityKey && chatDocDataEqual(priorityInboxDocuments.get(priorityKey), doc)) {
|
|
24061
25012
|
priorityInboxDocuments.delete(priorityKey);
|
|
24062
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
|
+
}
|
|
24063
25020
|
},
|
|
24064
25021
|
onPingStarted: (doc) => {
|
|
24065
25022
|
const key = inboxDocumentKey2(doc);
|
|
@@ -24135,6 +25092,27 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24135
25092
|
setTimeout(processQueuedInbox, 0);
|
|
24136
25093
|
}
|
|
24137
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
|
+
};
|
|
24138
25116
|
const schedule = (delayMs) => {
|
|
24139
25117
|
if (timer || processing || closed) {
|
|
24140
25118
|
return;
|
|
@@ -24194,13 +25172,19 @@ function listenToChats(cloud, uid, userChatPK, userPrivKey, onUpdate, onError, o
|
|
|
24194
25172
|
inboxQueued = false;
|
|
24195
25173
|
priorityInboxDocuments.clear();
|
|
24196
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();
|
|
24197
25180
|
unsub?.();
|
|
24198
25181
|
tokenUnsub?.();
|
|
24199
25182
|
inboxUnsub?.();
|
|
24200
25183
|
};
|
|
24201
25184
|
return Object.freeze({
|
|
24202
25185
|
close,
|
|
24203
|
-
prioritizeChat: (chatId) => settlementController.prioritizeChat(chatId)
|
|
25186
|
+
prioritizeChat: (chatId) => settlementController.prioritizeChat(chatId),
|
|
25187
|
+
resolveChatRequest
|
|
24204
25188
|
});
|
|
24205
25189
|
}
|
|
24206
25190
|
async function loadMoreChats(cloud, uid, userChatPK, userPrivKey, afterChat, pageSize) {
|
|
@@ -24282,7 +25266,7 @@ async function getChat(cloud, uid, chatId, userChatPK, userPrivKey) {
|
|
|
24282
25266
|
}
|
|
24283
25267
|
async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
|
|
24284
25268
|
const data = entryRecord;
|
|
24285
|
-
const entry = await
|
|
25269
|
+
const entry = await openOwnChatRecord(userPrivKey, entryRecord.id, data);
|
|
24286
25270
|
if (entry.ownerRevision !== data?.revision) {
|
|
24287
25271
|
throw new Error("chat owner revision mismatch");
|
|
24288
25272
|
}
|
|
@@ -24320,6 +25304,8 @@ async function decryptChatEntry(entryRecord, userChatPK, userPrivKey) {
|
|
|
24320
25304
|
readMs: null,
|
|
24321
25305
|
startMs,
|
|
24322
25306
|
deliveryRegistered: entry.deliveryRegistered === true,
|
|
25307
|
+
attentionRegistrationVersion: entry.attentionRegistrationVersion,
|
|
25308
|
+
notificationMode: entry.notificationMode,
|
|
24323
25309
|
routes: entry.routes,
|
|
24324
25310
|
peerDeliveryCapability: directPeer ? entry.routes?.[directPeer.chatPK]?.deliveryCapability || null : null,
|
|
24325
25311
|
peerNotificationPK: directPeer?.notificationPK || null,
|
|
@@ -24597,12 +25583,14 @@ function announcementRow(announcement, selfChatPK) {
|
|
|
24597
25583
|
peerChatPK: directPeer?.chatPK || null,
|
|
24598
25584
|
peerUid: directPeer?.uid || null,
|
|
24599
25585
|
settings: announcement.settings || {},
|
|
24600
|
-
preview: null,
|
|
25586
|
+
preview: announcement.preview || null,
|
|
24601
25587
|
readMs: null,
|
|
24602
25588
|
inboxMessageId: announcement.messageId || null,
|
|
24603
25589
|
inboxMessageAt: at,
|
|
24604
25590
|
ts: at,
|
|
24605
|
-
unseen:
|
|
25591
|
+
unseen: announcement.messageRequest === true,
|
|
25592
|
+
messageRequest: announcement.messageRequest === true,
|
|
25593
|
+
requestToken: announcement.token
|
|
24606
25594
|
};
|
|
24607
25595
|
}
|
|
24608
25596
|
function sourceEntry() {
|
|
@@ -24964,6 +25952,8 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
24964
25952
|
settings: retiredEntry.current.settings.values,
|
|
24965
25953
|
routes: retiredEntry.routes,
|
|
24966
25954
|
deliveryRegistered: retiredEntry.deliveryRegistered === true,
|
|
25955
|
+
attentionRegistrationVersion: retiredEntry.attentionRegistrationVersion,
|
|
25956
|
+
notificationMode: retiredEntry.notificationMode,
|
|
24967
25957
|
notificationTag: retiredEntry.notificationTag || null,
|
|
24968
25958
|
epochState: retiredEntry.current,
|
|
24969
25959
|
ownEntry: retiredEntry,
|
|
@@ -25050,6 +26040,8 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
25050
26040
|
peerSigningPublicKey: owner?.signingKeysByChatKey?.[peerChatPK] || "",
|
|
25051
26041
|
signerShared: owner?.signerShared === true,
|
|
25052
26042
|
deliveryRegistered: owner?.deliveryRegistered === true,
|
|
26043
|
+
attentionRegistrationVersion: owner?.attentionRegistrationVersion || 0,
|
|
26044
|
+
notificationMode: owner?.notificationMode || null,
|
|
25053
26045
|
peerDeliveryCapability: owner?.peerDeliveryCapability || "",
|
|
25054
26046
|
peerNotificationPK: owner?.peerNotificationPK || "",
|
|
25055
26047
|
notificationTag: owner?.notificationTag || "",
|
|
@@ -25131,12 +26123,14 @@ function createChatListListener({
|
|
|
25131
26123
|
let listening = false;
|
|
25132
26124
|
let unsubscribeChats = null;
|
|
25133
26125
|
let prioritizeInboxChat = null;
|
|
26126
|
+
let resolveInboxChatRequest = null;
|
|
25134
26127
|
let retryTimer = null;
|
|
25135
26128
|
let updateSequence = 0;
|
|
25136
26129
|
const stop = () => {
|
|
25137
26130
|
unsubscribeChats?.();
|
|
25138
26131
|
unsubscribeChats = null;
|
|
25139
26132
|
prioritizeInboxChat = null;
|
|
26133
|
+
resolveInboxChatRequest = null;
|
|
25140
26134
|
};
|
|
25141
26135
|
const retainAfterError = (error) => {
|
|
25142
26136
|
const sources = getSources();
|
|
@@ -25295,6 +26289,8 @@ function createChatListListener({
|
|
|
25295
26289
|
inboxTransport: sources.inboxTransport,
|
|
25296
26290
|
mls: sources.mls,
|
|
25297
26291
|
blockedUids: sources.blockedUidSet,
|
|
26292
|
+
chatAdmission: sources.chatAdmission,
|
|
26293
|
+
incomingChatDecision: sources.incomingChatDecision,
|
|
25298
26294
|
localCache: sources.localCache,
|
|
25299
26295
|
diag: sources.diag,
|
|
25300
26296
|
getCurrentChat: index.getOwnerChat,
|
|
@@ -25305,6 +26301,7 @@ function createChatListListener({
|
|
|
25305
26301
|
throw new Error("chat list subscription lifecycle required");
|
|
25306
26302
|
}
|
|
25307
26303
|
prioritizeInboxChat = subscription.prioritizeChat;
|
|
26304
|
+
resolveInboxChatRequest = subscription.resolveChatRequest;
|
|
25308
26305
|
unsubscribeChats = () => {
|
|
25309
26306
|
cancelled = true;
|
|
25310
26307
|
markDiag(getSources().diag, "chat.provider.listen.stop", { elapsedMs: Date.now() - listenStartedAt });
|
|
@@ -25322,6 +26319,7 @@ function createChatListListener({
|
|
|
25322
26319
|
},
|
|
25323
26320
|
isListening: () => listening,
|
|
25324
26321
|
prioritizeChat: (chatId) => prioritizeInboxChat?.(chatId) || Promise.resolve(false),
|
|
26322
|
+
resolveChatRequest: (chatId, decision) => resolveInboxChatRequest?.(chatId, decision) || Promise.resolve(false),
|
|
25325
26323
|
start: () => {
|
|
25326
26324
|
if (listening)
|
|
25327
26325
|
return;
|
|
@@ -25922,6 +26920,7 @@ function createChatList(initialSources) {
|
|
|
25922
26920
|
getRow: index.getRow,
|
|
25923
26921
|
markChatRead: index.markChatRead,
|
|
25924
26922
|
reconcileChatEpoch: epoch.reconcileChatEpoch,
|
|
26923
|
+
resolveChatRequest: listener.resolveChatRequest,
|
|
25925
26924
|
sendOptionsForPeer: index.sendOptionsForPeer,
|
|
25926
26925
|
hasChat: paging.hasChat
|
|
25927
26926
|
};
|
|
@@ -26490,20 +27489,24 @@ function memberFromChatIdentity(value) {
|
|
|
26490
27489
|
uid: identity.uid,
|
|
26491
27490
|
chatPK: identity.chatPK,
|
|
26492
27491
|
chatSigningPK: identity.chatSigningPK,
|
|
26493
|
-
notificationPK: identity.notificationPK
|
|
27492
|
+
notificationPK: identity.notificationPK,
|
|
27493
|
+
...value?.admissionCapability ? { admissionCapability: cleanText(value.admissionCapability).toLowerCase() } : {}
|
|
26494
27494
|
};
|
|
26495
27495
|
const mlsLeafId = cleanText(value?.mlsLeafId).toLowerCase();
|
|
26496
27496
|
return /^[0-9a-f]{64}$/u.test(mlsLeafId) ? { ...member, mlsLeafId } : member;
|
|
26497
27497
|
}
|
|
26498
27498
|
async function registerInitialDelivery(cloud, identity, epochState) {
|
|
26499
|
-
const
|
|
27499
|
+
const registration = chatDeliveryRegistration(epochState.stateCapability, {
|
|
27500
|
+
attentionSecret: epochState.epochSecret,
|
|
26500
27501
|
chatId: epochState.manifest.chatId,
|
|
26501
27502
|
recipientChatPK: identity.chatPK,
|
|
26502
|
-
generation: epochState.manifest.epochVersion
|
|
27503
|
+
generation: epochState.manifest.epochVersion,
|
|
27504
|
+
manifest: epochState.manifest
|
|
26503
27505
|
});
|
|
26504
|
-
return cloud.delivery.register(
|
|
27506
|
+
return cloud.delivery.register(registration).then(() => true, () => false);
|
|
26505
27507
|
}
|
|
26506
|
-
function initialRoutes(manifest, stateCapability, ownerChatPK) {
|
|
27508
|
+
function initialRoutes(manifest, stateCapability, ownerChatPK, memberValues = []) {
|
|
27509
|
+
const privateByChatPK = new Map(memberValues.map((member) => [member.chatPK, member]));
|
|
26507
27510
|
return Object.fromEntries(manifest.members.map((member) => [member.chatPK, {
|
|
26508
27511
|
uid: member.uid,
|
|
26509
27512
|
deliveryCapability: manifest.lineage === CHAT_LINEAGES.GROUP || member.chatPK === ownerChatPK ? deriveChatDeliveryCapability(stateCapability, {
|
|
@@ -26512,13 +27515,14 @@ function initialRoutes(manifest, stateCapability, ownerChatPK) {
|
|
|
26512
27515
|
generation: manifest.epochVersion
|
|
26513
27516
|
}) : null,
|
|
26514
27517
|
notificationPK: member.notificationPK,
|
|
27518
|
+
admissionCapability: privateByChatPK.get(member.chatPK)?.admissionCapability || null,
|
|
26515
27519
|
generation: manifest.epochVersion
|
|
26516
27520
|
}]));
|
|
26517
27521
|
}
|
|
26518
27522
|
async function prepareInitialOwner(identity, epochState, fields = {}) {
|
|
26519
27523
|
const entryId = ownChatEntryId(identity.chatPrivateKey, epochState.manifest.chatId);
|
|
26520
27524
|
const entry = makeOwnChatEntry(epochState, {
|
|
26521
|
-
routes: initialRoutes(epochState.manifest, epochState.stateCapability, identity.chatPK),
|
|
27525
|
+
routes: initialRoutes(epochState.manifest, epochState.stateCapability, identity.chatPK, fields.members),
|
|
26522
27526
|
startMs: Date.now(),
|
|
26523
27527
|
deliveryRegistered: fields.deliveryRegistered === true,
|
|
26524
27528
|
notificationTag: notificationChatTag(epochState.stateCapability, epochState.manifest.chatId, identity.chatPK)
|
|
@@ -26533,6 +27537,7 @@ async function prepareInitialOwner(identity, epochState, fields = {}) {
|
|
|
26533
27537
|
entryId,
|
|
26534
27538
|
record: {
|
|
26535
27539
|
body: await sealOwnChatEntry(identity.chatPrivateKey, entryId, entry),
|
|
27540
|
+
notificationBody: await sealOwnChatNotificationPreference(identity.chatPrivateKey, entryId, entry),
|
|
26536
27541
|
revision: entry.ownerRevision,
|
|
26537
27542
|
tsMs
|
|
26538
27543
|
}
|
|
@@ -26632,7 +27637,8 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
26632
27637
|
const stateId = chatMlsStateId(identity.chatPrivateKey, chatId, initial.transitionCommitment);
|
|
26633
27638
|
const ownerMlsState = await sealChatMlsState(identity.chatPrivateKey, stateId, { v: 1, chatId, transitionCommitment: initial.transitionCommitment }, mlsGroup.snapshot);
|
|
26634
27639
|
const owner = await prepareInitialOwner(identity, initialState, {
|
|
26635
|
-
deliveryRegistered: true
|
|
27640
|
+
deliveryRegistered: true,
|
|
27641
|
+
members
|
|
26636
27642
|
});
|
|
26637
27643
|
let stateError = null;
|
|
26638
27644
|
try {
|
|
@@ -26746,7 +27752,7 @@ async function createGroupChat(cloud, identityValue, memberValues, settings = {}
|
|
|
26746
27752
|
|
|
26747
27753
|
// ../../core/chat/direct.js
|
|
26748
27754
|
"use client";
|
|
26749
|
-
function
|
|
27755
|
+
function orderedChatKeys2(first, second) {
|
|
26750
27756
|
return [
|
|
26751
27757
|
cleanChatHex(first, "chat public key"),
|
|
26752
27758
|
cleanChatHex(second, "peer chat public key")
|
|
@@ -26768,7 +27774,7 @@ function deriveDirectRouteId(chatPrivateKey, chatPK, peerChatPK) {
|
|
|
26768
27774
|
try {
|
|
26769
27775
|
peerKey = fromHex(otherChatPK, "peer chat public key");
|
|
26770
27776
|
shared = x25519.getSharedSecret(toBytes32(chatPrivateKey, "chat private key"), peerKey);
|
|
26771
|
-
routeId = deriveKey(shared, "direct-route-id-v3",
|
|
27777
|
+
routeId = deriveKey(shared, "direct-route-id-v3", orderedChatKeys2(ownChatPK, otherChatPK));
|
|
26772
27778
|
return toHex(routeId);
|
|
26773
27779
|
} finally {
|
|
26774
27780
|
cleanBytes(peerKey, shared, routeId);
|
|
@@ -26918,6 +27924,9 @@ function createChatSessionLaunches({
|
|
|
26918
27924
|
setSelectedChat(existingChatId);
|
|
26919
27925
|
return existingChatId;
|
|
26920
27926
|
}
|
|
27927
|
+
if (directAdmissionForPeer(operation.identity.chatPrivateKey, chatPK, profile) === CHAT_ADMISSION_DECISIONS.REJECT) {
|
|
27928
|
+
throw new Error("peer is not accepting direct chats");
|
|
27929
|
+
}
|
|
26921
27930
|
const routeId = directRouteIdForPeer(member.chatPK, operation);
|
|
26922
27931
|
if (operation.pendingOwner.getPendingLaunch()?.id === routeId)
|
|
26923
27932
|
return routeId;
|
|
@@ -26986,6 +27995,13 @@ function createChatSessionLaunches({
|
|
|
26986
27995
|
const profileList = Array.isArray(profiles) ? profiles : [profiles];
|
|
26987
27996
|
const blockedSet = new Set(blocked);
|
|
26988
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
|
+
}
|
|
26989
28005
|
for (const member of requestedMembers) {
|
|
26990
28006
|
if (member.chatPK === chatPK)
|
|
26991
28007
|
throw new Error("current user is already a chat member");
|
|
@@ -27114,6 +28130,9 @@ function createChatSessionLaunches({
|
|
|
27114
28130
|
directOpens.delete(routeId);
|
|
27115
28131
|
return runExistingDirectLifecycle(existingChatId, lifecycle, operation);
|
|
27116
28132
|
}
|
|
28133
|
+
if (directAdmissionForPeer(operation.identity.chatPrivateKey, chatPK, profile) === CHAT_ADMISSION_DECISIONS.REJECT) {
|
|
28134
|
+
throw new Error("peer is not accepting direct chats");
|
|
28135
|
+
}
|
|
27117
28136
|
const resolution = startDirectResolution(routeId, member.chatPK, operation);
|
|
27118
28137
|
const state = { operation, promise: null };
|
|
27119
28138
|
state.promise = resolution.promise.then((resolvedChatId) => {
|
|
@@ -27195,6 +28214,7 @@ function createChatSessionPending({
|
|
|
27195
28214
|
setSelectedChat,
|
|
27196
28215
|
publish,
|
|
27197
28216
|
getOwnerChat,
|
|
28217
|
+
getChatRow,
|
|
27198
28218
|
ensureChat,
|
|
27199
28219
|
profileMember,
|
|
27200
28220
|
transitionIdentity,
|
|
@@ -27559,7 +28579,7 @@ function createChatSessionPending({
|
|
|
27559
28579
|
setSelectedChat(chatId);
|
|
27560
28580
|
return true;
|
|
27561
28581
|
};
|
|
27562
|
-
if (pendingMatches || getOwnerChat(chatId))
|
|
28582
|
+
if (pendingMatches || getOwnerChat(chatId) || getChatRow(chatId)?.messageRequest)
|
|
27563
28583
|
return selectResolved();
|
|
27564
28584
|
return Promise.resolve(ensureChat(chatId)).then((ready) => ready === true && selectResolved());
|
|
27565
28585
|
},
|
|
@@ -27632,11 +28652,13 @@ function createChatSessionMembership({
|
|
|
27632
28652
|
if (!profile?.uid || !profile?.chatPK || !profile?.chatSigningPK || !profile?.notificationPK) {
|
|
27633
28653
|
throw new Error("complete peer chat identity required");
|
|
27634
28654
|
}
|
|
28655
|
+
const identity = getIdentity();
|
|
27635
28656
|
return {
|
|
27636
28657
|
uid: profile.uid,
|
|
27637
28658
|
chatPK: profile.chatPK,
|
|
27638
28659
|
chatSigningPK: profile.chatSigningPK,
|
|
27639
|
-
notificationPK: profile.notificationPK
|
|
28660
|
+
notificationPK: profile.notificationPK,
|
|
28661
|
+
admissionCapability: deriveDirectAdmissionCapability(identity.chatPrivateKey, identity.chatPK, profile.chatPK, profile.chatPK)
|
|
27640
28662
|
};
|
|
27641
28663
|
};
|
|
27642
28664
|
const prepareMlsAdditions = async (profiles, operation = operationContext()) => {
|
|
@@ -27880,7 +28902,12 @@ function createChatSessionMembership({
|
|
|
27880
28902
|
return state.promise;
|
|
27881
28903
|
};
|
|
27882
28904
|
const addChatMembers = (chatId, profiles) => {
|
|
27883
|
-
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);
|
|
27884
28911
|
const blockedSet = new Set(getIdentity().blocked);
|
|
27885
28912
|
for (const member of additions) {
|
|
27886
28913
|
if (blockedSet.has(member.uid))
|
|
@@ -27938,7 +28965,7 @@ function createChatSessionMembership({
|
|
|
27938
28965
|
|
|
27939
28966
|
// ../../core/chat/deletiondelivery.js
|
|
27940
28967
|
"use client";
|
|
27941
|
-
async function deliverChatDeletedPings(cloud, identityValue, epochState, members = [], eventIdValue = "") {
|
|
28968
|
+
async function deliverChatDeletedPings(cloud, identityValue, epochState, members = [], eventIdValue = "", routes = {}) {
|
|
27942
28969
|
const identity = {
|
|
27943
28970
|
...identityValue,
|
|
27944
28971
|
uid: cleanText(identityValue?.uid),
|
|
@@ -27976,6 +29003,7 @@ async function deliverChatDeletedPings(cloud, identityValue, epochState, members
|
|
|
27976
29003
|
deliveries.push({
|
|
27977
29004
|
member,
|
|
27978
29005
|
promise: pushCriticalInbox(cloud, member.uid, ping, {
|
|
29006
|
+
admissionCapability: routes?.[member.chatPK]?.admissionCapability || null,
|
|
27979
29007
|
descriptor,
|
|
27980
29008
|
routeTag: notificationRouteTag(descriptor),
|
|
27981
29009
|
notify: true
|
|
@@ -28179,6 +29207,7 @@ function createChatDelete({
|
|
|
28179
29207
|
stateCapability: serverChat?.epochState?.stateCapability,
|
|
28180
29208
|
epochVersion: serverChat?.epochVersion,
|
|
28181
29209
|
members: serverChat?.members || [],
|
|
29210
|
+
routes: sourceChat?.routes || {},
|
|
28182
29211
|
transitionCommitment: serverChat?.epochState?.transitionCommitment,
|
|
28183
29212
|
ownEntry: sourceChat?.ownEntry || null,
|
|
28184
29213
|
membershipRemoved: sourceChat?.membershipRemoved === true,
|
|
@@ -28225,7 +29254,7 @@ function createChatDelete({
|
|
|
28225
29254
|
},
|
|
28226
29255
|
stateCapability: target.stateCapability,
|
|
28227
29256
|
transitionCommitment: target.transitionCommitment
|
|
28228
|
-
}, target.members, target.expectedEpochId);
|
|
29257
|
+
}, target.members, target.expectedEpochId, target.routes);
|
|
28229
29258
|
return result.deliveredCount;
|
|
28230
29259
|
};
|
|
28231
29260
|
const deleteCurrentChat = async (input, options = {}) => {
|
|
@@ -29356,6 +30385,188 @@ function createChatSessionSettings({
|
|
|
29356
30385
|
};
|
|
29357
30386
|
}
|
|
29358
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
|
+
|
|
29359
30570
|
// ../../core/chat/sessionsources.js
|
|
29360
30571
|
function normalizeChatSessionSources(sources = {}) {
|
|
29361
30572
|
return {
|
|
@@ -29369,11 +30580,12 @@ function normalizeChatSessionSources(sources = {}) {
|
|
|
29369
30580
|
chatSigningSecret: sources.chatSigningSecret || null,
|
|
29370
30581
|
notificationPK: sources.notificationPK || "",
|
|
29371
30582
|
notificationPrivateKey: sources.notificationPrivateKey || "",
|
|
30583
|
+
chatAdmission: sources.chatAdmission || null,
|
|
29372
30584
|
localCache: sources.localCache || null
|
|
29373
30585
|
};
|
|
29374
30586
|
}
|
|
29375
30587
|
function chatSessionAuthChanged(previous, next) {
|
|
29376
|
-
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;
|
|
29377
30589
|
}
|
|
29378
30590
|
|
|
29379
30591
|
// ../../core/chat/session.js
|
|
@@ -29403,15 +30615,21 @@ function createChatSession({
|
|
|
29403
30615
|
chatWarming = false,
|
|
29404
30616
|
preloadMessageMedia,
|
|
29405
30617
|
adoptLocalMessageMedia,
|
|
30618
|
+
refreshAttentionRegistrationsOnStart = false,
|
|
29406
30619
|
chatCrypto = null,
|
|
29407
30620
|
mls = null,
|
|
29408
30621
|
live = null,
|
|
30622
|
+
maintenance,
|
|
30623
|
+
incomingChatDecision = null,
|
|
29409
30624
|
diag = null,
|
|
29410
30625
|
loadOwnerChats = loadAllChats
|
|
29411
30626
|
}, initialSources = {}) {
|
|
29412
30627
|
if (!cloud) {
|
|
29413
30628
|
throw new Error("createChatSession requires cloud");
|
|
29414
30629
|
}
|
|
30630
|
+
if (!maintenance?.claim || !maintenance?.open || !maintenance?.close) {
|
|
30631
|
+
throw new Error("createChatSession requires account message maintenance");
|
|
30632
|
+
}
|
|
29415
30633
|
let {
|
|
29416
30634
|
uid = "",
|
|
29417
30635
|
blocked = [],
|
|
@@ -29423,6 +30641,7 @@ function createChatSession({
|
|
|
29423
30641
|
chatPrivateKey = "",
|
|
29424
30642
|
notificationPK = "",
|
|
29425
30643
|
notificationPrivateKey = "",
|
|
30644
|
+
chatAdmission = null,
|
|
29426
30645
|
localCache = null
|
|
29427
30646
|
} = initialSources;
|
|
29428
30647
|
let isActive = isForegroundAppState(appState?.currentState);
|
|
@@ -29435,6 +30654,9 @@ function createChatSession({
|
|
|
29435
30654
|
let mlsPoolLastCheckedAt = 0;
|
|
29436
30655
|
let mlsPoolGeneration = 0;
|
|
29437
30656
|
let authGeneration = 0;
|
|
30657
|
+
let attentionRegistrationPreparation = null;
|
|
30658
|
+
let attentionRegistrationReadyGeneration = null;
|
|
30659
|
+
let attentionRegistrationReadyOwner = null;
|
|
29438
30660
|
const inboxSource = () => ({
|
|
29439
30661
|
uid,
|
|
29440
30662
|
chatPK,
|
|
@@ -29466,6 +30688,7 @@ function createChatSession({
|
|
|
29466
30688
|
let pendingOwner = null;
|
|
29467
30689
|
let membershipOwner = null;
|
|
29468
30690
|
let settingsOwner = null;
|
|
30691
|
+
let notificationsOwner = null;
|
|
29469
30692
|
let chatListOwner = null;
|
|
29470
30693
|
let unsubscribeChatList = null;
|
|
29471
30694
|
let selectionIntent = 0;
|
|
@@ -29648,11 +30871,48 @@ function createChatSession({
|
|
|
29648
30871
|
diag
|
|
29649
30872
|
});
|
|
29650
30873
|
const drainMembershipOutbox = () => {
|
|
29651
|
-
if (!uid || !chatPK || !chatPrivateKey || !chatSigningPK || !chatSigningSecret)
|
|
29652
|
-
return;
|
|
29653
|
-
|
|
30874
|
+
if (!uid || !chatPK || !chatPrivateKey || !chatSigningPK || !chatSigningSecret) {
|
|
30875
|
+
return Promise.resolve(0);
|
|
30876
|
+
}
|
|
30877
|
+
return drainChatMembershipOutbox(cloud, transitionIdentity()).catch((error) => {
|
|
29654
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;
|
|
29655
30913
|
});
|
|
30914
|
+
attentionRegistrationPreparation = state;
|
|
30915
|
+
return state.promise;
|
|
29656
30916
|
};
|
|
29657
30917
|
const replenishMlsPool = ({ force = false } = {}) => {
|
|
29658
30918
|
if (!uid || !chatPK || !chatPrivateKey || !chatSigningPK || !chatSigningSecret)
|
|
@@ -29731,6 +30991,20 @@ function createChatSession({
|
|
|
29731
30991
|
getChatListOwner: () => chatListOwner,
|
|
29732
30992
|
setLocalByChat
|
|
29733
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
|
+
});
|
|
29734
31008
|
pendingOwner = createChatSessionPending({
|
|
29735
31009
|
beginSelection: beginChatSelection,
|
|
29736
31010
|
getIdentity: () => ({ uid, blocked, chatPK, chatPrivateKey, chatBanned }),
|
|
@@ -29738,6 +31012,7 @@ function createChatSession({
|
|
|
29738
31012
|
setSelectedChat,
|
|
29739
31013
|
publish,
|
|
29740
31014
|
getOwnerChat,
|
|
31015
|
+
getChatRow: (chatId) => chatListOwner.getRow(chatId),
|
|
29741
31016
|
ensureChat,
|
|
29742
31017
|
profileMember: membershipOwner.profileMember,
|
|
29743
31018
|
transitionIdentity,
|
|
@@ -29769,12 +31044,16 @@ function createChatSession({
|
|
|
29769
31044
|
const selectChat = (chatId) => pendingOwner.selectChat(chatId, {
|
|
29770
31045
|
flushPrevious: (previousChatId) => actionOwner.seenActions.flushChatRead(previousChatId)
|
|
29771
31046
|
});
|
|
31047
|
+
const resolveChatRequest = (chatId, decision) => chatListOwner.resolveChatRequest(chatId, decision);
|
|
29772
31048
|
const getPeerChatId = (...args) => launchOwner.getPeerChatId(...args);
|
|
29773
31049
|
const resolvePeerChatId = (...args) => launchOwner.resolvePeerChatId(...args);
|
|
29774
31050
|
const selectPeerChat = (...args) => launchOwner.selectPeerChat(...args);
|
|
29775
31051
|
const openDirectChat = (...args) => launchOwner.openDirectChat(...args);
|
|
29776
31052
|
const openNotesChat = (...args) => launchOwner.openNotesChat(...args);
|
|
29777
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);
|
|
29778
31057
|
const createGroupChat2 = (...args) => launchOwner.createGroupChat(...args);
|
|
29779
31058
|
const selectLocalChat = (...args) => launchOwner.selectLocalChat(...args);
|
|
29780
31059
|
const addChatMembers = (chatId, profiles) => {
|
|
@@ -29792,6 +31071,7 @@ function createChatSession({
|
|
|
29792
31071
|
const leaveOwnedChat = (...args) => membershipOwner.leaveOwnedChat(...args);
|
|
29793
31072
|
const updateChatSettings2 = gateChatMutation((...args) => settingsOwner.updateChatSettings(...args));
|
|
29794
31073
|
const updateChatAvatar = gateChatMutation((...args) => settingsOwner.updateChatAvatar(...args));
|
|
31074
|
+
const setChatNotificationMode = gateChatMutation((...args) => notificationsOwner.setChatNotificationMode(...args));
|
|
29795
31075
|
const updateMessage = (chatId, msgId, newMessage, peerChatPK) => {
|
|
29796
31076
|
if (chatBanned)
|
|
29797
31077
|
throw makeChatUnavailableError();
|
|
@@ -29914,6 +31194,8 @@ function createChatSession({
|
|
|
29914
31194
|
notificationPrivateKey: identity.notificationPrivateKey,
|
|
29915
31195
|
mls,
|
|
29916
31196
|
blockedUids: new Set(identity.blocked),
|
|
31197
|
+
chatAdmission,
|
|
31198
|
+
incomingChatDecision,
|
|
29917
31199
|
scanAllSlots: true,
|
|
29918
31200
|
requireComplete: true
|
|
29919
31201
|
});
|
|
@@ -30117,7 +31399,8 @@ function createChatSession({
|
|
|
30117
31399
|
getChatPreviewKey: getChatPreviewKey2,
|
|
30118
31400
|
writeChatPreview,
|
|
30119
31401
|
chatCrypto,
|
|
30120
|
-
diag
|
|
31402
|
+
diag,
|
|
31403
|
+
maintenance
|
|
30121
31404
|
};
|
|
30122
31405
|
const batchSources = () => ({
|
|
30123
31406
|
cloud,
|
|
@@ -30131,6 +31414,7 @@ function createChatSession({
|
|
|
30131
31414
|
chatBanned,
|
|
30132
31415
|
isActive,
|
|
30133
31416
|
localCache,
|
|
31417
|
+
maintenance,
|
|
30134
31418
|
listRef: lastServerChatsRef,
|
|
30135
31419
|
pendingDeleteIdsRef: actionOwner.deleteActions.pendingDeleteIdsRef,
|
|
30136
31420
|
config: chatWarming,
|
|
@@ -30157,6 +31441,8 @@ function createChatSession({
|
|
|
30157
31441
|
localCache,
|
|
30158
31442
|
isActive,
|
|
30159
31443
|
inboxTransport,
|
|
31444
|
+
chatAdmission,
|
|
31445
|
+
incomingChatDecision,
|
|
30160
31446
|
diag,
|
|
30161
31447
|
selectedChatId: stateOwner.getSelectedChatId(),
|
|
30162
31448
|
selectedChatIdRef,
|
|
@@ -30212,18 +31498,23 @@ function createChatSession({
|
|
|
30212
31498
|
stateOwner.setActions({
|
|
30213
31499
|
loadMoreChats: loadMoreChats2,
|
|
30214
31500
|
selectChat,
|
|
31501
|
+
resolveChatRequest,
|
|
30215
31502
|
getPeerChatId,
|
|
30216
31503
|
resolvePeerChatId,
|
|
30217
31504
|
selectPeerChat,
|
|
30218
31505
|
openDirectChat,
|
|
30219
31506
|
openNotesChat,
|
|
30220
31507
|
openNewChat,
|
|
31508
|
+
directAdmissionForPeer: directAdmissionForPeer2,
|
|
31509
|
+
canStartDirectChat: canStartDirectChat2,
|
|
31510
|
+
canInviteToGroup: canInviteToGroup2,
|
|
30221
31511
|
createGroupChat: createGroupChat2,
|
|
30222
31512
|
addChatMembers,
|
|
30223
31513
|
kickChatMember,
|
|
30224
31514
|
leaveChat,
|
|
30225
31515
|
updateChatSettings: updateChatSettings2,
|
|
30226
31516
|
updateChatAvatar,
|
|
31517
|
+
setChatNotificationMode,
|
|
30227
31518
|
dropChat,
|
|
30228
31519
|
dropUnavailableChat,
|
|
30229
31520
|
deleteChat,
|
|
@@ -30345,6 +31636,7 @@ function createChatSession({
|
|
|
30345
31636
|
if (value) {
|
|
30346
31637
|
if (selectedChatIdRef.current)
|
|
30347
31638
|
enterChatActivity(selectedChatIdRef.current);
|
|
31639
|
+
refreshAttentionRegistrations();
|
|
30348
31640
|
drainMembershipOutbox();
|
|
30349
31641
|
replenishMlsPool();
|
|
30350
31642
|
}
|
|
@@ -30358,7 +31650,7 @@ function createChatSession({
|
|
|
30358
31650
|
};
|
|
30359
31651
|
const setSources = (nextSources = {}) => {
|
|
30360
31652
|
const previousBlockedKey = [...blocked].sort().join("|");
|
|
30361
|
-
const current = { uid, chatPK, chatBanned, chatPrivateKey, chatSigningPK, chatSigningSecret, notificationPK, notificationPrivateKey, localCache };
|
|
31653
|
+
const current = { uid, chatPK, chatBanned, chatPrivateKey, chatSigningPK, chatSigningSecret, notificationPK, notificationPrivateKey, chatAdmission, localCache };
|
|
30362
31654
|
const next = normalizeChatSessionSources(nextSources);
|
|
30363
31655
|
const authChanged = chatSessionAuthChanged(current, next);
|
|
30364
31656
|
if (authChanged) {
|
|
@@ -30376,6 +31668,7 @@ function createChatSession({
|
|
|
30376
31668
|
chatSigningSecret,
|
|
30377
31669
|
notificationPK,
|
|
30378
31670
|
notificationPrivateKey,
|
|
31671
|
+
chatAdmission,
|
|
30379
31672
|
localCache
|
|
30380
31673
|
} = next);
|
|
30381
31674
|
inboxTransport.setSource(inboxSource());
|
|
@@ -30393,6 +31686,7 @@ function createChatSession({
|
|
|
30393
31686
|
}
|
|
30394
31687
|
publish();
|
|
30395
31688
|
if (authChanged && isActive) {
|
|
31689
|
+
refreshAttentionRegistrations();
|
|
30396
31690
|
drainMembershipOutbox();
|
|
30397
31691
|
replenishMlsPool({ force: true });
|
|
30398
31692
|
}
|
|
@@ -30406,10 +31700,13 @@ function createChatSession({
|
|
|
30406
31700
|
const start = () => {
|
|
30407
31701
|
if (started)
|
|
30408
31702
|
return;
|
|
31703
|
+
maintenance.open();
|
|
30409
31704
|
started = true;
|
|
30410
31705
|
unsubscribeChatList = chatListOwner.subscribe(() => {
|
|
30411
31706
|
stateOwner.setListSnapshot(chatListOwner.getSnapshot());
|
|
30412
31707
|
publish();
|
|
31708
|
+
if (serverChatsReadyRef.current)
|
|
31709
|
+
refreshAttentionRegistrations();
|
|
30413
31710
|
});
|
|
30414
31711
|
stateOwner.setListSnapshot(chatListOwner.getSnapshot());
|
|
30415
31712
|
publish();
|
|
@@ -30420,6 +31717,7 @@ function createChatSession({
|
|
|
30420
31717
|
markDiag(diag, "chat.block.retire.failed", { message: error?.message || String(error) });
|
|
30421
31718
|
});
|
|
30422
31719
|
}
|
|
31720
|
+
refreshAttentionRegistrations();
|
|
30423
31721
|
drainMembershipOutbox();
|
|
30424
31722
|
replenishMlsPool({ force: true });
|
|
30425
31723
|
if (appState?.addEventListener) {
|
|
@@ -30428,6 +31726,7 @@ function createChatSession({
|
|
|
30428
31726
|
};
|
|
30429
31727
|
const close = () => {
|
|
30430
31728
|
authGeneration += 1;
|
|
31729
|
+
maintenance.close();
|
|
30431
31730
|
settingsOwner.reset();
|
|
30432
31731
|
mls?.close?.();
|
|
30433
31732
|
if (!started)
|
|
@@ -33180,18 +34479,261 @@ function isPasswordStrengthAcceptable(feedback) {
|
|
|
33180
34479
|
return feedback?.version === PASSWORD_STRENGTH_VERSION && feedback.acceptable === true;
|
|
33181
34480
|
}
|
|
33182
34481
|
|
|
33183
|
-
// ../../core/
|
|
33184
|
-
var
|
|
33185
|
-
var
|
|
33186
|
-
|
|
33187
|
-
|
|
33188
|
-
|
|
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;
|
|
33189
34495
|
}
|
|
33190
|
-
function
|
|
33191
|
-
return
|
|
34496
|
+
function ceilDiv(value, divisor) {
|
|
34497
|
+
return (value + divisor - 1n) / divisor;
|
|
33192
34498
|
}
|
|
33193
|
-
function
|
|
33194
|
-
|
|
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)}`;
|
|
33195
34737
|
}
|
|
33196
34738
|
|
|
33197
34739
|
// ../../core/wallet/bitcoin.js
|
|
@@ -34104,7 +35646,7 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
|
|
|
34104
35646
|
if (!uid || expectedVersion == null || typeof avatarCache?.read !== "function")
|
|
34105
35647
|
return null;
|
|
34106
35648
|
try {
|
|
34107
|
-
const cached = await avatarCache.read(uid);
|
|
35649
|
+
const cached = await avatarCache.read(uid, expectedVersion);
|
|
34108
35650
|
const version = readAvatarVersion(cached?.version);
|
|
34109
35651
|
const url = typeof cached?.url === "string" && cached.url ? cached.url : typeof cached?.source === "string" && cached.source ? cached.source : null;
|
|
34110
35652
|
return version === expectedVersion && url ? url : null;
|
|
@@ -41605,8 +43147,6 @@ function requirePorts(options) {
|
|
|
41605
43147
|
throw new Error("account cloud port required");
|
|
41606
43148
|
if (!options?.defaultNetwork)
|
|
41607
43149
|
throw new Error("account default network required");
|
|
41608
|
-
if (typeof options?.setNetwork !== "function")
|
|
41609
|
-
throw new Error("account network port required");
|
|
41610
43150
|
if (!options?.vaultCrypto)
|
|
41611
43151
|
throw new Error("account vault crypto port required");
|
|
41612
43152
|
if (typeof options?.bootWallet !== "function")
|
|
@@ -41642,8 +43182,7 @@ function openAccount(options = {}) {
|
|
|
41642
43182
|
vaultCrypto,
|
|
41643
43183
|
bootWallet,
|
|
41644
43184
|
bootChat,
|
|
41645
|
-
openLocalCache
|
|
41646
|
-
setNetwork: writeNetwork
|
|
43185
|
+
openLocalCache
|
|
41647
43186
|
} = ports;
|
|
41648
43187
|
let closed = false;
|
|
41649
43188
|
let attempt = 0;
|
|
@@ -41653,26 +43192,46 @@ function openAccount(options = {}) {
|
|
|
41653
43192
|
let closePromise = null;
|
|
41654
43193
|
let state;
|
|
41655
43194
|
let chatSession = null;
|
|
43195
|
+
let revokedUid = null;
|
|
43196
|
+
let revocationPromise = null;
|
|
41656
43197
|
let nextAgreement = agreementContract(options.agreement || CURRENT_AGREEMENT);
|
|
41657
43198
|
const listeners = new Set;
|
|
41658
43199
|
let activeWriteTail = Promise.resolve();
|
|
41659
43200
|
const sessionCloseWork = new Set;
|
|
41660
43201
|
const operationBarrier = createAccountOperationCloseBarrier();
|
|
43202
|
+
const messageMaintenance = createAccountMessageMaintenance({
|
|
43203
|
+
isAccountCurrent: () => !closed
|
|
43204
|
+
});
|
|
43205
|
+
function revokeSession(details = {}) {
|
|
43206
|
+
const { uid = null } = details;
|
|
43207
|
+
const accountUid = uid || state?.uid || null;
|
|
43208
|
+
if (accountUid && revokedUid === accountUid) {
|
|
43209
|
+
return revocationPromise || Promise.resolve();
|
|
43210
|
+
}
|
|
43211
|
+
revokedUid = accountUid;
|
|
43212
|
+
let callback;
|
|
43213
|
+
try {
|
|
43214
|
+
callback = options.onSessionRevoked?.({ ...details, uid: accountUid });
|
|
43215
|
+
} catch (error) {
|
|
43216
|
+
callback = Promise.reject(error);
|
|
43217
|
+
}
|
|
43218
|
+
lock();
|
|
43219
|
+
revocationPromise = Promise.resolve(callback);
|
|
43220
|
+
return revocationPromise;
|
|
43221
|
+
}
|
|
41661
43222
|
const user = options.user || createUser({
|
|
41662
43223
|
cloud,
|
|
41663
43224
|
network: options.network || defaultNetwork,
|
|
41664
43225
|
avatarCache,
|
|
41665
43226
|
diag,
|
|
41666
|
-
onSessionRevoked:
|
|
41667
|
-
lock();
|
|
41668
|
-
options.onSessionRevoked?.({ uid: uid || state?.uid || null });
|
|
41669
|
-
}
|
|
43227
|
+
onSessionRevoked: revokeSession
|
|
41670
43228
|
});
|
|
41671
43229
|
state = emptyState(user.getSnapshot(), options.network || defaultNetwork);
|
|
41672
43230
|
const chat = createChatSession({
|
|
41673
43231
|
...options.chat || {},
|
|
41674
43232
|
cloud,
|
|
41675
|
-
diag: options.chat?.diag || diag
|
|
43233
|
+
diag: options.chat?.diag || diag,
|
|
43234
|
+
maintenance: messageMaintenance
|
|
41676
43235
|
}, chatSources());
|
|
41677
43236
|
const wallet = openWallet({
|
|
41678
43237
|
...options.wallet || {},
|
|
@@ -41701,6 +43260,7 @@ function openAccount(options = {}) {
|
|
|
41701
43260
|
chatPrivateKey: session?.chatPrivateKey || "",
|
|
41702
43261
|
notificationPK: session?.notificationPK || "",
|
|
41703
43262
|
notificationPrivateKey: session?.notificationPrivateKey || "",
|
|
43263
|
+
chatAdmission: currentUser.chatAdmission,
|
|
41704
43264
|
localCache: session?.localCache || null
|
|
41705
43265
|
};
|
|
41706
43266
|
}
|
|
@@ -41780,6 +43340,15 @@ function openAccount(options = {}) {
|
|
|
41780
43340
|
await cloud.user.profile.avatar.set(uid, null);
|
|
41781
43341
|
user.getSnapshot().clearAvatar?.();
|
|
41782
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;
|
|
41783
43352
|
}
|
|
41784
43353
|
});
|
|
41785
43354
|
const push = Object.freeze({
|
|
@@ -41792,6 +43361,56 @@ function openAccount(options = {}) {
|
|
|
41792
43361
|
return cloud.user.push.drop(payload);
|
|
41793
43362
|
}
|
|
41794
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
|
+
});
|
|
41795
43414
|
function emit2(patch) {
|
|
41796
43415
|
if (closed || operationBarrier.suppressPublications)
|
|
41797
43416
|
return state;
|
|
@@ -41849,7 +43468,13 @@ function openAccount(options = {}) {
|
|
|
41849
43468
|
chatSession = null;
|
|
41850
43469
|
closeAccountSession(session);
|
|
41851
43470
|
if (session && typeof options.onSessionClosed === "function") {
|
|
41852
|
-
|
|
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) => {
|
|
41853
43478
|
diag?.("account.session.closed.error", {
|
|
41854
43479
|
code: error?.code || "",
|
|
41855
43480
|
message: error?.message || String(error)
|
|
@@ -41875,13 +43500,17 @@ function openAccount(options = {}) {
|
|
|
41875
43500
|
}
|
|
41876
43501
|
return session;
|
|
41877
43502
|
}
|
|
43503
|
+
async function drainSessionCloseWork() {
|
|
43504
|
+
while (sessionCloseWork.size) {
|
|
43505
|
+
await Promise.allSettled([...sessionCloseWork]);
|
|
43506
|
+
}
|
|
43507
|
+
}
|
|
41878
43508
|
function lock() {
|
|
41879
43509
|
const session = closeSession();
|
|
41880
43510
|
setActive(false);
|
|
41881
43511
|
return session;
|
|
41882
43512
|
}
|
|
41883
43513
|
async function applyNetwork(network) {
|
|
41884
|
-
await writeNetwork(network);
|
|
41885
43514
|
user.setNetwork(network);
|
|
41886
43515
|
emitDomains({ network });
|
|
41887
43516
|
peers.refreshNetwork();
|
|
@@ -41915,6 +43544,24 @@ function openAccount(options = {}) {
|
|
|
41915
43544
|
code: error?.code || "",
|
|
41916
43545
|
message: error?.message || String(error)
|
|
41917
43546
|
});
|
|
43547
|
+
if (isAccountAuthenticationError(error)) {
|
|
43548
|
+
revokeSession({
|
|
43549
|
+
uid,
|
|
43550
|
+
code: error?.code || "",
|
|
43551
|
+
reason: "vault-listener"
|
|
43552
|
+
}).catch((revocationError) => {
|
|
43553
|
+
diag?.("account.session.revoke.error", {
|
|
43554
|
+
code: revocationError?.code || "",
|
|
43555
|
+
message: revocationError?.message || String(revocationError)
|
|
43556
|
+
});
|
|
43557
|
+
}).then(() => cloud.auth.logout()).catch((logoutError) => {
|
|
43558
|
+
diag?.("account.session.revoke.auth.error", {
|
|
43559
|
+
code: logoutError?.code || "",
|
|
43560
|
+
message: logoutError?.message || String(logoutError)
|
|
43561
|
+
});
|
|
43562
|
+
});
|
|
43563
|
+
return;
|
|
43564
|
+
}
|
|
41918
43565
|
lock();
|
|
41919
43566
|
emit2({ vault: null, vaultReady: true, vaultError: error });
|
|
41920
43567
|
});
|
|
@@ -41946,7 +43593,6 @@ function openAccount(options = {}) {
|
|
|
41946
43593
|
writeActive(previousUid, false);
|
|
41947
43594
|
active = { uid: null, value: false };
|
|
41948
43595
|
user.setNetwork(defaultNetwork);
|
|
41949
|
-
Promise.resolve(writeNetwork(defaultNetwork)).catch(() => {});
|
|
41950
43596
|
const currentUser = user.getSnapshot();
|
|
41951
43597
|
const currentUid = currentUser.uid || null;
|
|
41952
43598
|
state = {
|
|
@@ -41964,6 +43610,11 @@ function openAccount(options = {}) {
|
|
|
41964
43610
|
const stopUser = user.subscribe(syncUser);
|
|
41965
43611
|
watchVault(state.uid);
|
|
41966
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();
|
|
41967
43618
|
if (closed)
|
|
41968
43619
|
throw new Error("account closed");
|
|
41969
43620
|
if (state.lockState !== "locked")
|
|
@@ -42307,10 +43958,12 @@ function openAccount(options = {}) {
|
|
|
42307
43958
|
user,
|
|
42308
43959
|
profile,
|
|
42309
43960
|
push,
|
|
43961
|
+
payment,
|
|
42310
43962
|
support,
|
|
42311
43963
|
chat,
|
|
42312
43964
|
wallet,
|
|
42313
43965
|
bitcoin,
|
|
43966
|
+
messageMaintenance,
|
|
42314
43967
|
peers,
|
|
42315
43968
|
closeBarrier: Object.freeze({
|
|
42316
43969
|
acquire: operationBarrier.acquireClose,
|