@glyphteck/veyl 0.77.1 → 0.78.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 +1221 -694
- package/dist/accountprofiles.js +2 -0
- package/dist/auth.js +4 -1
- package/dist/cli.js +1283 -779
- package/dist/index.js +1283 -779
- package/docs/validation.md +8 -0
- package/package.json +1 -1
package/dist/account.js
CHANGED
|
@@ -16835,6 +16835,8 @@ var WALLET_PENDING_TRANSFER_STUCK_RETRY_MS = 10 * MINUTE_MS;
|
|
|
16835
16835
|
var WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS = HOUR_MS;
|
|
16836
16836
|
var WALLET_TRANSFER_CACHE_WRITE_DELAY_MS = 3 * MS_PER_SECOND;
|
|
16837
16837
|
var BITCOIN_FEES_MAX_AGE_MS = 5 * 60000;
|
|
16838
|
+
var CALL_MAX_VIDEO_SOURCES = 4;
|
|
16839
|
+
var CALL_MAX_VIDEO_SOURCES_PER_PARTICIPANT = 1;
|
|
16838
16840
|
|
|
16839
16841
|
// ../../core/calls/wire.js
|
|
16840
16842
|
var CALL_REQUEST_MAX_BYTES = 192 * 1024;
|
|
@@ -17007,144 +17009,6 @@ function sameText(left, right) {
|
|
|
17007
17009
|
return lowerText(left) === lowerText(right);
|
|
17008
17010
|
}
|
|
17009
17011
|
|
|
17010
|
-
// ../../core/utils/time.js
|
|
17011
|
-
function timestampMs(value, fallback = null, options = {}) {
|
|
17012
|
-
let ms = null;
|
|
17013
|
-
if (typeof value?.toMillis === "function") {
|
|
17014
|
-
ms = value.toMillis();
|
|
17015
|
-
} else if (value instanceof Date) {
|
|
17016
|
-
ms = value.getTime();
|
|
17017
|
-
} else if (typeof value?.seconds === "number") {
|
|
17018
|
-
ms = value.seconds * 1000 + Math.floor((value.nanoseconds || 0) / 1e6);
|
|
17019
|
-
} else if (typeof value?._seconds === "number") {
|
|
17020
|
-
ms = value._seconds * 1000 + Math.floor((value._nanoseconds || 0) / 1e6);
|
|
17021
|
-
} else if (Number.isFinite(value)) {
|
|
17022
|
-
ms = value;
|
|
17023
|
-
} else if (options.parseString && typeof value === "string") {
|
|
17024
|
-
const numberMs = Number(value);
|
|
17025
|
-
ms = Number.isFinite(numberMs) ? numberMs : Date.parse(value);
|
|
17026
|
-
}
|
|
17027
|
-
if (!Number.isFinite(ms) || options.positive && ms <= 0) {
|
|
17028
|
-
return fallback;
|
|
17029
|
-
}
|
|
17030
|
-
return ms;
|
|
17031
|
-
}
|
|
17032
|
-
function timestampKey(value) {
|
|
17033
|
-
if (value == null) {
|
|
17034
|
-
return null;
|
|
17035
|
-
}
|
|
17036
|
-
return timestampMs(value, null) ?? String(value);
|
|
17037
|
-
}
|
|
17038
|
-
function makeTimestamp(ms) {
|
|
17039
|
-
return {
|
|
17040
|
-
toMillis() {
|
|
17041
|
-
return ms;
|
|
17042
|
-
},
|
|
17043
|
-
toDate() {
|
|
17044
|
-
return new Date(ms);
|
|
17045
|
-
}
|
|
17046
|
-
};
|
|
17047
|
-
}
|
|
17048
|
-
function twoDigits(value) {
|
|
17049
|
-
return String(value).padStart(2, "0");
|
|
17050
|
-
}
|
|
17051
|
-
function dayKey(date) {
|
|
17052
|
-
return `${date.getFullYear()}-${twoDigits(date.getMonth() + 1)}-${twoDigits(date.getDate())}`;
|
|
17053
|
-
}
|
|
17054
|
-
function localDayKey(value) {
|
|
17055
|
-
const ms = timestampMs(value, null, { parseString: true });
|
|
17056
|
-
if (!Number.isFinite(ms))
|
|
17057
|
-
return "";
|
|
17058
|
-
return dayKey(new Date(ms));
|
|
17059
|
-
}
|
|
17060
|
-
function hourKey(dateOrHour) {
|
|
17061
|
-
const hour = dateOrHour instanceof Date ? dateOrHour.getHours() : dateOrHour;
|
|
17062
|
-
return twoDigits(hour);
|
|
17063
|
-
}
|
|
17064
|
-
function dayHourKey(date) {
|
|
17065
|
-
return `${dayKey(date)}-${hourKey(date)}`;
|
|
17066
|
-
}
|
|
17067
|
-
var MINUTE_MS2 = 60000;
|
|
17068
|
-
var HOUR_MS2 = 60 * MINUTE_MS2;
|
|
17069
|
-
function nextLocalDayStartMs(ms) {
|
|
17070
|
-
const date = new Date(ms);
|
|
17071
|
-
date.setDate(date.getDate() + 1);
|
|
17072
|
-
date.setHours(0, 0, 0, 0);
|
|
17073
|
-
return date.getTime();
|
|
17074
|
-
}
|
|
17075
|
-
function nextRowDateTimeRefreshMs(value, now = Date.now()) {
|
|
17076
|
-
const ms = timestampMs(value, null, { parseString: true });
|
|
17077
|
-
const nowMs2 = timestampMs(now, Date.now(), { parseString: true });
|
|
17078
|
-
if (!Number.isFinite(ms) || !Number.isFinite(nowMs2))
|
|
17079
|
-
return null;
|
|
17080
|
-
const age = nowMs2 - ms;
|
|
17081
|
-
if (age < -MINUTE_MS2)
|
|
17082
|
-
return ms - MINUTE_MS2;
|
|
17083
|
-
if (age < MINUTE_MS2)
|
|
17084
|
-
return ms + MINUTE_MS2;
|
|
17085
|
-
if (age < HOUR_MS2)
|
|
17086
|
-
return ms + (Math.floor(age / MINUTE_MS2) + 1) * MINUTE_MS2;
|
|
17087
|
-
if (localDayKey(ms) === localDayKey(nowMs2))
|
|
17088
|
-
return nextLocalDayStartMs(nowMs2);
|
|
17089
|
-
return null;
|
|
17090
|
-
}
|
|
17091
|
-
function nextRelativeTimeRefreshMs(value, now = Date.now()) {
|
|
17092
|
-
const ms = timestampMs(value, null, { parseString: true });
|
|
17093
|
-
const nowMs2 = timestampMs(now, Date.now(), { parseString: true });
|
|
17094
|
-
if (!Number.isFinite(ms) || !Number.isFinite(nowMs2))
|
|
17095
|
-
return null;
|
|
17096
|
-
const age = Math.max(0, nowMs2 - ms);
|
|
17097
|
-
if (age < 30000)
|
|
17098
|
-
return ms + 30000;
|
|
17099
|
-
if (age < 60 * 60000)
|
|
17100
|
-
return ms + (Math.floor(age / 60000) + 1) * 60000;
|
|
17101
|
-
if (age < 24 * 60 * 60000)
|
|
17102
|
-
return ms + (Math.floor(age / (60 * 60000)) + 1) * 60 * 60000;
|
|
17103
|
-
return ms + (Math.floor(age / (24 * 60 * 60000)) + 1) * 24 * 60 * 60000;
|
|
17104
|
-
}
|
|
17105
|
-
|
|
17106
|
-
// ../../core/chat/state.js
|
|
17107
|
-
function makeCid() {
|
|
17108
|
-
return `${Date.now().toString(36)}${toHex(randomBytes3(3))}`;
|
|
17109
|
-
}
|
|
17110
|
-
function getMessageKey(message) {
|
|
17111
|
-
return message?.cid || message?.id || null;
|
|
17112
|
-
}
|
|
17113
|
-
function getCidMs(cid) {
|
|
17114
|
-
if (typeof cid !== "string" || !/^[0-9a-z]+[0-9a-f]{6}$/u.test(cid)) {
|
|
17115
|
-
return null;
|
|
17116
|
-
}
|
|
17117
|
-
const base = cid.slice(0, -6);
|
|
17118
|
-
const ms = Number.parseInt(base, 36);
|
|
17119
|
-
return Number.isSafeInteger(ms) && ms > 0 ? ms : null;
|
|
17120
|
-
}
|
|
17121
|
-
function getMessageOrderMs(message) {
|
|
17122
|
-
return getCidMs(message?.cid) ?? timestampMs(message?.ts, Infinity);
|
|
17123
|
-
}
|
|
17124
|
-
function sortMessages(messages) {
|
|
17125
|
-
return [...messages].sort((a, b) => {
|
|
17126
|
-
const aMs = getMessageOrderMs(a);
|
|
17127
|
-
const bMs = getMessageOrderMs(b);
|
|
17128
|
-
if (aMs !== bMs) {
|
|
17129
|
-
return aMs - bMs;
|
|
17130
|
-
}
|
|
17131
|
-
return String(a?.id || "").localeCompare(String(b?.id || ""));
|
|
17132
|
-
});
|
|
17133
|
-
}
|
|
17134
|
-
function mergeMessages(...groups) {
|
|
17135
|
-
const merged = new Map;
|
|
17136
|
-
for (const group of groups) {
|
|
17137
|
-
for (const message of group || []) {
|
|
17138
|
-
const key = getMessageKey(message);
|
|
17139
|
-
if (!key) {
|
|
17140
|
-
continue;
|
|
17141
|
-
}
|
|
17142
|
-
merged.set(key, message);
|
|
17143
|
-
}
|
|
17144
|
-
}
|
|
17145
|
-
return sortMessages([...merged.values()]);
|
|
17146
|
-
}
|
|
17147
|
-
|
|
17148
17012
|
// ../../core/chat/messages/call.js
|
|
17149
17013
|
var CALL_MSG_TYPE = "call";
|
|
17150
17014
|
function readCallMessage(message) {
|
|
@@ -17162,13 +17026,6 @@ function callMessageText(message) {
|
|
|
17162
17026
|
const call = readCallMessage(message);
|
|
17163
17027
|
return call ? `call ${call.event}` : "";
|
|
17164
17028
|
}
|
|
17165
|
-
function latestCallStartKey(messages) {
|
|
17166
|
-
for (let index = messages.length - 1;index >= 0; index -= 1) {
|
|
17167
|
-
if (readCallMessage(messages[index])?.event === "started")
|
|
17168
|
-
return getMessageKey(messages[index]);
|
|
17169
|
-
}
|
|
17170
|
-
return "";
|
|
17171
|
-
}
|
|
17172
17029
|
|
|
17173
17030
|
// ../../core/chat/messages/types.js
|
|
17174
17031
|
var ATTACHMENT_MSG_TYPES = ["img", "gif", "m4a", "mp4", "file"];
|
|
@@ -17701,6 +17558,7 @@ var domains = Object.freeze({
|
|
|
17701
17558
|
root: ROOT_DOMAIN,
|
|
17702
17559
|
rootDev: `dev.${ROOT_DOMAIN}`,
|
|
17703
17560
|
veyl: `veyl.${ROOT_DOMAIN}`,
|
|
17561
|
+
veylOpen: `open.veyl.${ROOT_DOMAIN}`,
|
|
17704
17562
|
veylDev: `dev.veyl.${ROOT_DOMAIN}`,
|
|
17705
17563
|
live: `live.veyl.${ROOT_DOMAIN}`,
|
|
17706
17564
|
liveDev: `live.dev.veyl.${ROOT_DOMAIN}`,
|
|
@@ -17719,6 +17577,7 @@ var origins = Object.freeze({
|
|
|
17719
17577
|
root: `https://${domains.root}`,
|
|
17720
17578
|
rootDev: `https://${domains.rootDev}`,
|
|
17721
17579
|
veyl: `https://${domains.veyl}`,
|
|
17580
|
+
veylOpen: `https://${domains.veylOpen}`,
|
|
17722
17581
|
veylDev: `https://${domains.veylDev}`,
|
|
17723
17582
|
veylDevWeb: getVeylDevWebOrigin(VEYL_DEV_WEB_PORT_MIN)
|
|
17724
17583
|
});
|
|
@@ -17733,7 +17592,8 @@ var appDomains = Object.freeze([
|
|
|
17733
17592
|
]);
|
|
17734
17593
|
var appLinkDomains = Object.freeze([
|
|
17735
17594
|
domains.veyl,
|
|
17736
|
-
domains.veylDev
|
|
17595
|
+
domains.veylDev,
|
|
17596
|
+
domains.veylOpen
|
|
17737
17597
|
]);
|
|
17738
17598
|
|
|
17739
17599
|
// ../../core/network.js
|
|
@@ -17761,7 +17621,6 @@ function isAddressOnNetwork(address, network) {
|
|
|
17761
17621
|
|
|
17762
17622
|
// ../../core/settings.js
|
|
17763
17623
|
var SEND_ON_SCAN_ENABLED = false;
|
|
17764
|
-
var WEB_LAYOUTS = ["floating", "sidebar"];
|
|
17765
17624
|
var WEB_SIDEBAR_WIDTH = { default: 320, min: 64, max: 480 };
|
|
17766
17625
|
var DEFAULT_WEB_CHAT_LAYOUT = Object.freeze({ open: null, width: null });
|
|
17767
17626
|
var WALLET_NETWORKS = new Set([MAINNET_NETWORK, REGTEST_NETWORK]);
|
|
@@ -17774,7 +17633,6 @@ class SettingsValidationError extends Error {
|
|
|
17774
17633
|
}
|
|
17775
17634
|
var defaultSettings = {
|
|
17776
17635
|
glass: true,
|
|
17777
|
-
webLayout: "floating",
|
|
17778
17636
|
sidebarWidth: WEB_SIDEBAR_WIDTH.default,
|
|
17779
17637
|
walletChartHeight: null,
|
|
17780
17638
|
webChatLayouts: [],
|
|
@@ -17889,9 +17747,6 @@ function normalizeSettings(settings, base = defaultSettings) {
|
|
|
17889
17747
|
if (!MONEY_FORMATS.includes(next.moneyFormat)) {
|
|
17890
17748
|
throw new SettingsValidationError("money-format", "bad moneyFormat");
|
|
17891
17749
|
}
|
|
17892
|
-
if (!WEB_LAYOUTS.includes(next.webLayout)) {
|
|
17893
|
-
throw new SettingsValidationError("web-layout", "bad webLayout");
|
|
17894
|
-
}
|
|
17895
17750
|
if (typeof next.glass !== "boolean") {
|
|
17896
17751
|
throw new SettingsValidationError("glass", "glass must be boolean");
|
|
17897
17752
|
}
|
|
@@ -17913,7 +17768,7 @@ function normalizeSettings(settings, base = defaultSettings) {
|
|
|
17913
17768
|
if (next.faceID !== null && typeof next.faceID !== "boolean") {
|
|
17914
17769
|
throw new SettingsValidationError("face-id", "faceID must be boolean or null");
|
|
17915
17770
|
}
|
|
17916
|
-
return next;
|
|
17771
|
+
return Object.fromEntries(Object.keys(defaultSettings).map((key) => [key, next[key]]));
|
|
17917
17772
|
}
|
|
17918
17773
|
function normalizeCallAudio(patch, base = defaultSettings.callAudio) {
|
|
17919
17774
|
if (patch !== undefined && (!patch || typeof patch !== "object" || Array.isArray(patch)))
|
|
@@ -19360,7 +19215,14 @@ async function openVaultAccountSession(vault, password, options = {}) {
|
|
|
19360
19215
|
return Promise.reject(new Error("account session closed"));
|
|
19361
19216
|
if (connecting)
|
|
19362
19217
|
return connecting;
|
|
19218
|
+
let releaseBoot;
|
|
19363
19219
|
connecting = (async () => {
|
|
19220
|
+
const walletStartedAt = Date.now();
|
|
19221
|
+
mark(diag, "vault.unlock.wallet.start", { source });
|
|
19222
|
+
const boot = startWalletBoot(bootWallet, mnemonicFromWalletEntropy(sessionWalletEntropy), currentUser, { network, diag });
|
|
19223
|
+
walletBoot = boot;
|
|
19224
|
+
releaseBoot = session.own(boot);
|
|
19225
|
+
boot.result.catch(() => {});
|
|
19364
19226
|
const authStartedAt = Date.now();
|
|
19365
19227
|
mark(diag, "vault.unlock.session.start", { source });
|
|
19366
19228
|
await cloud.auth.user.getIdToken(true);
|
|
@@ -19394,11 +19256,6 @@ async function openVaultAccountSession(vault, password, options = {}) {
|
|
|
19394
19256
|
requireCurrent(isCurrent);
|
|
19395
19257
|
chatIdentitiesRegistered = true;
|
|
19396
19258
|
}
|
|
19397
|
-
const walletStartedAt = Date.now();
|
|
19398
|
-
mark(diag, "vault.unlock.wallet.start", { source });
|
|
19399
|
-
const boot = startWalletBoot(bootWallet, mnemonicFromWalletEntropy(sessionWalletEntropy), currentUser, { network, diag });
|
|
19400
|
-
walletBoot = boot;
|
|
19401
|
-
const releaseBoot = session.own(boot);
|
|
19402
19259
|
const walletReady = boot.result.then(async (walletIdentity) => {
|
|
19403
19260
|
if (session.closed)
|
|
19404
19261
|
throw new Error("account session closed");
|
|
@@ -19430,6 +19287,7 @@ async function openVaultAccountSession(vault, password, options = {}) {
|
|
|
19430
19287
|
walletReady.catch(() => {});
|
|
19431
19288
|
return { walletReady };
|
|
19432
19289
|
})().catch((error) => {
|
|
19290
|
+
releaseBoot?.();
|
|
19433
19291
|
connecting = null;
|
|
19434
19292
|
throw error;
|
|
19435
19293
|
});
|
|
@@ -19475,6 +19333,102 @@ function hasAgreement(user, contract = CURRENT_AGREEMENT) {
|
|
|
19475
19333
|
return isAgreementAccepted(user?.agreement, contract);
|
|
19476
19334
|
}
|
|
19477
19335
|
|
|
19336
|
+
// ../../core/utils/time.js
|
|
19337
|
+
function timestampMs(value, fallback = null, options = {}) {
|
|
19338
|
+
let ms = null;
|
|
19339
|
+
if (typeof value?.toMillis === "function") {
|
|
19340
|
+
ms = value.toMillis();
|
|
19341
|
+
} else if (value instanceof Date) {
|
|
19342
|
+
ms = value.getTime();
|
|
19343
|
+
} else if (typeof value?.seconds === "number") {
|
|
19344
|
+
ms = value.seconds * 1000 + Math.floor((value.nanoseconds || 0) / 1e6);
|
|
19345
|
+
} else if (typeof value?._seconds === "number") {
|
|
19346
|
+
ms = value._seconds * 1000 + Math.floor((value._nanoseconds || 0) / 1e6);
|
|
19347
|
+
} else if (Number.isFinite(value)) {
|
|
19348
|
+
ms = value;
|
|
19349
|
+
} else if (options.parseString && typeof value === "string") {
|
|
19350
|
+
const numberMs = Number(value);
|
|
19351
|
+
ms = Number.isFinite(numberMs) ? numberMs : Date.parse(value);
|
|
19352
|
+
}
|
|
19353
|
+
if (!Number.isFinite(ms) || options.positive && ms <= 0) {
|
|
19354
|
+
return fallback;
|
|
19355
|
+
}
|
|
19356
|
+
return ms;
|
|
19357
|
+
}
|
|
19358
|
+
function timestampKey(value) {
|
|
19359
|
+
if (value == null) {
|
|
19360
|
+
return null;
|
|
19361
|
+
}
|
|
19362
|
+
return timestampMs(value, null) ?? String(value);
|
|
19363
|
+
}
|
|
19364
|
+
function makeTimestamp(ms) {
|
|
19365
|
+
return {
|
|
19366
|
+
toMillis() {
|
|
19367
|
+
return ms;
|
|
19368
|
+
},
|
|
19369
|
+
toDate() {
|
|
19370
|
+
return new Date(ms);
|
|
19371
|
+
}
|
|
19372
|
+
};
|
|
19373
|
+
}
|
|
19374
|
+
function twoDigits(value) {
|
|
19375
|
+
return String(value).padStart(2, "0");
|
|
19376
|
+
}
|
|
19377
|
+
function dayKey(date) {
|
|
19378
|
+
return `${date.getFullYear()}-${twoDigits(date.getMonth() + 1)}-${twoDigits(date.getDate())}`;
|
|
19379
|
+
}
|
|
19380
|
+
function localDayKey(value) {
|
|
19381
|
+
const ms = timestampMs(value, null, { parseString: true });
|
|
19382
|
+
if (!Number.isFinite(ms))
|
|
19383
|
+
return "";
|
|
19384
|
+
return dayKey(new Date(ms));
|
|
19385
|
+
}
|
|
19386
|
+
function hourKey(dateOrHour) {
|
|
19387
|
+
const hour = dateOrHour instanceof Date ? dateOrHour.getHours() : dateOrHour;
|
|
19388
|
+
return twoDigits(hour);
|
|
19389
|
+
}
|
|
19390
|
+
function dayHourKey(date) {
|
|
19391
|
+
return `${dayKey(date)}-${hourKey(date)}`;
|
|
19392
|
+
}
|
|
19393
|
+
var MINUTE_MS2 = 60000;
|
|
19394
|
+
var HOUR_MS2 = 60 * MINUTE_MS2;
|
|
19395
|
+
function nextLocalDayStartMs(ms) {
|
|
19396
|
+
const date = new Date(ms);
|
|
19397
|
+
date.setDate(date.getDate() + 1);
|
|
19398
|
+
date.setHours(0, 0, 0, 0);
|
|
19399
|
+
return date.getTime();
|
|
19400
|
+
}
|
|
19401
|
+
function nextRowDateTimeRefreshMs(value, now = Date.now()) {
|
|
19402
|
+
const ms = timestampMs(value, null, { parseString: true });
|
|
19403
|
+
const nowMs2 = timestampMs(now, Date.now(), { parseString: true });
|
|
19404
|
+
if (!Number.isFinite(ms) || !Number.isFinite(nowMs2))
|
|
19405
|
+
return null;
|
|
19406
|
+
const age = nowMs2 - ms;
|
|
19407
|
+
if (age < -MINUTE_MS2)
|
|
19408
|
+
return ms - MINUTE_MS2;
|
|
19409
|
+
if (age < MINUTE_MS2)
|
|
19410
|
+
return ms + MINUTE_MS2;
|
|
19411
|
+
if (age < HOUR_MS2)
|
|
19412
|
+
return ms + (Math.floor(age / MINUTE_MS2) + 1) * MINUTE_MS2;
|
|
19413
|
+
if (localDayKey(ms) === localDayKey(nowMs2))
|
|
19414
|
+
return nextLocalDayStartMs(nowMs2);
|
|
19415
|
+
return null;
|
|
19416
|
+
}
|
|
19417
|
+
function nextRelativeTimeRefreshMs(value, now = Date.now()) {
|
|
19418
|
+
const ms = timestampMs(value, null, { parseString: true });
|
|
19419
|
+
const nowMs2 = timestampMs(now, Date.now(), { parseString: true });
|
|
19420
|
+
if (!Number.isFinite(ms) || !Number.isFinite(nowMs2))
|
|
19421
|
+
return null;
|
|
19422
|
+
const age = Math.max(0, nowMs2 - ms);
|
|
19423
|
+
if (age < 30000)
|
|
19424
|
+
return ms + 30000;
|
|
19425
|
+
if (age < 60 * 60000)
|
|
19426
|
+
return ms + (Math.floor(age / 60000) + 1) * 60000;
|
|
19427
|
+
if (age < 24 * 60 * 60000)
|
|
19428
|
+
return ms + (Math.floor(age / (60 * 60000)) + 1) * 60 * 60000;
|
|
19429
|
+
return ms + (Math.floor(age / (24 * 60 * 60000)) + 1) * 24 * 60 * 60000;
|
|
19430
|
+
}
|
|
19431
|
+
|
|
19478
19432
|
// ../../core/moderation.js
|
|
19479
19433
|
function banUntilMs(ban) {
|
|
19480
19434
|
if (!ban || typeof ban !== "object" || Array.isArray(ban) || ban.until == null) {
|
|
@@ -20003,6 +19957,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20003
19957
|
let avatarPreview;
|
|
20004
19958
|
let blockedSource = state.blocked;
|
|
20005
19959
|
let blockedSet = new Set(blockedSource);
|
|
19960
|
+
const blockedChanges = new Map;
|
|
20006
19961
|
let banTimer = null;
|
|
20007
19962
|
let unsubscribeAuth = NOOP;
|
|
20008
19963
|
let userWatches = [];
|
|
@@ -20016,11 +19971,27 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20016
19971
|
function makeSnapshot() {
|
|
20017
19972
|
const bans = banState(state.banned);
|
|
20018
19973
|
const userBan = bans.full || bans.chat;
|
|
19974
|
+
const visibleBlocked = new Set(state.blocked);
|
|
19975
|
+
const blockedPending = [];
|
|
19976
|
+
for (const [uid, change] of blockedChanges) {
|
|
19977
|
+
if (change.blocked)
|
|
19978
|
+
visibleBlocked.add(uid);
|
|
19979
|
+
else
|
|
19980
|
+
visibleBlocked.delete(uid);
|
|
19981
|
+
if (!change.written)
|
|
19982
|
+
blockedPending.push(uid);
|
|
19983
|
+
}
|
|
19984
|
+
if (visibleBlocked.size !== blockedSet.size || [...visibleBlocked].some((uid) => !blockedSet.has(uid))) {
|
|
19985
|
+
blockedSet = visibleBlocked;
|
|
19986
|
+
}
|
|
19987
|
+
const blocked = snapshot?.blocked && snapshot.blocked.length === blockedSet.size && snapshot.blocked.every((uid) => blockedSet.has(uid)) ? snapshot.blocked : [...blockedSet];
|
|
20019
19988
|
return {
|
|
20020
19989
|
...state,
|
|
20021
19990
|
avatar: avatarPreview === undefined ? state.avatar : avatarPreview,
|
|
20022
19991
|
network: activeNetwork,
|
|
20023
19992
|
blockedSet,
|
|
19993
|
+
blocked,
|
|
19994
|
+
blockedPending: snapshot?.blockedPending && snapshot.blockedPending.length === blockedPending.length && snapshot.blockedPending.every((uid, index) => uid === blockedPending[index]) ? snapshot.blockedPending : blockedPending,
|
|
20024
19995
|
chatBanned: bans.chatBanned,
|
|
20025
19996
|
avatarBanned: bans.avatarBanned,
|
|
20026
19997
|
chatBanUntil: userBan?.until ?? null,
|
|
@@ -20304,6 +20275,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20304
20275
|
const authStartedAt = Date.now();
|
|
20305
20276
|
const session = authSession + 1;
|
|
20306
20277
|
authSession = session;
|
|
20278
|
+
blockedChanges.clear();
|
|
20307
20279
|
closeUserWatches();
|
|
20308
20280
|
markDiag(diag, "user.auth.state", { signedIn: !!authUser });
|
|
20309
20281
|
if (!authUser) {
|
|
@@ -20403,6 +20375,20 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20403
20375
|
setState((user) => ({ ...user, banned: null }));
|
|
20404
20376
|
})));
|
|
20405
20377
|
userWatches.push(cloud.user.blocked.watch(authUser.uid, current((blocked, info = {}) => {
|
|
20378
|
+
if (info.pending || info.fromCache && state.blockedReady)
|
|
20379
|
+
return;
|
|
20380
|
+
const confirmed = new Set(blocked);
|
|
20381
|
+
for (const [uid, change] of blockedChanges) {
|
|
20382
|
+
if (!change.written)
|
|
20383
|
+
continue;
|
|
20384
|
+
if (!info.fromCache && confirmed.has(uid) === change.blocked)
|
|
20385
|
+
blockedChanges.delete(uid);
|
|
20386
|
+
else if (state.blocked.includes(uid))
|
|
20387
|
+
confirmed.add(uid);
|
|
20388
|
+
else
|
|
20389
|
+
confirmed.delete(uid);
|
|
20390
|
+
}
|
|
20391
|
+
blocked = [...confirmed];
|
|
20406
20392
|
setState((user) => {
|
|
20407
20393
|
const blockedReady = user.blockedReady || !info.fromCache;
|
|
20408
20394
|
if (user.blockedReady === blockedReady && user.blocked.length === blocked.length && user.blocked.every((id, index) => id === blocked[index])) {
|
|
@@ -20478,6 +20464,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20478
20464
|
if (!started)
|
|
20479
20465
|
return;
|
|
20480
20466
|
authSession += 1;
|
|
20467
|
+
blockedChanges.clear();
|
|
20481
20468
|
closeUserWatches();
|
|
20482
20469
|
unsubscribeAuth();
|
|
20483
20470
|
setState((user) => ({ ...user, authError: null, authSessionError: null, profileError: null, settingsError: null }));
|
|
@@ -20499,6 +20486,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20499
20486
|
agreementStored = null;
|
|
20500
20487
|
agreementOverride = null;
|
|
20501
20488
|
agreementAcceptance = null;
|
|
20489
|
+
blockedChanges.clear();
|
|
20502
20490
|
userUid = null;
|
|
20503
20491
|
avatarFetch = { uid: null, key: null, promise: null };
|
|
20504
20492
|
avatarPreview = undefined;
|
|
@@ -20507,25 +20495,63 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20507
20495
|
blockedSet = new Set(blockedSource);
|
|
20508
20496
|
snapshot = makeSnapshot();
|
|
20509
20497
|
}
|
|
20510
|
-
function
|
|
20498
|
+
function changePeerBlock(peer, blocked, { beforeCommit } = {}) {
|
|
20511
20499
|
const nextPeerUid = peerUid(peer);
|
|
20512
20500
|
const uid = cloud.auth.user?.uid;
|
|
20501
|
+
const session = authSession;
|
|
20513
20502
|
if (!uid)
|
|
20514
20503
|
throw new Error("auth");
|
|
20515
20504
|
if (!nextPeerUid)
|
|
20516
20505
|
throw new Error("peer uid required");
|
|
20517
|
-
if (nextPeerUid === uid)
|
|
20506
|
+
if (blocked && nextPeerUid === uid)
|
|
20518
20507
|
return;
|
|
20519
|
-
|
|
20508
|
+
const prior = blockedChanges.get(nextPeerUid);
|
|
20509
|
+
if (prior && prior.blocked === blocked)
|
|
20510
|
+
return prior.promise;
|
|
20511
|
+
const current = () => isCurrentUser(uid, session);
|
|
20512
|
+
const assertCurrent = () => {
|
|
20513
|
+
if (!current())
|
|
20514
|
+
throw new Error("account changed during block update");
|
|
20515
|
+
};
|
|
20516
|
+
const change = { blocked, written: false, promise: null };
|
|
20517
|
+
change.promise = Promise.resolve().then(async () => {
|
|
20518
|
+
try {
|
|
20519
|
+
await prior?.promise.catch(NOOP);
|
|
20520
|
+
assertCurrent();
|
|
20521
|
+
await beforeCommit?.();
|
|
20522
|
+
assertCurrent();
|
|
20523
|
+
const result = await cloud.user.blocked[blocked ? "add" : "remove"](uid, nextPeerUid);
|
|
20524
|
+
assertCurrent();
|
|
20525
|
+
change.written = true;
|
|
20526
|
+
if (blockedChanges.get(nextPeerUid) === change && state.blocked.includes(nextPeerUid) === blocked) {
|
|
20527
|
+
blockedChanges.delete(nextPeerUid);
|
|
20528
|
+
}
|
|
20529
|
+
const confirmed = new Set(state.blocked);
|
|
20530
|
+
if (blocked)
|
|
20531
|
+
confirmed.add(nextPeerUid);
|
|
20532
|
+
else
|
|
20533
|
+
confirmed.delete(nextPeerUid);
|
|
20534
|
+
setState((user) => ({ ...user, blocked: [...confirmed] }));
|
|
20535
|
+
return result;
|
|
20536
|
+
} catch (error) {
|
|
20537
|
+
if (current() && blockedChanges.get(nextPeerUid) === change) {
|
|
20538
|
+
blockedChanges.delete(nextPeerUid);
|
|
20539
|
+
if (prior?.written)
|
|
20540
|
+
blockedChanges.set(nextPeerUid, prior);
|
|
20541
|
+
setState(state, true);
|
|
20542
|
+
}
|
|
20543
|
+
throw error;
|
|
20544
|
+
}
|
|
20545
|
+
});
|
|
20546
|
+
blockedChanges.set(nextPeerUid, change);
|
|
20547
|
+
setState(state, true);
|
|
20548
|
+
return change.promise;
|
|
20520
20549
|
}
|
|
20521
|
-
function
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
if (!nextPeerUid)
|
|
20527
|
-
throw new Error("peer uid required");
|
|
20528
|
-
return cloud.user.blocked.remove(uid, nextPeerUid);
|
|
20550
|
+
function blockPeer(peer, options) {
|
|
20551
|
+
return changePeerBlock(peer, true, options);
|
|
20552
|
+
}
|
|
20553
|
+
function unblockPeer(peer, options) {
|
|
20554
|
+
return changePeerBlock(peer, false, options);
|
|
20529
20555
|
}
|
|
20530
20556
|
async function acceptAgreement(requestedAgreement = CURRENT_AGREEMENT) {
|
|
20531
20557
|
const uid = cloud.auth.user?.uid;
|
|
@@ -20739,6 +20765,13 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20739
20765
|
syncAvatar();
|
|
20740
20766
|
return true;
|
|
20741
20767
|
}
|
|
20768
|
+
function restoreBlocked({ uid, blocked }) {
|
|
20769
|
+
if (!started || !uid || uid !== state.uid || cloud.auth.user?.uid !== uid)
|
|
20770
|
+
return;
|
|
20771
|
+
if (state.blockedReady || !Array.isArray(blocked))
|
|
20772
|
+
return;
|
|
20773
|
+
setState((user) => ({ ...user, blocked: [...blocked], blockedReady: true }));
|
|
20774
|
+
}
|
|
20742
20775
|
snapshot = makeSnapshot();
|
|
20743
20776
|
return {
|
|
20744
20777
|
getSnapshot() {
|
|
@@ -20764,6 +20797,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20764
20797
|
previewAvatar,
|
|
20765
20798
|
confirmAvatar,
|
|
20766
20799
|
restoreBootstrap,
|
|
20800
|
+
restoreBlocked,
|
|
20767
20801
|
retry
|
|
20768
20802
|
};
|
|
20769
20803
|
}
|
|
@@ -21463,6 +21497,48 @@ function readAccountBootstrap(value, uid) {
|
|
|
21463
21497
|
};
|
|
21464
21498
|
}
|
|
21465
21499
|
|
|
21500
|
+
// ../../core/chat/state.js
|
|
21501
|
+
function makeCid() {
|
|
21502
|
+
return `${Date.now().toString(36)}${toHex(randomBytes3(3))}`;
|
|
21503
|
+
}
|
|
21504
|
+
function getMessageKey(message) {
|
|
21505
|
+
return message?.cid || message?.id || null;
|
|
21506
|
+
}
|
|
21507
|
+
function getCidMs(cid) {
|
|
21508
|
+
if (typeof cid !== "string" || !/^[0-9a-z]+[0-9a-f]{6}$/u.test(cid)) {
|
|
21509
|
+
return null;
|
|
21510
|
+
}
|
|
21511
|
+
const base = cid.slice(0, -6);
|
|
21512
|
+
const ms = Number.parseInt(base, 36);
|
|
21513
|
+
return Number.isSafeInteger(ms) && ms > 0 ? ms : null;
|
|
21514
|
+
}
|
|
21515
|
+
function getMessageOrderMs(message) {
|
|
21516
|
+
return getCidMs(message?.cid) ?? timestampMs(message?.ts, Infinity);
|
|
21517
|
+
}
|
|
21518
|
+
function sortMessages(messages) {
|
|
21519
|
+
return [...messages].sort((a, b) => {
|
|
21520
|
+
const aMs = getMessageOrderMs(a);
|
|
21521
|
+
const bMs = getMessageOrderMs(b);
|
|
21522
|
+
if (aMs !== bMs) {
|
|
21523
|
+
return aMs - bMs;
|
|
21524
|
+
}
|
|
21525
|
+
return String(a?.id || "").localeCompare(String(b?.id || ""));
|
|
21526
|
+
});
|
|
21527
|
+
}
|
|
21528
|
+
function mergeMessages(...groups) {
|
|
21529
|
+
const merged = new Map;
|
|
21530
|
+
for (const group of groups) {
|
|
21531
|
+
for (const message of group || []) {
|
|
21532
|
+
const key = getMessageKey(message);
|
|
21533
|
+
if (!key) {
|
|
21534
|
+
continue;
|
|
21535
|
+
}
|
|
21536
|
+
merged.set(key, message);
|
|
21537
|
+
}
|
|
21538
|
+
}
|
|
21539
|
+
return sortMessages([...merged.values()]);
|
|
21540
|
+
}
|
|
21541
|
+
|
|
21466
21542
|
// ../../core/chat/ids.js
|
|
21467
21543
|
function isChatMessageForParticipants(message, chatPK, peerChatPK, memberChatPKs = null) {
|
|
21468
21544
|
const members = Array.isArray(memberChatPKs) ? new Set(memberChatPKs.filter(Boolean)) : null;
|
|
@@ -24023,6 +24099,24 @@ async function openStateRecordsWithEvidence(epochState, records) {
|
|
|
24023
24099
|
closeStateEpoch(opened);
|
|
24024
24100
|
}
|
|
24025
24101
|
}
|
|
24102
|
+
async function readChatMemberState(cloud, epochState, actorChatPK) {
|
|
24103
|
+
if (!chatEpochCapabilities(epochState).sharedReceipts)
|
|
24104
|
+
return null;
|
|
24105
|
+
const member = epochState?.manifest?.members?.find((value) => value.chatPK === actorChatPK);
|
|
24106
|
+
if (!member)
|
|
24107
|
+
return null;
|
|
24108
|
+
const opened = openStateEpoch(epochState);
|
|
24109
|
+
try {
|
|
24110
|
+
const slotId = deriveChatMemberStateSlot(opened.epoch, member.chatSigningPK);
|
|
24111
|
+
const record = await cloud.chat.memberState.readSlot(opened.epoch.epochId, slotId);
|
|
24112
|
+
if (!record || !stateRecordActive(record))
|
|
24113
|
+
return null;
|
|
24114
|
+
const state = await openChatMemberState(opened.epoch, record);
|
|
24115
|
+
return state.actor === actorChatPK ? stateRecordProjection(opened.epoch.epochId, record, state) : null;
|
|
24116
|
+
} finally {
|
|
24117
|
+
closeStateEpoch(opened);
|
|
24118
|
+
}
|
|
24119
|
+
}
|
|
24026
24120
|
function watchChatMemberStateEvidence(cloud, epochState, onUpdate, onError) {
|
|
24027
24121
|
if (!chatEpochCapabilities(epochState).sharedReceipts)
|
|
24028
24122
|
return () => {};
|
|
@@ -25178,8 +25272,6 @@ function cleanChatPayment(message, { requireTx }) {
|
|
|
25178
25272
|
} catch {
|
|
25179
25273
|
return null;
|
|
25180
25274
|
}
|
|
25181
|
-
if (message.mode || message.transfers?.length !== 1)
|
|
25182
|
-
return null;
|
|
25183
25275
|
} else if (message.tokenIdentifier != null)
|
|
25184
25276
|
return null;
|
|
25185
25277
|
const transfers = cleanChatPaymentTransfers(message?.transfers, { requireTx, token });
|
|
@@ -25191,6 +25283,13 @@ function cleanChatPayment(message, { requireTx }) {
|
|
|
25191
25283
|
if (transfers.length > 1 && !mode || rawMode && !mode)
|
|
25192
25284
|
return null;
|
|
25193
25285
|
const total = transfers.reduce((sum, transfer) => sum + BigInt(transfer.a), 0n);
|
|
25286
|
+
if (token) {
|
|
25287
|
+
try {
|
|
25288
|
+
tokenUnits(total);
|
|
25289
|
+
} catch {
|
|
25290
|
+
return null;
|
|
25291
|
+
}
|
|
25292
|
+
}
|
|
25194
25293
|
if (!mode && (transfers.length !== 1 || BigInt(transfers[0].a) !== amount))
|
|
25195
25294
|
return null;
|
|
25196
25295
|
if (mode === GROUP_SEND_MODES.EACH && transfers.some((transfer) => BigInt(transfer.a) !== amount))
|
|
@@ -25216,14 +25315,14 @@ function makeChatPaymentPayload({ amountSats, amountUnits, tokenIdentifier, tran
|
|
|
25216
25315
|
if (token) {
|
|
25217
25316
|
tokenAsset(tokenIdentifier, paymentNetwork);
|
|
25218
25317
|
tokenUnits(amount);
|
|
25219
|
-
if (mode || transfers.length !== 1)
|
|
25220
|
-
throw new Error("token payments require one direct recipient");
|
|
25221
25318
|
}
|
|
25222
25319
|
const rawMode = cleanText(mode);
|
|
25223
25320
|
const groupMode = GROUP_PAYMENT_MODES.has(rawMode) ? rawMode : null;
|
|
25224
25321
|
if (transfers.length > 1 && !groupMode || rawMode && !groupMode)
|
|
25225
25322
|
throw new Error("chat payment mode required");
|
|
25226
25323
|
const total = transfers.reduce((sum, transfer) => sum + BigInt(transfer.a), 0n);
|
|
25324
|
+
if (token)
|
|
25325
|
+
tokenUnits(total);
|
|
25227
25326
|
if (!groupMode && (transfers.length !== 1 || BigInt(transfers[0].a) !== amount)) {
|
|
25228
25327
|
throw new Error("direct payment amount must match its transfer");
|
|
25229
25328
|
}
|
|
@@ -25260,8 +25359,6 @@ function chatPaymentMembersValid(message, payment) {
|
|
|
25260
25359
|
if (sender && recipients.includes(sender))
|
|
25261
25360
|
return false;
|
|
25262
25361
|
const epochMembers = Array.isArray(message?.epochMemberChatPKs) ? new Set(message.epochMemberChatPKs.map((member) => cleanText(member).toLowerCase()).filter(Boolean)) : null;
|
|
25263
|
-
if (message.tokenIdentifier != null && epochMembers?.size > 2)
|
|
25264
|
-
return false;
|
|
25265
25362
|
return !epochMembers?.size || recipients.every((recipient) => epochMembers.has(recipient));
|
|
25266
25363
|
}
|
|
25267
25364
|
function isChatPayment(message) {
|
|
@@ -25870,17 +25967,10 @@ function analyzeMessageExpiry(messages, selfChatPublicKey, _peerChatPublicKey, o
|
|
|
25870
25967
|
const heldExpired = [];
|
|
25871
25968
|
const shorten = [];
|
|
25872
25969
|
let nextExpiryAt = null;
|
|
25873
|
-
const latestStart = latestCallStartKey(projectedMessages.filter(isServerConfirmedMsg));
|
|
25874
|
-
const endedCalls = new Set(projectedMessages.filter(isServerConfirmedMsg).filter((message) => readCallMessage(message)?.event === "ended").map((message) => message.callId));
|
|
25875
25970
|
for (const message of projectedMessages) {
|
|
25876
25971
|
if (!isServerConfirmedMsg(message) || !canShowMsg(message) || message.ttl == null)
|
|
25877
25972
|
continue;
|
|
25878
|
-
const
|
|
25879
|
-
if (call?.event === "started" && getMessageKey(message) === latestStart && (options.activeCallId === undefined && !endedCalls.has(call.callId) || options.activeCallId === call.callId))
|
|
25880
|
-
continue;
|
|
25881
|
-
const readTarget = isPaymentRequest(message) ? message.paymentConfirmation : message;
|
|
25882
|
-
if (!readTarget)
|
|
25883
|
-
continue;
|
|
25973
|
+
const readTarget = isPaymentRequest(message) && message.paymentConfirmation ? message.paymentConfirmation : message;
|
|
25884
25974
|
const originalSeenAt = allReadersSeenAt(message, members, evidence, options.memberStatesByEpoch);
|
|
25885
25975
|
const resultSeenAt = readTarget === message ? originalSeenAt : allReadersSeenAt(readTarget, members, evidence, options.memberStatesByEpoch);
|
|
25886
25976
|
if (originalSeenAt == null || resultSeenAt == null)
|
|
@@ -29858,6 +29948,20 @@ function uniqueValues(items) {
|
|
|
29858
29948
|
function sortedUniqueValues(items) {
|
|
29859
29949
|
return uniqueValues(items).sort();
|
|
29860
29950
|
}
|
|
29951
|
+
function sameArray(a, b) {
|
|
29952
|
+
if (a === b) {
|
|
29953
|
+
return true;
|
|
29954
|
+
}
|
|
29955
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
29956
|
+
return false;
|
|
29957
|
+
}
|
|
29958
|
+
for (let index = 0;index < a.length; index += 1) {
|
|
29959
|
+
if (a[index] !== b[index]) {
|
|
29960
|
+
return false;
|
|
29961
|
+
}
|
|
29962
|
+
}
|
|
29963
|
+
return true;
|
|
29964
|
+
}
|
|
29861
29965
|
|
|
29862
29966
|
// ../../core/chat/chats.js
|
|
29863
29967
|
"use client";
|
|
@@ -31115,7 +31219,8 @@ function writeCachedChats(cache, chats, options = {}) {
|
|
|
31115
31219
|
}
|
|
31116
31220
|
}
|
|
31117
31221
|
for (const item of items) {
|
|
31118
|
-
|
|
31222
|
+
const previous = reviveChat(payload.chatsById?.[item.id]);
|
|
31223
|
+
byId.set(item.id, previous ? serializeChat(mergeChatActivity(item, previous, options.chatPK)) : item);
|
|
31119
31224
|
}
|
|
31120
31225
|
const merged = [...byId.values()];
|
|
31121
31226
|
const capped = merged.length > LOCAL_CHAT_CACHE_MAX_ITEMS;
|
|
@@ -31273,18 +31378,24 @@ function readCachedReadFrontiers(cache, chatId) {
|
|
|
31273
31378
|
return [];
|
|
31274
31379
|
return Object.values(source).map(cleanFrontier2).filter(Boolean);
|
|
31275
31380
|
}
|
|
31276
|
-
function
|
|
31381
|
+
function writeCachedChatRead(cache, chatId, { frontier: value, message, openedAt, chatPK } = {}) {
|
|
31277
31382
|
const frontier = cleanFrontier2(value);
|
|
31278
|
-
if (!cache?.patch || !chatId || !frontier)
|
|
31383
|
+
if (!cache?.patch || !chatId || !frontier && !message)
|
|
31279
31384
|
return Promise.resolve(false);
|
|
31280
31385
|
return cache.patch((payload) => {
|
|
31281
|
-
|
|
31282
|
-
|
|
31283
|
-
|
|
31284
|
-
|
|
31285
|
-
|
|
31286
|
-
|
|
31287
|
-
|
|
31386
|
+
if (frontier) {
|
|
31387
|
+
const chats = payload.pendingReadFrontiersByChat || {};
|
|
31388
|
+
const current = chats[chatId] || {};
|
|
31389
|
+
chats[chatId] = {
|
|
31390
|
+
...current,
|
|
31391
|
+
[frontier.epochId]: laterFrontier(cleanFrontier2(current[frontier.epochId]), frontier)
|
|
31392
|
+
};
|
|
31393
|
+
payload.pendingReadFrontiersByChat = chats;
|
|
31394
|
+
}
|
|
31395
|
+
const chat = payload.chatsById?.[chatId];
|
|
31396
|
+
if (chat && message) {
|
|
31397
|
+
[payload.chatsById[chatId]] = markChatsRead([chat], chatId, message, { openedAt, chatPK });
|
|
31398
|
+
}
|
|
31288
31399
|
return payload;
|
|
31289
31400
|
}, { flush: true }).then(() => true);
|
|
31290
31401
|
}
|
|
@@ -31640,6 +31751,15 @@ function createPendingSendQueue({ diag = null } = {}) {
|
|
|
31640
31751
|
const generationRef = { current: 0 };
|
|
31641
31752
|
const sentAtRef = { current: [] };
|
|
31642
31753
|
const timerRef = { current: null };
|
|
31754
|
+
let active = true;
|
|
31755
|
+
let scheduleVersion = 0;
|
|
31756
|
+
function clearSchedule() {
|
|
31757
|
+
scheduleVersion += 1;
|
|
31758
|
+
if (timerRef.current !== null)
|
|
31759
|
+
clearTimeout(timerRef.current);
|
|
31760
|
+
timerRef.current = null;
|
|
31761
|
+
scheduledRef.current = false;
|
|
31762
|
+
}
|
|
31643
31763
|
function nextRateDelay() {
|
|
31644
31764
|
const now = Date.now();
|
|
31645
31765
|
const since = now - CHAT_SEND_QUEUE_RATE_LIMIT_WINDOW_MS;
|
|
@@ -31681,6 +31801,8 @@ function createPendingSendQueue({ diag = null } = {}) {
|
|
|
31681
31801
|
}
|
|
31682
31802
|
}
|
|
31683
31803
|
const flush = () => {
|
|
31804
|
+
if (!active)
|
|
31805
|
+
return;
|
|
31684
31806
|
if (scheduledRef.current || !queueRef.current.length) {
|
|
31685
31807
|
markDiag(diag, "chat.send.queue.flush.skip", {
|
|
31686
31808
|
scheduled: !!scheduledRef.current,
|
|
@@ -31706,7 +31828,10 @@ function createPendingSendQueue({ diag = null } = {}) {
|
|
|
31706
31828
|
running: runningJobsRef.current.size,
|
|
31707
31829
|
sentWindow: sentAtRef.current.length
|
|
31708
31830
|
});
|
|
31831
|
+
const version = ++scheduleVersion;
|
|
31709
31832
|
timerRef.current = setTimeout(() => {
|
|
31833
|
+
if (!active || version !== scheduleVersion)
|
|
31834
|
+
return;
|
|
31710
31835
|
timerRef.current = null;
|
|
31711
31836
|
scheduledRef.current = false;
|
|
31712
31837
|
const nextIndex = nextRunnableJobIndex();
|
|
@@ -31801,6 +31926,7 @@ function createPendingSendQueue({ diag = null } = {}) {
|
|
|
31801
31926
|
};
|
|
31802
31927
|
const resetPendingSendQueue = () => {
|
|
31803
31928
|
generationRef.current += 1;
|
|
31929
|
+
clearSchedule();
|
|
31804
31930
|
if (queueRef.current.length) {
|
|
31805
31931
|
const error = new Error("chat reset");
|
|
31806
31932
|
queueRef.current.forEach((job) => {
|
|
@@ -31809,16 +31935,18 @@ function createPendingSendQueue({ diag = null } = {}) {
|
|
|
31809
31935
|
});
|
|
31810
31936
|
}
|
|
31811
31937
|
queueRef.current = [];
|
|
31812
|
-
scheduledRef.current = false;
|
|
31813
31938
|
sentAtRef.current = [];
|
|
31814
|
-
if (timerRef.current) {
|
|
31815
|
-
clearTimeout(timerRef.current);
|
|
31816
|
-
timerRef.current = null;
|
|
31817
|
-
}
|
|
31818
31939
|
};
|
|
31819
31940
|
return {
|
|
31820
31941
|
enqueuePendingSendJob,
|
|
31821
31942
|
resetPendingSendQueue,
|
|
31943
|
+
setActive(value) {
|
|
31944
|
+
active = value === true;
|
|
31945
|
+
clearSchedule();
|
|
31946
|
+
markDiag(diag, "chat.send.queue.activity", { active, queued: queueRef.current.length, running: runningJobsRef.current.size });
|
|
31947
|
+
if (active)
|
|
31948
|
+
flush();
|
|
31949
|
+
},
|
|
31822
31950
|
close: resetPendingSendQueue
|
|
31823
31951
|
};
|
|
31824
31952
|
}
|
|
@@ -33454,6 +33582,7 @@ ${cid}` : "";
|
|
|
33454
33582
|
};
|
|
33455
33583
|
return {
|
|
33456
33584
|
resetSending,
|
|
33585
|
+
setActive: pendingSendQueueRef.current.setActive,
|
|
33457
33586
|
ackMessages,
|
|
33458
33587
|
stageMessage: showLocalMessage,
|
|
33459
33588
|
discardLocalMessage: (chatId, cid) => ackMessages(chatId, [cid], { remove: true }),
|
|
@@ -36097,7 +36226,7 @@ function createChatListCache({ getSources, state }) {
|
|
|
36097
36226
|
const pending = pendingWrite;
|
|
36098
36227
|
pendingWrite = null;
|
|
36099
36228
|
if (pending?.cache && Array.isArray(pending.chats)) {
|
|
36100
|
-
writeCachedChats(pending.cache, pending.chats, { ownerListToken: pending.ownerListToken });
|
|
36229
|
+
writeCachedChats(pending.cache, pending.chats, { ownerListToken: pending.ownerListToken, chatPK: pending.chatPK });
|
|
36101
36230
|
}
|
|
36102
36231
|
};
|
|
36103
36232
|
const queue = (nextChats, options = {}) => {
|
|
@@ -36109,6 +36238,7 @@ function createChatListCache({ getSources, state }) {
|
|
|
36109
36238
|
const requestedToken = requestedCoverage === CACHE_COVERAGE.COMPLETE ? cleanOwnerListToken(options.ownerListToken) : requestedCoverage === CACHE_COVERAGE.INCOMPLETE ? null : pendingWrite?.ownerListToken;
|
|
36110
36239
|
pendingWrite = {
|
|
36111
36240
|
cache: sources.localCache,
|
|
36241
|
+
chatPK: sources.chatPK,
|
|
36112
36242
|
chats: nextChats,
|
|
36113
36243
|
ownerListToken: requestedToken
|
|
36114
36244
|
};
|
|
@@ -36135,6 +36265,11 @@ function createChatListCache({ getSources, state }) {
|
|
|
36135
36265
|
state.setHasMoreChats(false);
|
|
36136
36266
|
}
|
|
36137
36267
|
hydrateReadCache(cachedChats, sources.readCacheRef.current);
|
|
36268
|
+
for (const chatId of Object.keys(cachedPayload?.pendingReadFrontiersByChat || {})) {
|
|
36269
|
+
const at = Math.max(0, ...readCachedReadFrontiers(sources.localCache, chatId).map((frontier) => frontier.at));
|
|
36270
|
+
if (at > (sources.readCacheRef.current.get(chatId) || 0))
|
|
36271
|
+
sources.readCacheRef.current.set(chatId, at);
|
|
36272
|
+
}
|
|
36138
36273
|
const cachedChatsWithRead = applyReadCache(cachedChats, sources.chatPK, sources.readCacheRef.current);
|
|
36139
36274
|
if (hasSavedChatSnapshot) {
|
|
36140
36275
|
const shownCachedChats = hydrateRows(cachedChatsWithRead);
|
|
@@ -38907,6 +39042,14 @@ function createChatListEpochOwner({ getGeneration, getSources, index }) {
|
|
|
38907
39042
|
}
|
|
38908
39043
|
|
|
38909
39044
|
// ../../core/chat/listindex.js
|
|
39045
|
+
function sameRecord(left, right) {
|
|
39046
|
+
if (left === right)
|
|
39047
|
+
return true;
|
|
39048
|
+
if (!left || !right)
|
|
39049
|
+
return false;
|
|
39050
|
+
const keys = Object.keys(left);
|
|
39051
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && Object.is(left[key], right[key]));
|
|
39052
|
+
}
|
|
38910
39053
|
function ownerRevision(chat) {
|
|
38911
39054
|
return Number.isSafeInteger(chat?.ownEntry?.ownerRevision) ? chat.ownEntry.ownerRevision : 0;
|
|
38912
39055
|
}
|
|
@@ -38936,11 +39079,26 @@ function activityFromChat(chat) {
|
|
|
38936
39079
|
unseen: chat.unseen === true
|
|
38937
39080
|
};
|
|
38938
39081
|
}
|
|
38939
|
-
function structuralChat(chat) {
|
|
39082
|
+
function structuralChat(chat, previous) {
|
|
38940
39083
|
if (!chat)
|
|
38941
39084
|
return null;
|
|
39085
|
+
const structure = {};
|
|
39086
|
+
if (previous && previous.epochId === chat.epochId && previous.epochVersion === chat.epochVersion) {
|
|
39087
|
+
for (const key of ["memberChatPKs", "memberUids"]) {
|
|
39088
|
+
if (sameArray(previous[key], chat[key]))
|
|
39089
|
+
structure[key] = previous[key];
|
|
39090
|
+
}
|
|
39091
|
+
for (const key of ["signingKeysByChatKey", "settings"]) {
|
|
39092
|
+
if (sameRecord(previous[key], chat[key]))
|
|
39093
|
+
structure[key] = previous[key];
|
|
39094
|
+
}
|
|
39095
|
+
if (Array.isArray(previous.members) && Array.isArray(chat.members) && previous.members.length === chat.members.length && previous.members.every((member, index) => sameRecord(member, chat.members[index]))) {
|
|
39096
|
+
structure.members = previous.members;
|
|
39097
|
+
}
|
|
39098
|
+
}
|
|
38942
39099
|
return {
|
|
38943
39100
|
...chat,
|
|
39101
|
+
...structure,
|
|
38944
39102
|
preview: null,
|
|
38945
39103
|
readMs: null,
|
|
38946
39104
|
inboxMessageId: null,
|
|
@@ -39004,7 +39162,7 @@ function sourceEntry() {
|
|
|
39004
39162
|
presentation: null
|
|
39005
39163
|
};
|
|
39006
39164
|
}
|
|
39007
|
-
function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenChatId, state }) {
|
|
39165
|
+
function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenChatId, state, onOwnersChanged }) {
|
|
39008
39166
|
const entries = new Map;
|
|
39009
39167
|
const authorityBoundaries = new Map;
|
|
39010
39168
|
let authorityRevision = 0;
|
|
@@ -39125,8 +39283,14 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
39125
39283
|
const visible = sources.blockedReady ? filterBlockedChats(composedRows()) : [];
|
|
39126
39284
|
const trimmed = trimExpiredChatPreviews(visible);
|
|
39127
39285
|
const shown = sources.blockedReady ? sortedChats(filterBlockedChats(setLocalChats(trimmed, sources.localByChatRef.current))) : [];
|
|
39128
|
-
|
|
39129
|
-
|
|
39286
|
+
state.setChats((current) => {
|
|
39287
|
+
const byId = new Map(current.map((chat) => [chat.id, chat]));
|
|
39288
|
+
const next = shown.map((chat) => {
|
|
39289
|
+
const previous = byId.get(chat.id);
|
|
39290
|
+
return previous && sameChats([previous], [chat]) ? previous : chat;
|
|
39291
|
+
});
|
|
39292
|
+
return sameArray(current, next) ? current : next;
|
|
39293
|
+
});
|
|
39130
39294
|
const last = getLastChat(shown);
|
|
39131
39295
|
state.setLastChat((current) => sameLastChat(current, last) ? current : last);
|
|
39132
39296
|
updatePeers(shown);
|
|
@@ -39144,6 +39308,7 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
39144
39308
|
if (options.warm !== false) {
|
|
39145
39309
|
sources.warmChats(sources.blockedReady ? filterBlockedChats(owners) : []);
|
|
39146
39310
|
}
|
|
39311
|
+
onOwnersChanged?.(owners);
|
|
39147
39312
|
return owners;
|
|
39148
39313
|
};
|
|
39149
39314
|
const applyBlockedFilter = () => {
|
|
@@ -39169,7 +39334,7 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
39169
39334
|
for (const chat of filterHiddenChats(rows)) {
|
|
39170
39335
|
const entry = entryFor(chat.id);
|
|
39171
39336
|
captureActivity(entry, chat);
|
|
39172
|
-
entry.cache = structuralChat(chat);
|
|
39337
|
+
entry.cache = structuralChat(chat, entry.cache);
|
|
39173
39338
|
}
|
|
39174
39339
|
const shown = render();
|
|
39175
39340
|
updatePeers(shown);
|
|
@@ -39219,7 +39384,7 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
39219
39384
|
for (const chat of incoming) {
|
|
39220
39385
|
const entry = entryFor(chat.id);
|
|
39221
39386
|
captureActivity(entry, chat);
|
|
39222
|
-
entry.observedOwner = selectOwner(entry.observedOwner, structuralChat(chat));
|
|
39387
|
+
entry.observedOwner = selectOwner(entry.observedOwner, structuralChat(chat, structuralOwnerFor(entry) || entry.cache));
|
|
39223
39388
|
if (entry.committedOwner && compareOwners(entry.observedOwner, entry.committedOwner) >= 0) {
|
|
39224
39389
|
entry.committedOwner = null;
|
|
39225
39390
|
}
|
|
@@ -39237,7 +39402,7 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
39237
39402
|
for (const chat of (rows || []).filter((item) => item?.id && sourceCanPublish(item, options.sourceRevision))) {
|
|
39238
39403
|
const entry = entryFor(chat.id);
|
|
39239
39404
|
captureActivity(entry, chat);
|
|
39240
|
-
entry.observedOwner = selectOwner(entry.observedOwner, structuralChat(chat));
|
|
39405
|
+
entry.observedOwner = selectOwner(entry.observedOwner, structuralChat(chat, structuralOwnerFor(entry) || entry.cache));
|
|
39241
39406
|
if (entry.committedOwner && compareOwners(entry.observedOwner, entry.committedOwner) >= 0) {
|
|
39242
39407
|
entry.committedOwner = null;
|
|
39243
39408
|
}
|
|
@@ -39257,7 +39422,7 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
39257
39422
|
return false;
|
|
39258
39423
|
const entry = entryFor(chat.id);
|
|
39259
39424
|
captureActivity(entry, chat);
|
|
39260
|
-
entry.committedOwner = selectOwner(entry.committedOwner, structuralChat(chat));
|
|
39425
|
+
entry.committedOwner = selectOwner(entry.committedOwner, structuralChat(chat, structuralOwnerFor(entry) || entry.cache));
|
|
39261
39426
|
if (ownerCoversAnnouncement(ownerFor(entry), entry.announcement))
|
|
39262
39427
|
entry.announcement = null;
|
|
39263
39428
|
entry.cache = null;
|
|
@@ -39433,6 +39598,9 @@ function createChatListIndex({ cache, filterHiddenChats, getSources, isHiddenCha
|
|
|
39433
39598
|
});
|
|
39434
39599
|
if (next === current)
|
|
39435
39600
|
return false;
|
|
39601
|
+
const readCache = getSources().readCacheRef.current;
|
|
39602
|
+
if (next.readMs > (readCache.get(chatId) || 0))
|
|
39603
|
+
readCache.set(chatId, next.readMs);
|
|
39436
39604
|
replaceActivity(entry, next);
|
|
39437
39605
|
publishOwners();
|
|
39438
39606
|
return true;
|
|
@@ -40085,6 +40253,75 @@ function createChatListPreview({ index, isListening, state }) {
|
|
|
40085
40253
|
};
|
|
40086
40254
|
}
|
|
40087
40255
|
|
|
40256
|
+
// ../../core/chat/listreads.js
|
|
40257
|
+
function createChatListReads({ getSources, index }) {
|
|
40258
|
+
const attempted = new Set;
|
|
40259
|
+
let queue = [];
|
|
40260
|
+
let generation = 0;
|
|
40261
|
+
let running = false;
|
|
40262
|
+
const admitted = (chat, sources) => !sources.chatBanned && !(chat?.memberUids || []).some((uid) => sources.blockedUidSet.has(uid)) && chat?.unseen && chat.ownEntry && !isChatMembershipRemoved(chat);
|
|
40263
|
+
const eligible = (chat, sources) => sources.isActive && admitted(chat, sources);
|
|
40264
|
+
const pump = async () => {
|
|
40265
|
+
if (running)
|
|
40266
|
+
return;
|
|
40267
|
+
running = true;
|
|
40268
|
+
const run = generation;
|
|
40269
|
+
try {
|
|
40270
|
+
while (queue.length && run === generation) {
|
|
40271
|
+
const { chatId, epochId, key } = queue.shift();
|
|
40272
|
+
const sources = getSources();
|
|
40273
|
+
const chat = index.getOwnerChat(chatId);
|
|
40274
|
+
if (!eligible(chat, sources)) {
|
|
40275
|
+
attempted.delete(key);
|
|
40276
|
+
continue;
|
|
40277
|
+
}
|
|
40278
|
+
try {
|
|
40279
|
+
const epoch = epochId === chat.epochId ? chat.epochState : await sources.getEpochState(chat, epochId);
|
|
40280
|
+
if (run !== generation)
|
|
40281
|
+
return;
|
|
40282
|
+
const state = epoch ? await readChatMemberState(sources.cloud, epoch, sources.chatPK) : null;
|
|
40283
|
+
if (run !== generation || !admitted(index.getOwnerChat(chatId), getSources()))
|
|
40284
|
+
continue;
|
|
40285
|
+
const frontier = state?.readFrontier;
|
|
40286
|
+
if (frontier)
|
|
40287
|
+
index.markChatRead(chatId, { id: frontier.id, ts: frontier.at }, frontier.seenAt);
|
|
40288
|
+
} catch (error) {
|
|
40289
|
+
if (run !== generation)
|
|
40290
|
+
return;
|
|
40291
|
+
markError(sources.diag, "chat.list.read.restore", Date.now(), error);
|
|
40292
|
+
}
|
|
40293
|
+
}
|
|
40294
|
+
} finally {
|
|
40295
|
+
if (run === generation)
|
|
40296
|
+
running = false;
|
|
40297
|
+
}
|
|
40298
|
+
};
|
|
40299
|
+
return {
|
|
40300
|
+
restore(chats) {
|
|
40301
|
+
const sources = getSources();
|
|
40302
|
+
if (typeof sources.cloud?.chat?.memberState?.readSlot !== "function")
|
|
40303
|
+
return;
|
|
40304
|
+
for (const chat of chats) {
|
|
40305
|
+
if (!eligible(chat, sources))
|
|
40306
|
+
continue;
|
|
40307
|
+
const epochId = chat.preview?.epochId || chat.epochId;
|
|
40308
|
+
const key = `${chat.id}:${epochId}`;
|
|
40309
|
+
if (attempted.has(key))
|
|
40310
|
+
continue;
|
|
40311
|
+
attempted.add(key);
|
|
40312
|
+
queue.push({ chatId: chat.id, epochId, key });
|
|
40313
|
+
}
|
|
40314
|
+
return pump();
|
|
40315
|
+
},
|
|
40316
|
+
reset() {
|
|
40317
|
+
generation += 1;
|
|
40318
|
+
attempted.clear();
|
|
40319
|
+
queue = [];
|
|
40320
|
+
running = false;
|
|
40321
|
+
}
|
|
40322
|
+
};
|
|
40323
|
+
}
|
|
40324
|
+
|
|
40088
40325
|
// ../../core/chat/liststate.js
|
|
40089
40326
|
function nextValue(current, next) {
|
|
40090
40327
|
return typeof next === "function" ? next(current) : next;
|
|
@@ -40263,7 +40500,16 @@ function createChatList(initialSources) {
|
|
|
40263
40500
|
const filterHiddenChats = (chats) => (chats || []).filter((chatItem) => chatItem?.id && !isHiddenChatId(chatItem.id));
|
|
40264
40501
|
const state = createChatListState({ getSources });
|
|
40265
40502
|
const cache = createChatListCache({ getSources, state });
|
|
40266
|
-
|
|
40503
|
+
let reads = null;
|
|
40504
|
+
const index = createChatListIndex({
|
|
40505
|
+
cache,
|
|
40506
|
+
filterHiddenChats,
|
|
40507
|
+
getSources,
|
|
40508
|
+
isHiddenChatId,
|
|
40509
|
+
state,
|
|
40510
|
+
onOwnersChanged: (chats) => reads?.restore(chats)
|
|
40511
|
+
});
|
|
40512
|
+
reads = createChatListReads({ getSources, index });
|
|
40267
40513
|
const epoch = createChatListEpochOwner({ getGeneration, getSources, index });
|
|
40268
40514
|
const paging = createChatListPaging({ cache, getGeneration, getSources, index, isHiddenChatId, state });
|
|
40269
40515
|
const invalidateRuntimeTasks = () => {
|
|
@@ -40272,6 +40518,7 @@ function createChatList(initialSources) {
|
|
|
40272
40518
|
epoch.reset();
|
|
40273
40519
|
};
|
|
40274
40520
|
const resetChatList = (ready = false) => {
|
|
40521
|
+
reads.reset();
|
|
40275
40522
|
invalidateRuntimeTasks();
|
|
40276
40523
|
index.reset(ready);
|
|
40277
40524
|
};
|
|
@@ -40322,6 +40569,7 @@ function createChatList(initialSources) {
|
|
|
40322
40569
|
index.applyBlockedFilter();
|
|
40323
40570
|
paging.fillVisibleChatPage();
|
|
40324
40571
|
}
|
|
40572
|
+
reads.restore(index.getOwnerChats());
|
|
40325
40573
|
};
|
|
40326
40574
|
const start2 = () => {
|
|
40327
40575
|
if (listener.isListening())
|
|
@@ -40330,6 +40578,7 @@ function createChatList(initialSources) {
|
|
|
40330
40578
|
preview.schedule();
|
|
40331
40579
|
};
|
|
40332
40580
|
const close = () => {
|
|
40581
|
+
reads.reset();
|
|
40333
40582
|
listener.close();
|
|
40334
40583
|
preview.close();
|
|
40335
40584
|
cache.flush();
|
|
@@ -42456,9 +42705,6 @@ function createChatSessionPending({
|
|
|
42456
42705
|
return track(launchId, previousOperation.then((result) => sendDurableSharedAttachment(result?.chatId || launchId, queuedAttachment, options)));
|
|
42457
42706
|
};
|
|
42458
42707
|
const sendChatMessage = (chatId, message, options = {}) => {
|
|
42459
|
-
const chat = getOwnerChat(resolvePendingChatId(chatId)) || launchForChat(chatId)?.chat;
|
|
42460
|
-
if (message?.tokenIdentifier != null && chat?.lineage === "group")
|
|
42461
|
-
throw new Error("group payments support bitcoin only");
|
|
42462
42708
|
const operation = operationForChat(chatId);
|
|
42463
42709
|
if (operation?.discarding)
|
|
42464
42710
|
throw makeChatUnavailableError();
|
|
@@ -43862,7 +44108,6 @@ function createChatSeen({
|
|
|
43862
44108
|
const scheduleRead = (chatId, message, previewMs, frontierMessage, messages = []) => {
|
|
43863
44109
|
const frontier = Number.isSafeInteger(frontierMessage?.at) && frontierMessage?.epochId ? frontierMessage : readFrontierFromMessage(frontierMessage, { seenAt: previewMs });
|
|
43864
44110
|
if (frontier) {
|
|
43865
|
-
writeCachedReadFrontier(localCache, chatId, frontier);
|
|
43866
44111
|
onReadFrontier?.(chatId, frontier);
|
|
43867
44112
|
}
|
|
43868
44113
|
const timing = getReadWriteTiming?.(chatId) || {};
|
|
@@ -43896,6 +44141,12 @@ function createChatSeen({
|
|
|
43896
44141
|
const frontier = readFrontierFromMessage(frontierMessage, { seenAt: read?.previewMs || Date.now() });
|
|
43897
44142
|
if (!read && !frontier && !cachedFrontiers.length)
|
|
43898
44143
|
return false;
|
|
44144
|
+
const savedRead = writeCachedChatRead(localCache, chatId, {
|
|
44145
|
+
frontier,
|
|
44146
|
+
message: read?.preview,
|
|
44147
|
+
openedAt: read?.previewMs,
|
|
44148
|
+
chatPK
|
|
44149
|
+
}).catch((error) => markError(diag, "chat.read.cache", Date.now(), error));
|
|
43899
44150
|
if (read) {
|
|
43900
44151
|
markChatRead?.(chatId, read.preview, read.previewMs);
|
|
43901
44152
|
readCacheRef.current.set(chatId, read.readMs);
|
|
@@ -43905,6 +44156,7 @@ function createChatSeen({
|
|
|
43905
44156
|
for (const cached of cachedFrontiers)
|
|
43906
44157
|
scheduleRead(chatId, preview, previewMs, cached, messages);
|
|
43907
44158
|
scheduleRead(chatId, preview, previewMs, frontierMessage, messages);
|
|
44159
|
+
await savedRead;
|
|
43908
44160
|
return true;
|
|
43909
44161
|
};
|
|
43910
44162
|
const markChatReadState = async (chatId, message, options = {}) => {
|
|
@@ -43992,6 +44244,7 @@ function createChatSessionActions({
|
|
|
43992
44244
|
diag,
|
|
43993
44245
|
readWriteInterval,
|
|
43994
44246
|
adoptLocalMessageMedia,
|
|
44247
|
+
getActive,
|
|
43995
44248
|
getIdentity,
|
|
43996
44249
|
getLiveActivity,
|
|
43997
44250
|
getOwnerChat,
|
|
@@ -44123,6 +44376,7 @@ function createChatSessionActions({
|
|
|
44123
44376
|
reconcileEpoch: (chatId) => getChatListOwner().reconcileChatEpoch(chatId),
|
|
44124
44377
|
diag
|
|
44125
44378
|
});
|
|
44379
|
+
sendActions.setActive(getActive());
|
|
44126
44380
|
reactionActions = createChatReaction({
|
|
44127
44381
|
cloud,
|
|
44128
44382
|
uid: identity().uid,
|
|
@@ -44267,10 +44521,10 @@ function sameReadFrontier(left, right) {
|
|
|
44267
44521
|
return left.actor === right.actor && left.readFrontier?.epochId === right.readFrontier?.epochId && left.readFrontier?.id === right.readFrontier?.id && left.readFrontier?.at === right.readFrontier?.at && left.readFrontier?.seenAt === right.readFrontier?.seenAt;
|
|
44268
44522
|
}
|
|
44269
44523
|
function sameActivity2(left, right) {
|
|
44270
|
-
return left.connected === right.connected && left.activeChatPKs.length === right.activeChatPKs.length && left.compositions.length === right.compositions.length && left.readFrontiers.length === right.readFrontiers.length && left.activeChatPKs.every((value, index) => value === right.activeChatPKs[index]) && left.compositions.every((value, index) => sameComposition(value, right.compositions[index])) && left.readFrontiers.every((value, index) => sameReadFrontier(value, right.readFrontiers[index]));
|
|
44524
|
+
return left.connected === right.connected && left.lastObservedAtByChatPK === right.lastObservedAtByChatPK && left.activeChatPKs.length === right.activeChatPKs.length && left.compositions.length === right.compositions.length && left.readFrontiers.length === right.readFrontiers.length && left.activeChatPKs.every((value, index) => value === right.activeChatPKs[index]) && left.compositions.every((value, index) => sameComposition(value, right.compositions[index])) && left.readFrontiers.every((value, index) => sameReadFrontier(value, right.readFrontiers[index]));
|
|
44271
44525
|
}
|
|
44272
44526
|
function hasActivity(activity) {
|
|
44273
|
-
return !!(activity?.connected || activity?.activeChatPKs?.length || activity?.compositions?.length || activity?.readFrontiers?.length);
|
|
44527
|
+
return !!(activity?.connected || activity?.activeChatPKs?.length || activity?.compositions?.length || activity?.readFrontiers?.length || Object.keys(activity?.lastObservedAtByChatPK || {}).length);
|
|
44274
44528
|
}
|
|
44275
44529
|
function summarizeLocalMessage(summary, message) {
|
|
44276
44530
|
summary.messages += 1;
|
|
@@ -44411,6 +44665,17 @@ function createChatSessionState({
|
|
|
44411
44665
|
return;
|
|
44412
44666
|
const next = new Map(liveActivityByChat);
|
|
44413
44667
|
const previous = next.get(chatId);
|
|
44668
|
+
let lastObservedAtByChatPK = previous?.lastObservedAtByChatPK;
|
|
44669
|
+
const departed = previous?.connected ? previous.activeChatPKs.filter((actor) => !activity?.connected || !activity.activeChatPKs.includes(actor)) : [];
|
|
44670
|
+
if (departed.length) {
|
|
44671
|
+
const now = Date.now();
|
|
44672
|
+
lastObservedAtByChatPK = { ...lastObservedAtByChatPK };
|
|
44673
|
+
for (const actor of departed)
|
|
44674
|
+
lastObservedAtByChatPK[actor] = Math.max(lastObservedAtByChatPK[actor] || 0, now);
|
|
44675
|
+
Object.freeze(lastObservedAtByChatPK);
|
|
44676
|
+
}
|
|
44677
|
+
if (lastObservedAtByChatPK)
|
|
44678
|
+
activity = { ...activity, lastObservedAtByChatPK };
|
|
44414
44679
|
if (previous?.connected && !activity?.connected)
|
|
44415
44680
|
onLiveDisconnect(chatId);
|
|
44416
44681
|
if (hasActivity(activity)) {
|
|
@@ -44805,6 +45070,7 @@ function normalizeChatSessionSources(sources = {}) {
|
|
|
44805
45070
|
uid: sources.uid || "",
|
|
44806
45071
|
online: sources.online !== false,
|
|
44807
45072
|
blocked: Array.isArray(sources.blocked) ? sources.blocked : [],
|
|
45073
|
+
blockedPending: Array.isArray(sources.blockedPending) ? sources.blockedPending : [],
|
|
44808
45074
|
blockedReady: sources.blockedReady === true,
|
|
44809
45075
|
chatPK: sources.chatPK || "",
|
|
44810
45076
|
chatBanned: sources.chatBanned === true,
|
|
@@ -44853,7 +45119,6 @@ function createChatSession({
|
|
|
44853
45119
|
mls = null,
|
|
44854
45120
|
live = null,
|
|
44855
45121
|
maintenance,
|
|
44856
|
-
resolveActiveCall,
|
|
44857
45122
|
incomingChatDecision = null,
|
|
44858
45123
|
getPeerProfile = null,
|
|
44859
45124
|
refreshPeerProfile = null,
|
|
@@ -44871,6 +45136,7 @@ function createChatSession({
|
|
|
44871
45136
|
uid = "",
|
|
44872
45137
|
online = true,
|
|
44873
45138
|
blocked = [],
|
|
45139
|
+
blockedPending = [],
|
|
44874
45140
|
blockedReady = false,
|
|
44875
45141
|
chatPK = "",
|
|
44876
45142
|
chatSigningPK = "",
|
|
@@ -44887,6 +45153,7 @@ function createChatSession({
|
|
|
44887
45153
|
let inboxEnabled = initialSources.inboxEnabled === true;
|
|
44888
45154
|
let started = false;
|
|
44889
45155
|
let appStateSubscription = null;
|
|
45156
|
+
const messageListeners = new Set;
|
|
44890
45157
|
let mlsPoolReplenishment = null;
|
|
44891
45158
|
let mlsPoolReplenishQueued = false;
|
|
44892
45159
|
let mlsPoolNeedsReplenish = true;
|
|
@@ -45061,6 +45328,13 @@ function createChatSession({
|
|
|
45061
45328
|
};
|
|
45062
45329
|
const handleBatchMessages = (chatId, messages) => {
|
|
45063
45330
|
chatListOwner.observeMessages(chatId, messages);
|
|
45331
|
+
for (const listener of messageListeners) {
|
|
45332
|
+
try {
|
|
45333
|
+
listener(chatId, messages);
|
|
45334
|
+
} catch (error) {
|
|
45335
|
+
diag?.("chat.message.observer.error", { code: error?.code || "chat/observer" });
|
|
45336
|
+
}
|
|
45337
|
+
}
|
|
45064
45338
|
if (![...messages || []].reverse().some(isEpochTransitionMsg))
|
|
45065
45339
|
return;
|
|
45066
45340
|
const startedAt = Date.now();
|
|
@@ -45493,8 +45767,6 @@ function createChatSession({
|
|
|
45493
45767
|
const chat = getOwnerChat(chatId);
|
|
45494
45768
|
if (!chat)
|
|
45495
45769
|
throw makeChatUnavailableError();
|
|
45496
|
-
if (payment.tokenIdentifier != null && chat.lineage !== "direct")
|
|
45497
|
-
throw new Error("group payments support bitcoin only");
|
|
45498
45770
|
const members = Array.isArray(chat?.memberChatPKs) ? chat.memberChatPKs.map((memberChatPK) => cleanText(memberChatPK).toLowerCase()) : (chat?.members || []).map((member) => cleanText(member?.chatPK).toLowerCase());
|
|
45499
45771
|
const message = pending ? makePendingChatPayment(payment) : makeChatPayment(payment);
|
|
45500
45772
|
const recipients = chatPaymentRecipients(message);
|
|
@@ -45521,6 +45793,7 @@ function createChatSession({
|
|
|
45521
45793
|
};
|
|
45522
45794
|
const getLocalMessages = (chatId) => isChatPendingDelete(chatId) || !isChatIdVisible(chatId) ? EMPTY_LOCAL_MESSAGES : localByChatRef.current.get(chatId) ?? EMPTY_LOCAL_MESSAGES;
|
|
45523
45795
|
const blockedAuthority = (values) => [...values || []].sort().join("|");
|
|
45796
|
+
const committedBlocked = () => blocked.filter((uid2) => !blockedPending.includes(uid2));
|
|
45524
45797
|
const bulkOperation = (blockedUids = null) => ({
|
|
45525
45798
|
generation: authGeneration,
|
|
45526
45799
|
identity: {
|
|
@@ -45536,7 +45809,7 @@ function createChatSession({
|
|
|
45536
45809
|
deleteActions: actionOwner.deleteActions,
|
|
45537
45810
|
blockedKey: blockedUids ? blockedAuthority(blockedUids) : null
|
|
45538
45811
|
});
|
|
45539
|
-
const isBulkOperationCurrent = (operation) => operation.generation === authGeneration && operation.deleteActions === actionOwner.deleteActions && (operation.blockedKey == null || operation.blockedKey === blockedAuthority(
|
|
45812
|
+
const isBulkOperationCurrent = (operation) => operation.generation === authGeneration && operation.deleteActions === actionOwner.deleteActions && (operation.blockedKey == null || operation.blockedKey === blockedAuthority(committedBlocked()));
|
|
45540
45813
|
const assertBulkOperationCurrent = (operation) => {
|
|
45541
45814
|
requireOnline();
|
|
45542
45815
|
if (!isBulkOperationCurrent(operation))
|
|
@@ -45550,32 +45823,42 @@ function createChatSession({
|
|
|
45550
45823
|
return 0;
|
|
45551
45824
|
let allChats = await loadOwnerChats(cloud, identity.uid, identity.chatPK, identity.chatPrivateKey);
|
|
45552
45825
|
assertBulkOperationCurrent(operation);
|
|
45553
|
-
|
|
45554
|
-
|
|
45555
|
-
|
|
45556
|
-
|
|
45557
|
-
|
|
45558
|
-
|
|
45559
|
-
|
|
45560
|
-
|
|
45561
|
-
|
|
45562
|
-
|
|
45563
|
-
|
|
45564
|
-
|
|
45565
|
-
|
|
45566
|
-
|
|
45567
|
-
|
|
45568
|
-
|
|
45569
|
-
|
|
45570
|
-
|
|
45571
|
-
assertBulkOperationCurrent(operation);
|
|
45572
|
-
}
|
|
45573
|
-
}
|
|
45826
|
+
await processInbox(cloud, identity.uid, identity.chatPK, identity.chatPrivateKey, {
|
|
45827
|
+
currentChats: allChats,
|
|
45828
|
+
chatSigningPK: identity.chatSigningPK,
|
|
45829
|
+
chatSigningSecret: identity.chatSigningSecret,
|
|
45830
|
+
notificationPK: identity.notificationPK,
|
|
45831
|
+
notificationPrivateKey: identity.notificationPrivateKey,
|
|
45832
|
+
mls,
|
|
45833
|
+
blockedUids: new Set(identity.blocked),
|
|
45834
|
+
chatAdmission,
|
|
45835
|
+
incomingChatDecision,
|
|
45836
|
+
scanAllSlots: true,
|
|
45837
|
+
requireComplete: true,
|
|
45838
|
+
isCurrent: () => isBulkOperationCurrent(operation),
|
|
45839
|
+
diag
|
|
45840
|
+
});
|
|
45841
|
+
assertBulkOperationCurrent(operation);
|
|
45842
|
+
allChats = await loadOwnerChats(cloud, identity.uid, identity.chatPK, identity.chatPrivateKey);
|
|
45843
|
+
assertBulkOperationCurrent(operation);
|
|
45574
45844
|
if (!allChats.length)
|
|
45575
45845
|
return 0;
|
|
45576
45846
|
let retired = 0;
|
|
45577
|
-
for (const
|
|
45847
|
+
for (const ownedChat of allChats) {
|
|
45578
45848
|
assertBulkOperationCurrent(operation);
|
|
45849
|
+
let chat = ownedChat;
|
|
45850
|
+
if (!isChatMembershipRemoved(chat)) {
|
|
45851
|
+
chatListOwner.commitOwner(chat);
|
|
45852
|
+
const reconciliation = await chatListOwner.reconcileChatEpoch(chat.id);
|
|
45853
|
+
assertBulkOperationCurrent(operation);
|
|
45854
|
+
chat = getOwnerChat(chat.id);
|
|
45855
|
+
if (!chat) {
|
|
45856
|
+
if (!reconciliation.removed)
|
|
45857
|
+
throw makeChatUnavailableError();
|
|
45858
|
+
retired += 1;
|
|
45859
|
+
continue;
|
|
45860
|
+
}
|
|
45861
|
+
}
|
|
45579
45862
|
if (isChatMembershipRemoved(chat)) {
|
|
45580
45863
|
await operation.deleteActions.deleteChat(chat, { cleanup: false });
|
|
45581
45864
|
assertBulkOperationCurrent(operation);
|
|
@@ -45717,9 +46000,10 @@ function createChatSession({
|
|
|
45717
46000
|
return false;
|
|
45718
46001
|
}
|
|
45719
46002
|
};
|
|
45720
|
-
const prepareCall = async (chatId) => {
|
|
46003
|
+
const prepareCall = async (chatId, options = {}) => {
|
|
45721
46004
|
const generation = authGeneration;
|
|
45722
|
-
|
|
46005
|
+
await sendReadiness.wait(chatId, options);
|
|
46006
|
+
const current = () => generation === authGeneration && !options.signal?.aborted && online && pendingOwner.canSendToChat(chatId);
|
|
45723
46007
|
if (!current())
|
|
45724
46008
|
throw new Error("chat unavailable");
|
|
45725
46009
|
await pendingOwner.assertChatAdmission(chatId);
|
|
@@ -45736,7 +46020,13 @@ function createChatSession({
|
|
|
45736
46020
|
throw new Error("chat unavailable");
|
|
45737
46021
|
return chat.epochState;
|
|
45738
46022
|
};
|
|
45739
|
-
const materializeChat =
|
|
46023
|
+
const materializeChat = async (chatId, options = {}) => {
|
|
46024
|
+
const generation = authGeneration;
|
|
46025
|
+
await sendReadiness.wait(chatId, options);
|
|
46026
|
+
if (generation !== authGeneration || options.signal?.aborted)
|
|
46027
|
+
throw makeChatUnavailableError();
|
|
46028
|
+
return pendingOwner.runChatMutation(chatId, (canonicalId) => canonicalId);
|
|
46029
|
+
};
|
|
45740
46030
|
const flushChatReadFrontier = (chatId) => {
|
|
45741
46031
|
if (!online)
|
|
45742
46032
|
return false;
|
|
@@ -45928,6 +46218,7 @@ function createChatSession({
|
|
|
45928
46218
|
lastHydratedCacheKeyRef,
|
|
45929
46219
|
chatsRef,
|
|
45930
46220
|
reconcileMessageBatches: (...args) => messageBatchOwner.reconcileChats(...args),
|
|
46221
|
+
getEpochState: (...args) => historyOwner.getEpochState(...args),
|
|
45931
46222
|
warmChats: (...args) => messageBatchOwner.warm(...args),
|
|
45932
46223
|
onOwnerRemoved: (chatIds) => {
|
|
45933
46224
|
for (const chatId of chatIds)
|
|
@@ -45986,6 +46277,7 @@ function createChatSession({
|
|
|
45986
46277
|
});
|
|
45987
46278
|
};
|
|
45988
46279
|
stateOwner.setActions({
|
|
46280
|
+
ensureChat,
|
|
45989
46281
|
loadMoreChats: loadMoreChats2,
|
|
45990
46282
|
selectChat,
|
|
45991
46283
|
resolveChatRequest,
|
|
@@ -46054,7 +46346,6 @@ function createChatSession({
|
|
|
46054
46346
|
deleteMessage,
|
|
46055
46347
|
deleteMessages,
|
|
46056
46348
|
deleteMessageDocs,
|
|
46057
|
-
resolveActiveCall,
|
|
46058
46349
|
setChatTtl,
|
|
46059
46350
|
makeMessagePermanent,
|
|
46060
46351
|
makeMessageTemporary,
|
|
@@ -46071,6 +46362,7 @@ function createChatSession({
|
|
|
46071
46362
|
diag,
|
|
46072
46363
|
readWriteInterval,
|
|
46073
46364
|
adoptLocalMessageMedia,
|
|
46365
|
+
getActive: () => isActive,
|
|
46074
46366
|
getIdentity: () => ({
|
|
46075
46367
|
uid,
|
|
46076
46368
|
chatPK,
|
|
@@ -46120,6 +46412,7 @@ function createChatSession({
|
|
|
46120
46412
|
const setActive = (nextActive) => {
|
|
46121
46413
|
foreground = nextActive === true;
|
|
46122
46414
|
const value = foreground && online;
|
|
46415
|
+
actionOwner.sendActions.setActive(value);
|
|
46123
46416
|
if (isActive === value)
|
|
46124
46417
|
return;
|
|
46125
46418
|
if (!value) {
|
|
@@ -46147,7 +46440,7 @@ function createChatSession({
|
|
|
46147
46440
|
inboxTransport.setSource(inboxSource());
|
|
46148
46441
|
};
|
|
46149
46442
|
const setSources = (nextSources = {}) => {
|
|
46150
|
-
const previousBlockedKey =
|
|
46443
|
+
const previousBlockedKey = blockedAuthority(committedBlocked());
|
|
46151
46444
|
const current = { uid, chatPK, chatBanned, chatPrivateKey, chatSigningPK, chatSigningSecret, notificationPK, notificationPrivateKey, chatAdmission, localCache };
|
|
46152
46445
|
const next = normalizeChatSessionSources(nextSources);
|
|
46153
46446
|
const admissionChanged = !sameChatAdmission(chatAdmission, next.chatAdmission);
|
|
@@ -46164,6 +46457,7 @@ function createChatSession({
|
|
|
46164
46457
|
uid,
|
|
46165
46458
|
online,
|
|
46166
46459
|
blocked,
|
|
46460
|
+
blockedPending,
|
|
46167
46461
|
blockedReady,
|
|
46168
46462
|
chatPK,
|
|
46169
46463
|
chatBanned,
|
|
@@ -46198,9 +46492,9 @@ function createChatSession({
|
|
|
46198
46492
|
drainMembershipOutbox();
|
|
46199
46493
|
replenishMlsPool({ force: true });
|
|
46200
46494
|
}
|
|
46201
|
-
const nextBlockedKey =
|
|
46495
|
+
const nextBlockedKey = blockedAuthority(committedBlocked());
|
|
46202
46496
|
if (online && blockedReady && nextBlockedKey && nextBlockedKey !== previousBlockedKey) {
|
|
46203
|
-
retireBlockedChats(new Set(
|
|
46497
|
+
retireBlockedChats(new Set(committedBlocked())).catch((error) => {
|
|
46204
46498
|
markDiag(diag, "chat.block.retire.failed", { message: error?.message || String(error) });
|
|
46205
46499
|
});
|
|
46206
46500
|
}
|
|
@@ -46220,8 +46514,8 @@ function createChatSession({
|
|
|
46220
46514
|
publish();
|
|
46221
46515
|
inboxTransport.start(inboxSource());
|
|
46222
46516
|
chatListOwner.start();
|
|
46223
|
-
if (blockedReady &&
|
|
46224
|
-
retireBlockedChats(new Set(
|
|
46517
|
+
if (blockedReady && committedBlocked().length) {
|
|
46518
|
+
retireBlockedChats(new Set(committedBlocked())).catch((error) => {
|
|
46225
46519
|
markDiag(diag, "chat.block.retire.failed", { message: error?.message || String(error) });
|
|
46226
46520
|
});
|
|
46227
46521
|
}
|
|
@@ -46234,6 +46528,7 @@ function createChatSession({
|
|
|
46234
46528
|
};
|
|
46235
46529
|
const close = () => {
|
|
46236
46530
|
authGeneration += 1;
|
|
46531
|
+
messageListeners.clear();
|
|
46237
46532
|
sendReadiness.reset();
|
|
46238
46533
|
pendingOwner.reset();
|
|
46239
46534
|
maintenance.close();
|
|
@@ -46259,7 +46554,20 @@ function createChatSession({
|
|
|
46259
46554
|
const subscribeFields = stateOwner.subscribeFields;
|
|
46260
46555
|
const getSnapshot = stateOwner.getSnapshot;
|
|
46261
46556
|
publish();
|
|
46262
|
-
return {
|
|
46557
|
+
return {
|
|
46558
|
+
close,
|
|
46559
|
+
getSnapshot,
|
|
46560
|
+
refreshAdmissionProjection,
|
|
46561
|
+
setInboxEnabled,
|
|
46562
|
+
setSources,
|
|
46563
|
+
start: start2,
|
|
46564
|
+
subscribe,
|
|
46565
|
+
subscribeFields,
|
|
46566
|
+
subscribeMessages(listener) {
|
|
46567
|
+
messageListeners.add(listener);
|
|
46568
|
+
return () => messageListeners.delete(listener);
|
|
46569
|
+
}
|
|
46570
|
+
};
|
|
46263
46571
|
}
|
|
46264
46572
|
|
|
46265
46573
|
// ../../core/presence/protocol.js
|
|
@@ -46433,6 +46741,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46433
46741
|
const peerListeners = new Set;
|
|
46434
46742
|
const records = new Map;
|
|
46435
46743
|
const peers = new Map;
|
|
46744
|
+
const peersByUid = new Map;
|
|
46436
46745
|
const readyShards = new Map;
|
|
46437
46746
|
let sources = { uid: null, identity: null, enabled: false };
|
|
46438
46747
|
let channel = null;
|
|
@@ -46444,11 +46753,13 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46444
46753
|
let seq = 0;
|
|
46445
46754
|
let renewTimer = null;
|
|
46446
46755
|
let expiryTimer = null;
|
|
46756
|
+
let expiryAt = Infinity;
|
|
46757
|
+
let ownTopic = null;
|
|
46447
46758
|
let command = null;
|
|
46448
46759
|
let authenticating = null;
|
|
46449
46760
|
let snapshot = Object.freeze({ visibility: null, ready: false, pending: false, pendingVisibility: null, error: null, ...UNKNOWN_PRESENCE });
|
|
46450
46761
|
function topic() {
|
|
46451
|
-
return
|
|
46762
|
+
return ownTopic;
|
|
46452
46763
|
}
|
|
46453
46764
|
function isReady() {
|
|
46454
46765
|
return Boolean(channel && challenge && policy && sources.enabled && !closed);
|
|
@@ -46481,17 +46792,19 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46481
46792
|
const latest = fresh.reduce((selected, lease) => !selected || lease.issuedAt > selected.issuedAt || lease.issuedAt === selected.issuedAt && lease.session > selected.session ? lease : selected, null);
|
|
46482
46793
|
return { availability: fresh.length ? "online" : "offline", status: latest?.status ?? null, lastActiveAt };
|
|
46483
46794
|
}
|
|
46484
|
-
function refresh() {
|
|
46485
|
-
clearTimeout(expiryTimer);
|
|
46486
|
-
expiryTimer = null;
|
|
46487
|
-
let changed = false;
|
|
46795
|
+
function refresh(keys = records.keys(), changedUids = new Set) {
|
|
46488
46796
|
let deadline = Infinity;
|
|
46489
46797
|
const timestamp = now();
|
|
46490
|
-
for (const
|
|
46798
|
+
for (const key of keys) {
|
|
46799
|
+
const record = records.get(key);
|
|
46800
|
+
if (!record)
|
|
46801
|
+
continue;
|
|
46491
46802
|
const next = projection(record);
|
|
46492
46803
|
if (!samePresence(record.view, next)) {
|
|
46493
46804
|
record.view = Object.freeze(next);
|
|
46494
|
-
|
|
46805
|
+
const uid = peers.get(key)?.uid;
|
|
46806
|
+
if (uid)
|
|
46807
|
+
changedUids.add(uid);
|
|
46495
46808
|
}
|
|
46496
46809
|
if (record.ready) {
|
|
46497
46810
|
for (const lease of record.leases)
|
|
@@ -46504,11 +46817,18 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46504
46817
|
}
|
|
46505
46818
|
const own = records.get(topic())?.view || UNKNOWN_PRESENCE;
|
|
46506
46819
|
publish(policy?.visibility === "private" ? OFFLINE_PRESENCE : own);
|
|
46507
|
-
if (
|
|
46820
|
+
if (changedUids.size)
|
|
46508
46821
|
for (const listener of peerListeners)
|
|
46509
|
-
listener();
|
|
46510
|
-
if (
|
|
46511
|
-
expiryTimer
|
|
46822
|
+
listener(changedUids);
|
|
46823
|
+
if (deadline < expiryAt && deadline > timestamp) {
|
|
46824
|
+
clearTimeout(expiryTimer);
|
|
46825
|
+
expiryAt = deadline;
|
|
46826
|
+
expiryTimer = setTimeout(() => {
|
|
46827
|
+
expiryAt = Infinity;
|
|
46828
|
+
expiryTimer = null;
|
|
46829
|
+
refresh();
|
|
46830
|
+
}, deadline - timestamp + 1);
|
|
46831
|
+
}
|
|
46512
46832
|
}
|
|
46513
46833
|
function finishCommand(error = null) {
|
|
46514
46834
|
if (!command)
|
|
@@ -46523,7 +46843,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46523
46843
|
pending.resolve(snapshot);
|
|
46524
46844
|
}
|
|
46525
46845
|
function expected(identity, extra = {}) {
|
|
46526
|
-
return { realm: realm2, topic:
|
|
46846
|
+
return { realm: realm2, topic: identity.topic, signingPK: identity.chatSigningPK, now: now(), ...extra };
|
|
46527
46847
|
}
|
|
46528
46848
|
function receive(frame) {
|
|
46529
46849
|
const identity = peers.get(frame.topic);
|
|
@@ -46548,6 +46868,15 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46548
46868
|
lastActiveAt = verifyPresenceObservation(frame.lastObservation, scope).lastActiveAt;
|
|
46549
46869
|
} catch {}
|
|
46550
46870
|
}
|
|
46871
|
+
const timestamp = now();
|
|
46872
|
+
if (previous?.revision === currentPolicy.revision) {
|
|
46873
|
+
lastActiveAt = Math.max(lastActiveAt || 0, previous.lastActiveAt || 0) || null;
|
|
46874
|
+
if (!frame.leases?.length && previous.ready && previous.leases.some((lease) => lease.expiresAt > timestamp)) {
|
|
46875
|
+
lastActiveAt = Math.max(lastActiveAt || 0, timestamp);
|
|
46876
|
+
}
|
|
46877
|
+
}
|
|
46878
|
+
for (const lease of leases)
|
|
46879
|
+
lastActiveAt = Math.max(lastActiveAt || 0, Math.min(lease.issuedAt, timestamp));
|
|
46551
46880
|
}
|
|
46552
46881
|
records.set(frame.topic, {
|
|
46553
46882
|
ready: true,
|
|
@@ -46556,7 +46885,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46556
46885
|
revision: currentPolicy?.revision ?? previous?.revision ?? 0,
|
|
46557
46886
|
view: previous?.view || UNKNOWN_PRESENCE
|
|
46558
46887
|
});
|
|
46559
|
-
refresh();
|
|
46888
|
+
refresh([frame.topic]);
|
|
46560
46889
|
} catch (error) {
|
|
46561
46890
|
report(error);
|
|
46562
46891
|
}
|
|
@@ -46574,10 +46903,10 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46574
46903
|
if (!session || !policy || policy.visibility !== "public" || command)
|
|
46575
46904
|
return;
|
|
46576
46905
|
const timestamp = now();
|
|
46577
|
-
const
|
|
46906
|
+
const ownTopic2 = topic();
|
|
46578
46907
|
const lease = signPresenceLease(sources.identity, {
|
|
46579
46908
|
realm: realm2,
|
|
46580
|
-
topic:
|
|
46909
|
+
topic: ownTopic2,
|
|
46581
46910
|
session,
|
|
46582
46911
|
challenge,
|
|
46583
46912
|
policyRevision: policy.revision,
|
|
@@ -46588,11 +46917,11 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46588
46917
|
});
|
|
46589
46918
|
const observation = signPresenceObservation(sources.identity, {
|
|
46590
46919
|
realm: realm2,
|
|
46591
|
-
topic:
|
|
46920
|
+
topic: ownTopic2,
|
|
46592
46921
|
policyRevision: policy.revision,
|
|
46593
46922
|
lastActiveAt: Math.floor(timestamp / PRESENCE_LAST_ACTIVE_BUCKET_MS) * PRESENCE_LAST_ACTIVE_BUCKET_MS
|
|
46594
46923
|
});
|
|
46595
|
-
channel.send(presenceShard(
|
|
46924
|
+
channel.send(presenceShard(ownTopic2), { type: "lease", lease, observation });
|
|
46596
46925
|
}
|
|
46597
46926
|
function receiveOwner(frame) {
|
|
46598
46927
|
if (frame.topic !== topic() || !sources.identity)
|
|
@@ -46614,7 +46943,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46614
46943
|
if (policy.visibility === "private") {
|
|
46615
46944
|
const previous = records.get(topic());
|
|
46616
46945
|
records.set(topic(), { ready: true, leases: [], lastActiveAt: null, revision: policy.revision, view: previous?.view || UNKNOWN_PRESENCE });
|
|
46617
|
-
refresh();
|
|
46946
|
+
refresh([topic()]);
|
|
46618
46947
|
}
|
|
46619
46948
|
if (command && policy.revision >= command.policy.revision) {
|
|
46620
46949
|
finishCommand(policy.signature === command.policy.signature ? null : new Error("activity setting changed on another device; try again"));
|
|
@@ -46725,7 +47054,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46725
47054
|
function setPeers(profiles) {
|
|
46726
47055
|
const next = new Map;
|
|
46727
47056
|
if (sources.uid && sources.identity)
|
|
46728
|
-
next.set(topic(), { uid: sources.uid, chatSigningPK: sources.identity.publicKey });
|
|
47057
|
+
next.set(topic(), { uid: sources.uid, topic: topic(), chatSigningPK: sources.identity.publicKey });
|
|
46729
47058
|
for (const profile of profiles || []) {
|
|
46730
47059
|
if (next.size >= PRESENCE_MAX_SUBSCRIPTIONS)
|
|
46731
47060
|
break;
|
|
@@ -46733,27 +47062,34 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46733
47062
|
continue;
|
|
46734
47063
|
if (profile.uid === sources.uid)
|
|
46735
47064
|
continue;
|
|
46736
|
-
|
|
47065
|
+
const key = peersByUid.get(profile.uid)?.topic || derivePresenceTopic(realm2, profile.uid);
|
|
47066
|
+
next.set(key, { uid: profile.uid, topic: key, chatSigningPK: profile.chatSigningPK });
|
|
46737
47067
|
}
|
|
46738
47068
|
let changed = peers.size !== next.size;
|
|
47069
|
+
const changedUids = new Set;
|
|
46739
47070
|
for (const [key, identity] of next) {
|
|
46740
47071
|
if (peers.get(key)?.chatSigningPK !== identity.chatSigningPK) {
|
|
46741
47072
|
changed = true;
|
|
47073
|
+
changedUids.add(identity.uid);
|
|
46742
47074
|
records.delete(key);
|
|
46743
47075
|
}
|
|
46744
47076
|
}
|
|
47077
|
+
for (const [key, identity] of peers)
|
|
47078
|
+
if (!next.has(key))
|
|
47079
|
+
changedUids.add(identity.uid);
|
|
46745
47080
|
if (!changed)
|
|
46746
47081
|
return;
|
|
46747
47082
|
peers.clear();
|
|
46748
|
-
|
|
47083
|
+
peersByUid.clear();
|
|
47084
|
+
for (const [key, value] of next) {
|
|
46749
47085
|
peers.set(key, value);
|
|
47086
|
+
peersByUid.set(value.uid, value);
|
|
47087
|
+
}
|
|
46750
47088
|
for (const key of records.keys())
|
|
46751
47089
|
if (!peers.has(key))
|
|
46752
47090
|
records.delete(key);
|
|
46753
47091
|
sync();
|
|
46754
|
-
refresh();
|
|
46755
|
-
for (const listener of peerListeners)
|
|
46756
|
-
listener();
|
|
47092
|
+
refresh([], changedUids);
|
|
46757
47093
|
}
|
|
46758
47094
|
function update(next) {
|
|
46759
47095
|
const accountChanged = sources.uid !== next.uid;
|
|
@@ -46762,11 +47098,15 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46762
47098
|
stop();
|
|
46763
47099
|
policy = null;
|
|
46764
47100
|
records.clear();
|
|
46765
|
-
if (accountChanged)
|
|
47101
|
+
if (accountChanged) {
|
|
46766
47102
|
peers.clear();
|
|
47103
|
+
peersByUid.clear();
|
|
47104
|
+
}
|
|
46767
47105
|
publish({ visibility: null, error: null, ...UNKNOWN_PRESENCE });
|
|
46768
47106
|
}
|
|
46769
47107
|
sources = next;
|
|
47108
|
+
if (accountChanged)
|
|
47109
|
+
ownTopic = next.uid ? derivePresenceTopic(realm2, next.uid) : null;
|
|
46770
47110
|
setPeers([...peers.values()]);
|
|
46771
47111
|
sync();
|
|
46772
47112
|
}
|
|
@@ -46807,10 +47147,10 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46807
47147
|
getPeer(profile) {
|
|
46808
47148
|
if (!profile?.uid)
|
|
46809
47149
|
return UNKNOWN_PRESENCE;
|
|
46810
|
-
const
|
|
46811
|
-
if (
|
|
47150
|
+
const identity = peersByUid.get(profile.uid);
|
|
47151
|
+
if (!identity || identity.chatSigningPK !== profile.chatSigningPK)
|
|
46812
47152
|
return UNKNOWN_PRESENCE;
|
|
46813
|
-
return records.get(
|
|
47153
|
+
return records.get(identity.topic)?.view || UNKNOWN_PRESENCE;
|
|
46814
47154
|
},
|
|
46815
47155
|
subscribe(listener) {
|
|
46816
47156
|
listeners.add(listener);
|
|
@@ -46829,6 +47169,7 @@ function createPresence({ cloud, diag = null, now = Date.now } = {}) {
|
|
|
46829
47169
|
listeners.clear();
|
|
46830
47170
|
peerListeners.clear();
|
|
46831
47171
|
peers.clear();
|
|
47172
|
+
peersByUid.clear();
|
|
46832
47173
|
records.clear();
|
|
46833
47174
|
}
|
|
46834
47175
|
});
|
|
@@ -47089,6 +47430,10 @@ function openCallMailbox({ cloud, capability, endpoint, protocol, onChange, onEr
|
|
|
47089
47430
|
}
|
|
47090
47431
|
return Object.freeze({
|
|
47091
47432
|
getSnapshot: () => candidate || record,
|
|
47433
|
+
async settled() {
|
|
47434
|
+
await tail;
|
|
47435
|
+
return record;
|
|
47436
|
+
},
|
|
47092
47437
|
async ready() {
|
|
47093
47438
|
const response = await channel.ready();
|
|
47094
47439
|
await consume(response.record);
|
|
@@ -47427,6 +47772,20 @@ function openAccountCallLease({ cloud, capability: accountCapability, endpoint,
|
|
|
47427
47772
|
});
|
|
47428
47773
|
}
|
|
47429
47774
|
|
|
47775
|
+
// ../../core/calls/sources.js
|
|
47776
|
+
function callVideoCapacity(participants, holder, count = 1) {
|
|
47777
|
+
return participants.filter((peer) => peer.holder !== holder).reduce((count2, peer) => count2 + (peer.media?.sources?.length || 0), 0) + count <= CALL_MAX_VIDEO_SOURCES;
|
|
47778
|
+
}
|
|
47779
|
+
function assertCallVideoSources(participants, holder, media) {
|
|
47780
|
+
const sources = media?.sources || [];
|
|
47781
|
+
if (!Array.isArray(sources) || sources.length > CALL_MAX_VIDEO_SOURCES_PER_PARTICIPANT || sources.some((source) => !/^[A-Za-z0-9_-]{1,64}$/u.test(source?.id || "") || !["screen", "camera"].includes(source.kind) || !Number.isInteger(source.contextId) || source.contextId < 1 || source.contextId >= 2 ** 24) || new Set(sources.map((source) => source.id)).size !== sources.length || new Set(sources.map((source) => source.contextId)).size !== sources.length) {
|
|
47782
|
+
throw Object.assign(new Error("invalid call video sources"), { code: "calls/invalid-source" });
|
|
47783
|
+
}
|
|
47784
|
+
if (sources.length && !callVideoCapacity(participants, holder, sources.length)) {
|
|
47785
|
+
throw Object.assign(new Error(`this call already has ${CALL_MAX_VIDEO_SOURCES} cameras or screens`), { code: "calls/video-full" });
|
|
47786
|
+
}
|
|
47787
|
+
}
|
|
47788
|
+
|
|
47430
47789
|
// ../../core/calls/room.js
|
|
47431
47790
|
function openCallRoom({
|
|
47432
47791
|
cloud,
|
|
@@ -47513,6 +47872,14 @@ function openCallRoom({
|
|
|
47513
47872
|
settleAdmission(admissionError("calls/taken-over", "call moved to another device"));
|
|
47514
47873
|
}
|
|
47515
47874
|
}
|
|
47875
|
+
function retireMembership() {
|
|
47876
|
+
departing = true;
|
|
47877
|
+
joined = false;
|
|
47878
|
+
clearTimeout(pulseTimer);
|
|
47879
|
+
disposeState(state);
|
|
47880
|
+
state = null;
|
|
47881
|
+
onRemoved?.();
|
|
47882
|
+
}
|
|
47516
47883
|
async function adopt(next, admissions) {
|
|
47517
47884
|
verifyCallMembers(next, admissions, protocol);
|
|
47518
47885
|
if (closed) {
|
|
@@ -47524,7 +47891,7 @@ function openCallRoom({
|
|
|
47524
47891
|
disposeState(previous);
|
|
47525
47892
|
const self = next.members.find((item) => toHex(item.leafId) === localHolder);
|
|
47526
47893
|
if (!self) {
|
|
47527
|
-
|
|
47894
|
+
retireMembership();
|
|
47528
47895
|
return;
|
|
47529
47896
|
}
|
|
47530
47897
|
if (!admissions.some((item) => item.holder === localHolder && item.actor === admission.actor && item.endpoint === endpoint.publicKey))
|
|
@@ -47598,7 +47965,15 @@ function openCallRoom({
|
|
|
47598
47965
|
if (bound)
|
|
47599
47966
|
leaving.add(packet.holder);
|
|
47600
47967
|
} else if (packet.kind === "pulse") {
|
|
47601
|
-
|
|
47968
|
+
let media = packet.data.media;
|
|
47969
|
+
try {
|
|
47970
|
+
assertCallVideoSources(participants(), packet.holder, media);
|
|
47971
|
+
} catch {
|
|
47972
|
+
media = published.get(packet.holder)?.media || null;
|
|
47973
|
+
}
|
|
47974
|
+
published.set(packet.holder, { ...packet.data, media });
|
|
47975
|
+
if (packet.holder === localHolder)
|
|
47976
|
+
ownMedia = media;
|
|
47602
47977
|
} else if (packet.kind === "signal") {
|
|
47603
47978
|
if (packet.data.to === localHolder && head?.participants.some((item) => item.holder === packet.holder && item.actor === packet.actor)) {
|
|
47604
47979
|
signals.push(packet);
|
|
@@ -47608,7 +47983,7 @@ function openCallRoom({
|
|
|
47608
47983
|
membershipChanged = true;
|
|
47609
47984
|
if (state && !replacement.participants.some((item) => item.holder === localHolder)) {
|
|
47610
47985
|
settleAdmission(cancelled2());
|
|
47611
|
-
|
|
47986
|
+
retireMembership();
|
|
47612
47987
|
return;
|
|
47613
47988
|
}
|
|
47614
47989
|
head = replacement;
|
|
@@ -47624,7 +47999,7 @@ function openCallRoom({
|
|
|
47624
47999
|
pendingCandidate = null;
|
|
47625
48000
|
if (next.removed) {
|
|
47626
48001
|
disposeState(next);
|
|
47627
|
-
|
|
48002
|
+
retireMembership();
|
|
47628
48003
|
return;
|
|
47629
48004
|
}
|
|
47630
48005
|
await adopt(next, change.participants);
|
|
@@ -47661,7 +48036,7 @@ function openCallRoom({
|
|
|
47661
48036
|
verifiedHead = null;
|
|
47662
48037
|
admittedIdentities.clear();
|
|
47663
48038
|
if (joined) {
|
|
47664
|
-
|
|
48039
|
+
retireMembership();
|
|
47665
48040
|
return;
|
|
47666
48041
|
}
|
|
47667
48042
|
if (submittedJoin)
|
|
@@ -47765,7 +48140,7 @@ function openCallRoom({
|
|
|
47765
48140
|
for (let attempt = 0;!closed && performance.now() < deadline; attempt += 1) {
|
|
47766
48141
|
if (kind === "join" && departing)
|
|
47767
48142
|
throw cancelled2();
|
|
47768
|
-
const current = mailbox.
|
|
48143
|
+
const current = await mailbox.settled();
|
|
47769
48144
|
if (!current?.head)
|
|
47770
48145
|
throw kind === "join" ? empty() : new Error("call ended");
|
|
47771
48146
|
if (kind === "join") {
|
|
@@ -47774,9 +48149,14 @@ function openCallRoom({
|
|
|
47774
48149
|
throw admissionFailure;
|
|
47775
48150
|
}
|
|
47776
48151
|
try {
|
|
48152
|
+
const value = typeof data === "function" ? data() : data;
|
|
48153
|
+
if (kind === "pulse")
|
|
48154
|
+
assertCallVideoSources(participants(), localHolder, value.media);
|
|
47777
48155
|
if (kind === "join")
|
|
47778
48156
|
submittedJoin = true;
|
|
47779
|
-
|
|
48157
|
+
const result = await mailbox.append([protocol.sign(kind, value)], { expectedRevision: current.revision });
|
|
48158
|
+
await mailbox.settled();
|
|
48159
|
+
return result;
|
|
47780
48160
|
} catch (error) {
|
|
47781
48161
|
if (closed)
|
|
47782
48162
|
throw cancelled2();
|
|
@@ -47807,7 +48187,7 @@ function openCallRoom({
|
|
|
47807
48187
|
if (closed || departing || !joined)
|
|
47808
48188
|
return;
|
|
47809
48189
|
try {
|
|
47810
|
-
await event("pulse", { ...audio, media: ownMedia });
|
|
48190
|
+
await event("pulse", () => ({ ...audio, media: ownMedia }));
|
|
47811
48191
|
} catch (error) {
|
|
47812
48192
|
fail(error);
|
|
47813
48193
|
}
|
|
@@ -47889,13 +48269,12 @@ function openCallRoom({
|
|
|
47889
48269
|
return event("signal", { to, signal });
|
|
47890
48270
|
},
|
|
47891
48271
|
async publishMedia(media) {
|
|
47892
|
-
|
|
47893
|
-
await event("pulse", { ...audio, media });
|
|
48272
|
+
await event("pulse", () => ({ ...audio, media }));
|
|
47894
48273
|
},
|
|
47895
48274
|
async setAudio(value) {
|
|
47896
48275
|
audio = { muted: value.muted === true || value.deafened === true, deafened: value.deafened === true };
|
|
47897
48276
|
if (joined && !departing && !closed)
|
|
47898
|
-
await event("pulse", { ...audio, media: ownMedia });
|
|
48277
|
+
await event("pulse", () => ({ ...audio, media: ownMedia }));
|
|
47899
48278
|
},
|
|
47900
48279
|
async leave() {
|
|
47901
48280
|
if (closed)
|
|
@@ -48092,15 +48471,15 @@ function createCallMediaSession({ port, mode, holder, preparation, provider, sig
|
|
|
48092
48471
|
const pendingSignals = [];
|
|
48093
48472
|
const incomingSignals = [];
|
|
48094
48473
|
function sourcesFor(peer) {
|
|
48095
|
-
if (!port.capabilities?.video || !videoCapable(peer) || !Array.isArray(peer.media.sources) || peer.media.sources.length >
|
|
48474
|
+
if (!port.capabilities?.video || !videoCapable(peer) || !Array.isArray(peer.media.sources) || peer.media.sources.length > CALL_MAX_VIDEO_SOURCES_PER_PARTICIPANT)
|
|
48096
48475
|
return [];
|
|
48097
|
-
const sources = peer.media.sources;
|
|
48476
|
+
const sources = peer.media.sources.filter((source) => mode === "group" ? source?.trackName != null : source?.mid != null);
|
|
48098
48477
|
if (sources.some((source) => !validSourceId(source?.id) || !["screen", "camera"].includes(source.kind) || !validContext(source.contextId) || (mode === "group" ? !/^[a-f0-9]{32}$/u.test(source.trackName || "") : !validMid(source.mid))) || new Set(sources.map((source) => source.id)).size !== sources.length || new Set(sources.map((source) => source.contextId)).size !== sources.length || new Set(sources.map((source) => mode === "group" ? source.trackName : source.mid)).size !== sources.length)
|
|
48099
48478
|
return [];
|
|
48100
48479
|
return sources;
|
|
48101
48480
|
}
|
|
48102
48481
|
function videoProjection() {
|
|
48103
|
-
const next = [...localSources.values()].map((source) => ({ id: source.id, holder, kind: source.kind, stream: source.stream, local: true }));
|
|
48482
|
+
const next = [...localSources.values()].filter((source) => source.admitted).map((source) => ({ id: source.id, holder, kind: source.kind, stream: source.stream, local: true }));
|
|
48104
48483
|
for (const peer of peers) {
|
|
48105
48484
|
if (peer.holder === holder)
|
|
48106
48485
|
continue;
|
|
@@ -48120,11 +48499,11 @@ function createCallMediaSession({ port, mode, holder, preparation, provider, sig
|
|
|
48120
48499
|
const base2 = mode === "group" ? { sessionId, trackName: microphoneTrack } : {};
|
|
48121
48500
|
if (!port.capabilities?.video)
|
|
48122
48501
|
return base2;
|
|
48123
|
-
return { ...base2, capabilities: ["video"], sources: [...localSources.values()].
|
|
48502
|
+
return { ...base2, capabilities: ["video"], sources: [...localSources.values()].map((source) => ({
|
|
48124
48503
|
id: source.id,
|
|
48125
48504
|
kind: source.kind,
|
|
48126
48505
|
contextId: source.contextId,
|
|
48127
|
-
...mode === "group" ? { trackName: source.trackName } : { mid: source.mid }
|
|
48506
|
+
...source.published ? mode === "group" ? { trackName: source.trackName } : { mid: source.mid } : {}
|
|
48128
48507
|
})) };
|
|
48129
48508
|
}
|
|
48130
48509
|
async function publishMedia() {
|
|
@@ -48579,7 +48958,7 @@ function createCallMediaSession({ port, mode, holder, preparation, provider, sig
|
|
|
48579
48958
|
current(generation);
|
|
48580
48959
|
if (videoCapable(peer))
|
|
48581
48960
|
for (const source of localSources.values()) {
|
|
48582
|
-
if (source.attached)
|
|
48961
|
+
if (!source.admitted || source.attached)
|
|
48583
48962
|
continue;
|
|
48584
48963
|
media.addSource(source);
|
|
48585
48964
|
source.attached = true;
|
|
@@ -48708,68 +49087,101 @@ function createCallMediaSession({ port, mode, holder, preparation, provider, sig
|
|
|
48708
49087
|
current();
|
|
48709
49088
|
videoProjection();
|
|
48710
49089
|
}
|
|
49090
|
+
function detachSource(source) {
|
|
49091
|
+
localSources.delete(source.id);
|
|
49092
|
+
source.stream.getTracks().forEach((track) => track.stop());
|
|
49093
|
+
if (source.attached)
|
|
49094
|
+
media?.removeSource(source.id);
|
|
49095
|
+
videoProjection();
|
|
49096
|
+
}
|
|
49097
|
+
async function closeSourceTrack(source) {
|
|
49098
|
+
if (mode === "group" && source.published) {
|
|
49099
|
+
await applyDescription(await provider("tracks/close", { sessionId, tracks: [{ mid: source.mid }] }));
|
|
49100
|
+
}
|
|
49101
|
+
}
|
|
48711
49102
|
return Object.freeze({
|
|
48712
|
-
|
|
48713
|
-
return port.capabilities?.
|
|
49103
|
+
supportsSource(kind) {
|
|
49104
|
+
return port.capabilities?.[kind] === true && port.capabilities?.video === true;
|
|
48714
49105
|
},
|
|
48715
|
-
async addSource(source) {
|
|
49106
|
+
async addSource(source, replaceId = null) {
|
|
48716
49107
|
current();
|
|
48717
49108
|
if (!started || !media?.addSource || !port.capabilities?.video)
|
|
48718
49109
|
throw new Error("video sharing unavailable");
|
|
48719
|
-
|
|
49110
|
+
const previous = localSources.get(replaceId);
|
|
49111
|
+
if (!validSourceId(source?.id) || !["screen", "camera"].includes(source.kind) || !validContext(source.contextId) || sourceContexts.has(source.contextId) || localSources.has(source.id) || localSources.size - (previous ? 1 : 0) >= CALL_MAX_VIDEO_SOURCES_PER_PARTICIPANT)
|
|
48720
49112
|
throw new Error("invalid call source");
|
|
48721
49113
|
const generation = connectionGeneration;
|
|
48722
|
-
if (
|
|
48723
|
-
|
|
48724
|
-
const local = { ...source, attached:
|
|
49114
|
+
if (previous)
|
|
49115
|
+
detachSource(previous);
|
|
49116
|
+
const local = { ...source, attached: false, admitted: false, published: false };
|
|
48725
49117
|
localSources.set(source.id, local);
|
|
48726
49118
|
sourceContexts.add(source.contextId);
|
|
48727
|
-
|
|
48728
|
-
|
|
48729
|
-
current(generation);
|
|
48730
|
-
return serialize(async () => {
|
|
48731
|
-
current(generation);
|
|
48732
|
-
if (localSources.get(source.id) !== local)
|
|
48733
|
-
return;
|
|
48734
|
-
if (mode === "group") {
|
|
48735
|
-
const offer = await media.offer();
|
|
49119
|
+
const result = await serialize(async () => {
|
|
49120
|
+
try {
|
|
48736
49121
|
current(generation);
|
|
48737
|
-
|
|
48738
|
-
|
|
48739
|
-
if (
|
|
48740
|
-
|
|
48741
|
-
|
|
49122
|
+
if (previous)
|
|
49123
|
+
await closeSourceTrack(previous);
|
|
49124
|
+
if (localSources.get(source.id) !== local)
|
|
49125
|
+
return;
|
|
49126
|
+
await publishMedia();
|
|
48742
49127
|
current(generation);
|
|
48743
|
-
if (
|
|
48744
|
-
|
|
48745
|
-
|
|
49128
|
+
if (localSources.get(source.id) !== local)
|
|
49129
|
+
return;
|
|
49130
|
+
local.admitted = true;
|
|
49131
|
+
if (mode === "group") {
|
|
49132
|
+
media.addSource(local);
|
|
49133
|
+
local.attached = true;
|
|
49134
|
+
}
|
|
49135
|
+
videoProjection();
|
|
49136
|
+
await updateKeys();
|
|
48746
49137
|
current(generation);
|
|
48747
|
-
|
|
49138
|
+
if (localSources.get(source.id) !== local)
|
|
49139
|
+
return;
|
|
49140
|
+
if (mode === "group") {
|
|
49141
|
+
const offer = await media.offer();
|
|
49142
|
+
current(generation);
|
|
49143
|
+
local.mid = media.sourceMid(source.id);
|
|
49144
|
+
local.trackName = toHex(randomBytes3(16));
|
|
49145
|
+
if (!validMid(local.mid))
|
|
49146
|
+
throw new Error("video track unavailable");
|
|
49147
|
+
const response = await provider("tracks/new", { sessionId, sessionDescription: sdp(offer), tracks: [{ location: "local", mid: local.mid, trackName: local.trackName }] });
|
|
49148
|
+
current(generation);
|
|
49149
|
+
if (response?.tracks?.length !== 1 || response.tracks[0].mid !== local.mid || response.tracks[0].trackName !== local.trackName)
|
|
49150
|
+
throw new Error("video publication failed");
|
|
49151
|
+
await applyDescription(response, true);
|
|
49152
|
+
current(generation);
|
|
49153
|
+
local.published = true;
|
|
49154
|
+
await publishMedia();
|
|
49155
|
+
} else
|
|
49156
|
+
await negotiate();
|
|
49157
|
+
} catch (error) {
|
|
49158
|
+
if (closed || generation !== connectionGeneration || !["calls/video-full", "calls/invalid-source"].includes(error?.code))
|
|
49159
|
+
throw error;
|
|
49160
|
+
if (localSources.get(source.id) === local)
|
|
49161
|
+
detachSource(local);
|
|
49162
|
+
await updateKeys();
|
|
48748
49163
|
await publishMedia();
|
|
48749
|
-
|
|
48750
|
-
|
|
49164
|
+
await closeSourceTrack(local);
|
|
49165
|
+
return { error };
|
|
49166
|
+
}
|
|
48751
49167
|
});
|
|
49168
|
+
if (result?.error)
|
|
49169
|
+
throw result.error;
|
|
48752
49170
|
},
|
|
48753
49171
|
removeSource(id) {
|
|
48754
49172
|
current();
|
|
48755
49173
|
const local = localSources.get(id);
|
|
48756
49174
|
if (!local)
|
|
48757
49175
|
return Promise.resolve();
|
|
48758
|
-
|
|
48759
|
-
local.stream.getTracks().forEach((track) => track.stop());
|
|
48760
|
-
if (local.attached)
|
|
48761
|
-
media?.removeSource(id);
|
|
48762
|
-
videoProjection();
|
|
49176
|
+
detachSource(local);
|
|
48763
49177
|
const removedKeys = updateKeys();
|
|
48764
49178
|
removedKeys.catch(() => {});
|
|
48765
49179
|
return serialize(async () => {
|
|
48766
49180
|
await removedKeys;
|
|
48767
49181
|
current();
|
|
48768
49182
|
await publishMedia();
|
|
48769
|
-
|
|
48770
|
-
|
|
48771
|
-
await applyDescription(await provider("tracks/close", { sessionId, tracks: [{ mid: local.mid }] }));
|
|
48772
|
-
} else if (local.attached) {
|
|
49183
|
+
await closeSourceTrack(local);
|
|
49184
|
+
if (mode === "direct" && local.attached) {
|
|
48773
49185
|
directDirty = true;
|
|
48774
49186
|
await negotiate();
|
|
48775
49187
|
}
|
|
@@ -49045,15 +49457,17 @@ function openCallObservation({ cloud, capability, endpoint, protocol, onChange,
|
|
|
49045
49457
|
}
|
|
49046
49458
|
|
|
49047
49459
|
// ../../core/calls/session.js
|
|
49048
|
-
var initial = () => ({ phase: "idle", chatId: null, callId: null, joiningChatId: null, mode: null, transport: null, available: null, elsewhere: null, participants: [], speaking: [], videoSources: [], sharingScreen: false, screenShareAvailable: false, peerAudio: {}, muted: false, deafened: false, microphoneVolume: 100, error: null });
|
|
49460
|
+
var initial = () => ({ phase: "idle", chatId: null, callId: null, joiningChatId: null, mode: null, transport: null, available: null, elsewhere: null, participants: [], speaking: [], videoSources: [], sharingScreen: false, screenShareAvailable: false, sharingCamera: false, cameraAvailable: false, peerAudio: {}, muted: false, deafened: false, microphoneVolume: 100, error: null });
|
|
49049
49461
|
var cancelled2 = () => Object.assign(new Error("call cancelled"), { code: "calls/cancelled" });
|
|
49050
49462
|
var ended = () => Object.assign(new Error("this call has ended"), { code: "calls/ended" });
|
|
49463
|
+
var RECENT_CALL_HINT_LIMIT = 32;
|
|
49051
49464
|
var callError = (chatId, error) => ({ chatId, code: error?.code || "calls/failed", message: error?.message || "could not connect the call" });
|
|
49052
49465
|
function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio, saveAudio, diag }) {
|
|
49053
49466
|
const listeners = new Set;
|
|
49054
49467
|
const discoveries = new Map;
|
|
49055
49468
|
const discoveryReads = new Map;
|
|
49056
49469
|
const observers = new Map;
|
|
49470
|
+
const messageHints = new Map;
|
|
49057
49471
|
const retiring = new Set;
|
|
49058
49472
|
let state = initial();
|
|
49059
49473
|
const confirmedPeerAudio = new Map;
|
|
@@ -49061,6 +49475,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49061
49475
|
let peerAudioSource = null;
|
|
49062
49476
|
let audioSource = normalizeCallAudio();
|
|
49063
49477
|
let pendingAudio = null;
|
|
49478
|
+
let audioSaveTail = Promise.resolve();
|
|
49064
49479
|
let identity = null;
|
|
49065
49480
|
let endpoint = null;
|
|
49066
49481
|
let leaseEndpoint = null;
|
|
@@ -49097,7 +49512,27 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49097
49512
|
diag?.("call.message.error", { event, code: error?.code || "" });
|
|
49098
49513
|
});
|
|
49099
49514
|
}
|
|
49100
|
-
const
|
|
49515
|
+
const captureFor = (kind) => kind === "camera" ? mediaPort?.captureCamera : mediaPort?.captureScreen;
|
|
49516
|
+
const canShare = (call, kind) => !!call && !call.leaving && call.mediaReady && !!captureFor(kind) && ["connecting", "connected", "waiting"].includes(state.phase) && call.media?.supportsSource(kind) === true && callVideoCapacity(call.participants, call.endpoint.holder);
|
|
49517
|
+
const sourceState = (call) => ({
|
|
49518
|
+
sharingScreen: call?.visual?.kind === "screen",
|
|
49519
|
+
sharingCamera: call?.visual?.kind === "camera",
|
|
49520
|
+
screenShareAvailable: canShare(call, "screen"),
|
|
49521
|
+
cameraAvailable: canShare(call, "camera")
|
|
49522
|
+
});
|
|
49523
|
+
async function captureVisual(operation) {
|
|
49524
|
+
while (operation.visual) {
|
|
49525
|
+
const source = operation.visual;
|
|
49526
|
+
try {
|
|
49527
|
+
await source.capture;
|
|
49528
|
+
} catch (error) {
|
|
49529
|
+
if (operation.visual === source)
|
|
49530
|
+
throw error;
|
|
49531
|
+
}
|
|
49532
|
+
if (operation.visual === source)
|
|
49533
|
+
return;
|
|
49534
|
+
}
|
|
49535
|
+
}
|
|
49101
49536
|
function current(token) {
|
|
49102
49537
|
if (closed || !identity || token !== generation)
|
|
49103
49538
|
throw cancelled2();
|
|
@@ -49118,17 +49553,29 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49118
49553
|
}
|
|
49119
49554
|
function available() {
|
|
49120
49555
|
const entry = discoveries.get(focused);
|
|
49121
|
-
const value = (chatId, current2) =>
|
|
49122
|
-
|
|
49123
|
-
|
|
49124
|
-
|
|
49125
|
-
|
|
49126
|
-
|
|
49127
|
-
|
|
49556
|
+
const value = (chatId, current2) => {
|
|
49557
|
+
if (!current2?.descriptor)
|
|
49558
|
+
return null;
|
|
49559
|
+
const roster = (active?.discovery === current2 ? active.participants : current2.roster)?.filter((peer) => ![...retiring].some((call) => call.callId === current2.descriptor.callId && call.endpoint.holder === peer.holder));
|
|
49560
|
+
if (roster?.length === 0)
|
|
49561
|
+
return null;
|
|
49562
|
+
return {
|
|
49563
|
+
chatId,
|
|
49564
|
+
callId: current2.descriptor.callId,
|
|
49565
|
+
startedAt: current2.descriptor.startedAt,
|
|
49566
|
+
participants: roster?.length ?? (current2.descriptor.participants || 1),
|
|
49567
|
+
roster: roster || []
|
|
49568
|
+
};
|
|
49569
|
+
};
|
|
49128
49570
|
for (const observer of observers.values()) {
|
|
49571
|
+
const observed = discoveries.get(observer.chatId);
|
|
49572
|
+
if (!observer.resolved && observed?.revision !== null && observed?.revision !== undefined) {
|
|
49573
|
+
observer.resolved = true;
|
|
49574
|
+
diag?.("call.discovery.observed", { elapsedMs: Date.now() - observer.startedAt, active: !!observed.descriptor });
|
|
49575
|
+
}
|
|
49129
49576
|
if (!observer.onAvailable)
|
|
49130
49577
|
continue;
|
|
49131
|
-
const next2 = value(observer.chatId,
|
|
49578
|
+
const next2 = value(observer.chatId, observed);
|
|
49132
49579
|
const fingerprint = JSON.stringify(next2);
|
|
49133
49580
|
if (observer.fingerprint === fingerprint)
|
|
49134
49581
|
continue;
|
|
@@ -49164,7 +49611,6 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49164
49611
|
return;
|
|
49165
49612
|
}
|
|
49166
49613
|
entry.observation?.close();
|
|
49167
|
-
entry.roster = [];
|
|
49168
49614
|
entry.observedCallId = callId;
|
|
49169
49615
|
const token = generation;
|
|
49170
49616
|
const protocol = createCallProtocol({ realm: cloud.environment, epochState: entry.epochState, identity, endpoint, callId });
|
|
@@ -49195,7 +49641,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49195
49641
|
}
|
|
49196
49642
|
function prune() {
|
|
49197
49643
|
for (const [chatId, value] of discoveries) {
|
|
49198
|
-
if (![...observers.values()].some((item) => item.chatId === chatId) && chatId !== active?.chatId && chatId !== joining?.chatId && ![...retiring].some((call) => call.chatId === chatId)) {
|
|
49644
|
+
if (!messageHints.get(chatId)?.watch && !discoveryReads.has(chatId) && !value.finishing.size && ![...observers.values()].some((item) => item.chatId === chatId) && chatId !== active?.chatId && chatId !== joining?.chatId && ![...retiring].some((call) => call.chatId === chatId)) {
|
|
49199
49645
|
closeDiscovery(value);
|
|
49200
49646
|
discoveries.delete(chatId);
|
|
49201
49647
|
} else
|
|
@@ -49217,7 +49663,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49217
49663
|
mailbox: null,
|
|
49218
49664
|
observation: null,
|
|
49219
49665
|
observedCallId: null,
|
|
49220
|
-
roster:
|
|
49666
|
+
roster: null,
|
|
49221
49667
|
revision: null,
|
|
49222
49668
|
expiredCallId: null,
|
|
49223
49669
|
finishing: new Map
|
|
@@ -49234,17 +49680,25 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49234
49680
|
if (descriptor && (!/^[a-f0-9]{64}$/u.test(descriptor.callId) || !/^[a-f0-9]{64}$/u.test(descriptor.secret) || descriptor.mode !== epochState.manifest.lineage || !Number.isSafeInteger(descriptor.startedAt) || descriptor.startedAt < 0 || descriptor.startedAt > Date.now() + 5000))
|
|
49235
49681
|
throw new Error("invalid call discovery");
|
|
49236
49682
|
const previous = entry.descriptor;
|
|
49683
|
+
if (previous?.callId !== descriptor?.callId)
|
|
49684
|
+
entry.roster = null;
|
|
49237
49685
|
entry.expiredCallId = !record.head ? previous?.callId || entry.expiredCallId : null;
|
|
49238
49686
|
entry.descriptor = descriptor;
|
|
49239
49687
|
if (!record.head && previous)
|
|
49240
49688
|
finishDiscovery(entry, previous);
|
|
49241
49689
|
const changed = entry.revision !== record.revision;
|
|
49242
49690
|
entry.revision = record.revision;
|
|
49691
|
+
if (!descriptor) {
|
|
49692
|
+
const hint = messageHints.get(chatId);
|
|
49693
|
+
if (hint)
|
|
49694
|
+
hint.watch = false;
|
|
49695
|
+
}
|
|
49243
49696
|
if (active?.discovery === entry && active.published && descriptor?.callId !== active.callId) {
|
|
49244
49697
|
leave();
|
|
49245
49698
|
}
|
|
49246
49699
|
observeRoster(entry, changed);
|
|
49247
49700
|
available();
|
|
49701
|
+
prune();
|
|
49248
49702
|
},
|
|
49249
49703
|
onError(error) {
|
|
49250
49704
|
if (token !== generation || discoveries.get(chatId) !== entry)
|
|
@@ -49291,7 +49745,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49291
49745
|
function observe(chatId, onAvailable) {
|
|
49292
49746
|
if (!chatId || closed)
|
|
49293
49747
|
return () => {};
|
|
49294
|
-
const observer = { chatId, onAvailable, fingerprint: undefined };
|
|
49748
|
+
const observer = { chatId, onAvailable, fingerprint: undefined, startedAt: Date.now(), resolved: false };
|
|
49295
49749
|
observers.set(observer, observer);
|
|
49296
49750
|
focused = chatId;
|
|
49297
49751
|
if (state.error?.chatId === chatId)
|
|
@@ -49343,11 +49797,11 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49343
49797
|
})();
|
|
49344
49798
|
const destination = (async () => {
|
|
49345
49799
|
if (!onlyExisting && !continuation)
|
|
49346
|
-
chatId = await chat.getSnapshot().materializeChat(chatId);
|
|
49800
|
+
chatId = await chat.getSnapshot().materializeChat(chatId, { signal: operation.controller.signal });
|
|
49347
49801
|
check();
|
|
49348
49802
|
joining.chatId = chatId;
|
|
49349
49803
|
step("chat");
|
|
49350
|
-
const epochState = continuation ? chat.getSnapshot().getOwnerChat(chatId)?.epochState : await chat.getSnapshot().prepareCall(chatId);
|
|
49804
|
+
const epochState = continuation ? chat.getSnapshot().getOwnerChat(chatId)?.epochState : await chat.getSnapshot().prepareCall(chatId, { signal: operation.controller.signal });
|
|
49351
49805
|
check();
|
|
49352
49806
|
if (!epochState)
|
|
49353
49807
|
throw cancelled2();
|
|
@@ -49356,7 +49810,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49356
49810
|
step("discovery");
|
|
49357
49811
|
return entry2;
|
|
49358
49812
|
})();
|
|
49359
|
-
const [, entry] = await Promise.all([permission, destination, operation
|
|
49813
|
+
const [, entry] = await Promise.all([permission, destination, captureVisual(operation)]);
|
|
49360
49814
|
check();
|
|
49361
49815
|
const discoveryRecord = entry.mailbox.getSnapshot();
|
|
49362
49816
|
const create = !entry.descriptor;
|
|
@@ -49392,7 +49846,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49392
49846
|
wasReady: false,
|
|
49393
49847
|
signalingReady: true,
|
|
49394
49848
|
ownershipReady: true,
|
|
49395
|
-
|
|
49849
|
+
visual: null,
|
|
49850
|
+
capturing: null,
|
|
49396
49851
|
sourceContext: 0,
|
|
49397
49852
|
leaving: false,
|
|
49398
49853
|
muteTail: Promise.resolve()
|
|
@@ -49438,7 +49893,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49438
49893
|
const waiting = call.mediaReady && descriptor.mode === "direct" && state.participants.length < 2;
|
|
49439
49894
|
const ready = call.mediaState === "connected" || waiting;
|
|
49440
49895
|
if (ready && call.signalingReady && call.ownershipReady) {
|
|
49441
|
-
call.wasReady =
|
|
49896
|
+
call.wasReady = !waiting;
|
|
49442
49897
|
clearTimeout(call.joinTimer);
|
|
49443
49898
|
call.joinTimer = null;
|
|
49444
49899
|
emit({ phase: waiting ? "waiting" : "connected" });
|
|
@@ -49452,7 +49907,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49452
49907
|
fail(new Error("call did not connect"));
|
|
49453
49908
|
}, 30000);
|
|
49454
49909
|
}
|
|
49455
|
-
emit(
|
|
49910
|
+
emit(sourceState(call));
|
|
49456
49911
|
};
|
|
49457
49912
|
call.connectionProgress = connectionProgress;
|
|
49458
49913
|
call.media = createCallMediaSession({
|
|
@@ -49499,8 +49954,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49499
49954
|
emit({ videoSources });
|
|
49500
49955
|
},
|
|
49501
49956
|
onSourceEnded(id) {
|
|
49502
|
-
if (valid() && call.
|
|
49503
|
-
|
|
49957
|
+
if (valid() && call.visual?.id === id)
|
|
49958
|
+
stopVisual().catch(() => {});
|
|
49504
49959
|
},
|
|
49505
49960
|
onError(error) {
|
|
49506
49961
|
if (valid())
|
|
@@ -49545,7 +50000,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49545
50000
|
if (preference)
|
|
49546
50001
|
call.media.setPeerAudio(peer.holder, { volume: preference.volume / 100, muted: preference.muted }).catch((error) => diag?.("call.peer.audio.error", { code: error?.code || "calls/audio" }));
|
|
49547
50002
|
}
|
|
49548
|
-
emit({ participants: value,
|
|
50003
|
+
emit({ participants: value, ...sourceState(call) });
|
|
49549
50004
|
available();
|
|
49550
50005
|
call.refresh?.();
|
|
49551
50006
|
if (call.mediaReady)
|
|
@@ -49576,6 +50031,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49576
50031
|
checkCall();
|
|
49577
50032
|
await call.room.start({ create });
|
|
49578
50033
|
checkCall();
|
|
50034
|
+
if (operation.visual)
|
|
50035
|
+
assertCallVideoSources(call.participants, callEndpoint.holder, { sources: [{ ...operation.visual, contextId: 1 }] });
|
|
49579
50036
|
step("admission");
|
|
49580
50037
|
if (create) {
|
|
49581
50038
|
try {
|
|
@@ -49677,12 +50134,14 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49677
50134
|
};
|
|
49678
50135
|
call.refresh = refresh;
|
|
49679
50136
|
refresh(true);
|
|
49680
|
-
|
|
49681
|
-
await operation
|
|
50137
|
+
while (operation.visual) {
|
|
50138
|
+
await captureVisual(operation);
|
|
49682
50139
|
check();
|
|
49683
|
-
|
|
50140
|
+
const source = operation.visual;
|
|
50141
|
+
await publishVisual(call, source);
|
|
49684
50142
|
check();
|
|
49685
|
-
operation.
|
|
50143
|
+
if (operation.visual === source)
|
|
50144
|
+
operation.visual = null;
|
|
49686
50145
|
}
|
|
49687
50146
|
return chatId;
|
|
49688
50147
|
} catch (error) {
|
|
@@ -49715,20 +50174,21 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49715
50174
|
throw new Error("open veyl to join a call");
|
|
49716
50175
|
return runJoin(chatId, expectedCallId, onlyExisting);
|
|
49717
50176
|
}
|
|
49718
|
-
function runJoin(chatId, expectedCallId = null, onlyExisting = false, continuation = null,
|
|
50177
|
+
function runJoin(chatId, expectedCallId = null, onlyExisting = false, continuation = null, visual = null) {
|
|
49719
50178
|
const intent = ++joinIntent;
|
|
49720
|
-
const operation = { predecessor: active, chatId, candidate: null, continuation,
|
|
50179
|
+
const operation = { predecessor: active, chatId, candidate: null, continuation, visual, controller: new AbortController };
|
|
49721
50180
|
let resolve, reject;
|
|
49722
50181
|
operation.promise = new Promise((done, fail2) => {
|
|
49723
50182
|
resolve = done;
|
|
49724
50183
|
reject = fail2;
|
|
49725
50184
|
});
|
|
49726
|
-
if (
|
|
49727
|
-
|
|
50185
|
+
if (visual)
|
|
50186
|
+
visual.promise = operation.promise;
|
|
49728
50187
|
const previous = joining;
|
|
49729
50188
|
joining = operation;
|
|
50189
|
+
previous?.controller.abort();
|
|
49730
50190
|
previous?.preparation?.close();
|
|
49731
|
-
previous?.
|
|
50191
|
+
previous?.visual?.stop();
|
|
49732
50192
|
emit({ joiningChatId: chatId });
|
|
49733
50193
|
if (previous?.candidate && previous.candidate !== active)
|
|
49734
50194
|
disposeCall(previous.candidate);
|
|
@@ -49752,7 +50212,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49752
50212
|
}
|
|
49753
50213
|
}
|
|
49754
50214
|
} finally {
|
|
49755
|
-
operation.
|
|
50215
|
+
operation.controller.abort();
|
|
50216
|
+
operation.visual?.stop();
|
|
49756
50217
|
if (joining === operation) {
|
|
49757
50218
|
joining = null;
|
|
49758
50219
|
emit({ joiningChatId: null });
|
|
@@ -49764,8 +50225,9 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49764
50225
|
}
|
|
49765
50226
|
function leave() {
|
|
49766
50227
|
joinIntent += 1;
|
|
50228
|
+
joining?.controller.abort();
|
|
49767
50229
|
joining?.preparation?.close();
|
|
49768
|
-
joining?.
|
|
50230
|
+
joining?.visual?.stop();
|
|
49769
50231
|
const candidate = joining?.candidate;
|
|
49770
50232
|
joining = null;
|
|
49771
50233
|
emit({ joiningChatId: null });
|
|
@@ -49780,8 +50242,9 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49780
50242
|
if (!operation)
|
|
49781
50243
|
return Promise.resolve();
|
|
49782
50244
|
joinIntent += 1;
|
|
50245
|
+
operation.controller.abort();
|
|
49783
50246
|
operation.preparation?.close();
|
|
49784
|
-
operation.
|
|
50247
|
+
operation.visual?.stop();
|
|
49785
50248
|
joining = null;
|
|
49786
50249
|
emit({ joiningChatId: null });
|
|
49787
50250
|
const pending = operation.candidate && operation.candidate !== active ? disposeCall(operation.candidate) : null;
|
|
@@ -49800,15 +50263,17 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49800
50263
|
leaving = retired.length ? Promise.race([released, Promise.all(retired.map((room) => room.closing))]) : released;
|
|
49801
50264
|
}
|
|
49802
50265
|
if (!call) {
|
|
49803
|
-
emit({ phase: "idle", chatId: null, callId: null, mode: null, transport: null, participants: [], speaking: [], videoSources: [], sharingScreen: false, screenShareAvailable: false });
|
|
50266
|
+
emit({ phase: "idle", chatId: null, callId: null, mode: null, transport: null, participants: [], speaking: [], videoSources: [], sharingScreen: false, screenShareAvailable: false, sharingCamera: false, cameraAvailable: false });
|
|
49804
50267
|
return leaving;
|
|
49805
50268
|
}
|
|
50269
|
+
if (call.discovery.descriptor?.callId === call.callId)
|
|
50270
|
+
call.discovery.roster = call.participants;
|
|
49806
50271
|
active = null;
|
|
49807
50272
|
leaving = disposeCall(call);
|
|
49808
50273
|
if (identity && discoveries.get(call.chatId) === call.discovery)
|
|
49809
50274
|
observeRoster(call.discovery);
|
|
49810
50275
|
available();
|
|
49811
|
-
emit({ phase: "idle", chatId: null, callId: null, mode: null, transport: null, participants: [], speaking: [], videoSources: [], sharingScreen: false, screenShareAvailable: false });
|
|
50276
|
+
emit({ phase: "idle", chatId: null, callId: null, mode: null, transport: null, participants: [], speaking: [], videoSources: [], sharingScreen: false, screenShareAvailable: false, sharingCamera: false, cameraAvailable: false });
|
|
49812
50277
|
return leaving;
|
|
49813
50278
|
}
|
|
49814
50279
|
async function retireDiscovery(call) {
|
|
@@ -49829,15 +50294,20 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49829
50294
|
function finishDiscovery(entry, descriptor) {
|
|
49830
50295
|
if (entry.finishing.has(descriptor.callId))
|
|
49831
50296
|
return;
|
|
49832
|
-
const finishing = retireDiscovery({ discovery: entry, callId: descriptor.callId, chatId: entry.epochState.manifest.chatId }).catch((error) => diag?.("call.close.error", { code: error?.code || "calls/discovery" })).finally(() =>
|
|
50297
|
+
const finishing = retireDiscovery({ discovery: entry, callId: descriptor.callId, chatId: entry.epochState.manifest.chatId }).catch((error) => diag?.("call.close.error", { code: error?.code || "calls/discovery" })).finally(() => {
|
|
50298
|
+
entry.finishing.delete(descriptor.callId);
|
|
50299
|
+
prune();
|
|
50300
|
+
});
|
|
49833
50301
|
entry.finishing.set(descriptor.callId, finishing);
|
|
49834
50302
|
}
|
|
49835
50303
|
function disposeCall(call) {
|
|
49836
50304
|
if (call.closing)
|
|
49837
50305
|
return call.closing;
|
|
49838
50306
|
call.leaving = true;
|
|
49839
|
-
call.
|
|
49840
|
-
call.
|
|
50307
|
+
call.capturing?.stop();
|
|
50308
|
+
call.capturing = null;
|
|
50309
|
+
call.visual?.stop();
|
|
50310
|
+
call.visual = null;
|
|
49841
50311
|
retiring.add(call);
|
|
49842
50312
|
clearTimeout(call.timer);
|
|
49843
50313
|
clearTimeout(call.joinTimer);
|
|
@@ -49879,28 +50349,40 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49879
50349
|
});
|
|
49880
50350
|
return call.closing;
|
|
49881
50351
|
}
|
|
49882
|
-
function
|
|
49883
|
-
if (!identity || closed || !
|
|
49884
|
-
return Promise.reject(new Error("screen sharing is unavailable in this call
|
|
50352
|
+
function startVisual(kind, chatId = active?.chatId) {
|
|
50353
|
+
if (!identity || closed || !captureFor(kind) || !mediaPort.capabilities?.[kind] || !mediaPort.capabilities?.video || !mls || !cloud.calls || !chatId)
|
|
50354
|
+
return Promise.reject(new Error(`${kind === "camera" ? "camera" : "screen sharing"} is unavailable in this call`));
|
|
49885
50355
|
const operation = joining && [joining.chatId, state.joiningChatId].includes(chatId) ? joining : null;
|
|
49886
|
-
if (operation?.screen)
|
|
49887
|
-
return operation.screen.promise;
|
|
49888
50356
|
const call = active?.chatId === chatId ? active : null;
|
|
49889
|
-
|
|
49890
|
-
|
|
50357
|
+
const pending = operation?.visual || call?.capturing;
|
|
50358
|
+
if (pending?.kind === kind)
|
|
50359
|
+
return pending.promise;
|
|
50360
|
+
if (!pending && call?.visual?.kind === kind)
|
|
50361
|
+
return call.visual.promise;
|
|
50362
|
+
if (call && !operation && !canShare(call, kind)) {
|
|
50363
|
+
try {
|
|
50364
|
+
assertCallVideoSources(call.participants, call.endpoint.holder, { sources: [{ id: "capture", kind, contextId: 1 }] });
|
|
50365
|
+
} catch (error) {
|
|
50366
|
+
return Promise.reject(error);
|
|
50367
|
+
}
|
|
50368
|
+
return Promise.reject(new Error(`${kind} is unavailable in this call`));
|
|
50369
|
+
}
|
|
49891
50370
|
if (!call && !foreground)
|
|
49892
50371
|
return Promise.reject(new Error("open veyl to join a call"));
|
|
49893
|
-
|
|
49894
|
-
|
|
50372
|
+
let rejectCapture;
|
|
50373
|
+
const cancellation = new Promise((_, reject) => {
|
|
50374
|
+
rejectCapture = reject;
|
|
50375
|
+
});
|
|
49895
50376
|
const source = {
|
|
49896
50377
|
id: toHex(randomBytes3(16)),
|
|
49897
50378
|
contextId: null,
|
|
49898
|
-
kind
|
|
50379
|
+
kind,
|
|
49899
50380
|
stream: null,
|
|
49900
50381
|
promise: null,
|
|
49901
50382
|
cancelled: false,
|
|
49902
50383
|
stop() {
|
|
49903
50384
|
source.cancelled = true;
|
|
50385
|
+
rejectCapture(cancelled2());
|
|
49904
50386
|
for (const track of source.stream?.getTracks() || []) {
|
|
49905
50387
|
track.removeEventListener?.("ended", sourceEnded);
|
|
49906
50388
|
track.stop();
|
|
@@ -49908,18 +50390,18 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49908
50390
|
}
|
|
49909
50391
|
};
|
|
49910
50392
|
const sourceEnded = () => {
|
|
49911
|
-
if (joining?.
|
|
50393
|
+
if (joining?.visual === source)
|
|
49912
50394
|
cancelJoin();
|
|
49913
|
-
else if (active?.
|
|
49914
|
-
|
|
50395
|
+
else if (active?.visual === source || active?.capturing === source)
|
|
50396
|
+
stopVisual().catch(() => {});
|
|
49915
50397
|
};
|
|
49916
50398
|
let capturing;
|
|
49917
50399
|
try {
|
|
49918
|
-
capturing =
|
|
50400
|
+
capturing = captureFor(kind).call(mediaPort);
|
|
49919
50401
|
} catch (error) {
|
|
49920
50402
|
return Promise.reject(error);
|
|
49921
50403
|
}
|
|
49922
|
-
source.capture = Promise.resolve(capturing).then((stream) => {
|
|
50404
|
+
source.capture = Promise.race([cancellation, Promise.resolve(capturing).then((stream) => {
|
|
49923
50405
|
if (source.cancelled || stream.getTracks().some((track) => track.readyState === "ended")) {
|
|
49924
50406
|
stream.getTracks().forEach((track) => track.stop());
|
|
49925
50407
|
throw cancelled2();
|
|
@@ -49927,64 +50409,78 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49927
50409
|
source.stream = stream;
|
|
49928
50410
|
for (const track of stream.getTracks())
|
|
49929
50411
|
track.addEventListener?.("ended", sourceEnded, { once: true });
|
|
49930
|
-
});
|
|
50412
|
+
})]);
|
|
49931
50413
|
if (operation) {
|
|
50414
|
+
operation.visual = source;
|
|
50415
|
+
if (pending !== call?.visual)
|
|
50416
|
+
pending?.stop();
|
|
49932
50417
|
source.promise = Promise.all([source.capture, operation.promise]).then(() => {
|
|
49933
50418
|
return;
|
|
49934
50419
|
});
|
|
49935
|
-
operation.screen = source;
|
|
49936
50420
|
return source.promise;
|
|
49937
50421
|
}
|
|
49938
50422
|
if (!call) {
|
|
49939
50423
|
source.promise = runJoin(chatId, null, false, null, source);
|
|
49940
50424
|
return source.promise;
|
|
49941
50425
|
}
|
|
49942
|
-
call.
|
|
50426
|
+
call.capturing = source;
|
|
50427
|
+
if (pending !== call.visual)
|
|
50428
|
+
pending?.stop();
|
|
49943
50429
|
source.promise = source.capture.then(() => {
|
|
49944
|
-
if (!
|
|
50430
|
+
if (source.cancelled || active !== call || call.capturing !== source || call.leaving || !call.mediaReady)
|
|
49945
50431
|
throw cancelled2();
|
|
49946
|
-
return
|
|
50432
|
+
return publishVisual(call, source);
|
|
49947
50433
|
}).catch(async (error) => {
|
|
49948
50434
|
source.stop();
|
|
49949
|
-
if (call.
|
|
49950
|
-
call.
|
|
50435
|
+
if (call.visual === source) {
|
|
50436
|
+
call.visual = null;
|
|
49951
50437
|
if (active === call)
|
|
49952
|
-
emit(
|
|
50438
|
+
emit(sourceState(call));
|
|
49953
50439
|
if (!call.leaving && source.stream)
|
|
49954
50440
|
await call.media.removeSource(source.id).catch(() => {});
|
|
49955
50441
|
}
|
|
49956
50442
|
throw error;
|
|
50443
|
+
}).finally(() => {
|
|
50444
|
+
if (call.capturing === source)
|
|
50445
|
+
call.capturing = null;
|
|
49957
50446
|
});
|
|
49958
50447
|
return source.promise;
|
|
49959
50448
|
}
|
|
49960
|
-
async function
|
|
49961
|
-
if (source.cancelled || active !== call || call.leaving || !call.mediaReady || !call.media.
|
|
50449
|
+
async function publishVisual(call, source) {
|
|
50450
|
+
if (source.cancelled || active !== call || call.leaving || !call.mediaReady || !call.media.supportsSource(source.kind))
|
|
49962
50451
|
throw cancelled2();
|
|
50452
|
+
const previous = call.visual;
|
|
49963
50453
|
source.contextId = ++call.sourceContext;
|
|
49964
|
-
|
|
49965
|
-
|
|
49966
|
-
|
|
49967
|
-
|
|
50454
|
+
previous?.stop();
|
|
50455
|
+
call.visual = source;
|
|
50456
|
+
emit(sourceState(call));
|
|
50457
|
+
await call.media.addSource(source, previous?.id);
|
|
50458
|
+
if (active !== call || call.visual !== source)
|
|
49968
50459
|
throw cancelled2();
|
|
49969
50460
|
}
|
|
49970
|
-
async function
|
|
49971
|
-
if (joining?.
|
|
50461
|
+
async function stopVisual(kind) {
|
|
50462
|
+
if (joining?.visual && (!kind || joining.visual.kind === kind))
|
|
49972
50463
|
return cancelJoin();
|
|
49973
50464
|
const call = active;
|
|
49974
|
-
|
|
49975
|
-
|
|
50465
|
+
if (call?.capturing && (!kind || call.capturing.kind === kind || call.visual?.kind === kind)) {
|
|
50466
|
+
call.capturing.stop();
|
|
50467
|
+
call.capturing = null;
|
|
50468
|
+
}
|
|
50469
|
+
const source = call?.visual;
|
|
50470
|
+
if (!source || kind && source.kind !== kind)
|
|
49976
50471
|
return;
|
|
49977
|
-
call.
|
|
50472
|
+
call.visual = null;
|
|
49978
50473
|
source.stop();
|
|
49979
|
-
emit(
|
|
50474
|
+
emit(sourceState(call));
|
|
49980
50475
|
if (source.stream)
|
|
49981
50476
|
await call.media.removeSource(source.id);
|
|
49982
50477
|
}
|
|
49983
50478
|
function applyAudio(call) {
|
|
49984
50479
|
const audio = { muted: state.muted || state.deafened, deafened: state.deafened };
|
|
49985
50480
|
const applied = Promise.all([call.media.mute(audio.muted), call.media.deafen(audio.deafened)]);
|
|
50481
|
+
call.pendingAudio = audio;
|
|
49986
50482
|
const result = Promise.all([applied, call.muteTail]).then(() => {
|
|
49987
|
-
if (!call.leaving)
|
|
50483
|
+
if (!call.leaving && call.pendingAudio === audio)
|
|
49988
50484
|
return call.room.setAudio(audio);
|
|
49989
50485
|
});
|
|
49990
50486
|
call.muteTail = result.catch(() => {});
|
|
@@ -49996,14 +50492,16 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
49996
50492
|
return;
|
|
49997
50493
|
const token = generation;
|
|
49998
50494
|
pendingAudio = value;
|
|
49999
|
-
const saving =
|
|
50495
|
+
const saving = audioSaveTail.then(async () => {
|
|
50000
50496
|
if (generation !== token)
|
|
50001
50497
|
throw cancelled2();
|
|
50002
|
-
|
|
50003
|
-
|
|
50498
|
+
if (pendingAudio !== value)
|
|
50499
|
+
return;
|
|
50500
|
+
await saveAudio(value);
|
|
50004
50501
|
if (generation === token)
|
|
50005
50502
|
audioSource = value;
|
|
50006
50503
|
});
|
|
50504
|
+
audioSaveTail = saving.catch(() => {});
|
|
50007
50505
|
try {
|
|
50008
50506
|
await Promise.all([saving, applyAudioPreferences(value)]);
|
|
50009
50507
|
} finally {
|
|
@@ -50174,6 +50672,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50174
50672
|
endpoint = null;
|
|
50175
50673
|
discoveries.clear();
|
|
50176
50674
|
discoveryReads.clear();
|
|
50675
|
+
messageHints.clear();
|
|
50177
50676
|
identity = session;
|
|
50178
50677
|
const stopped = leave();
|
|
50179
50678
|
Promise.resolve(stopped).finally(() => {
|
|
@@ -50188,6 +50687,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50188
50687
|
confirmedPeerAudio.clear();
|
|
50189
50688
|
pendingPeerAudio.clear();
|
|
50190
50689
|
pendingAudio = null;
|
|
50690
|
+
audioSaveTail = Promise.resolve();
|
|
50191
50691
|
audioSource = normalizeCallAudio(settings.callAudio);
|
|
50192
50692
|
peerAudioSource = session ? peerAudio : null;
|
|
50193
50693
|
for (const [chatPK, value] of Object.entries(peerAudioSource || {}))
|
|
@@ -50205,10 +50705,41 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50205
50705
|
return retirement;
|
|
50206
50706
|
}
|
|
50207
50707
|
function refreshObserved() {
|
|
50208
|
-
for (const chatId of
|
|
50708
|
+
for (const chatId of observedChatIds()) {
|
|
50209
50709
|
readDiscovery(chatId).catch((error) => diag?.("call.discovery.error", { stage: "prepare", code: error?.code || "calls/admission" }));
|
|
50210
50710
|
}
|
|
50211
50711
|
}
|
|
50712
|
+
function observedChatIds() {
|
|
50713
|
+
return new Set([...observers.values()].map((item) => item.chatId).concat([...messageHints].filter(([, hint]) => hint.watch).map(([chatId]) => chatId)));
|
|
50714
|
+
}
|
|
50715
|
+
const unsubscribeMessages = chat.subscribeMessages((chatId, messages) => {
|
|
50716
|
+
if (!identity || closed)
|
|
50717
|
+
return;
|
|
50718
|
+
let latest = null;
|
|
50719
|
+
for (const message of messages) {
|
|
50720
|
+
if (!isServerConfirmedMsg(message) || !readCallMessage(message))
|
|
50721
|
+
continue;
|
|
50722
|
+
if (!latest || getMessageOrderMs(message) > getMessageOrderMs(latest) || getMessageOrderMs(message) === getMessageOrderMs(latest) && getMessageKey(message) > getMessageKey(latest))
|
|
50723
|
+
latest = message;
|
|
50724
|
+
}
|
|
50725
|
+
if (!latest)
|
|
50726
|
+
return;
|
|
50727
|
+
const at = getMessageOrderMs(latest), key = getMessageKey(latest);
|
|
50728
|
+
if (!Number.isFinite(at) || !key)
|
|
50729
|
+
return;
|
|
50730
|
+
const previous = messageHints.get(chatId);
|
|
50731
|
+
if (previous && (previous.at > at || previous.at === at && previous.key >= key))
|
|
50732
|
+
return;
|
|
50733
|
+
const entry = discoveries.get(chatId);
|
|
50734
|
+
const watch = latest.event === "started" || previous?.watch === true || !!entry?.descriptor;
|
|
50735
|
+
messageHints.delete(chatId);
|
|
50736
|
+
messageHints.set(chatId, { at, key, watch });
|
|
50737
|
+
while (messageHints.size > RECENT_CALL_HINT_LIMIT)
|
|
50738
|
+
messageHints.delete(messageHints.keys().next().value);
|
|
50739
|
+
prune();
|
|
50740
|
+
if (watch && online)
|
|
50741
|
+
readDiscovery(chatId).catch((error) => diag?.("call.discovery.error", { stage: "message", code: error?.code || "calls/discovery" }));
|
|
50742
|
+
});
|
|
50212
50743
|
const unsubscribe = chat.subscribe(() => {
|
|
50213
50744
|
if (!identity)
|
|
50214
50745
|
return;
|
|
@@ -50222,6 +50753,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50222
50753
|
const intent = ++joinIntent;
|
|
50223
50754
|
const previous = active || joining?.candidate;
|
|
50224
50755
|
active = null;
|
|
50756
|
+
joining?.controller.abort();
|
|
50225
50757
|
joining = null;
|
|
50226
50758
|
if (previous)
|
|
50227
50759
|
disposeCall(previous);
|
|
@@ -50233,6 +50765,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50233
50765
|
videoSources: [],
|
|
50234
50766
|
sharingScreen: false,
|
|
50235
50767
|
screenShareAvailable: false,
|
|
50768
|
+
sharingCamera: false,
|
|
50769
|
+
cameraAvailable: false,
|
|
50236
50770
|
participants: state.participants.filter((member) => members.has(member.chatPK))
|
|
50237
50771
|
});
|
|
50238
50772
|
diag?.("call.epoch.transition", { stage: "starting", participants: state.participants.length });
|
|
@@ -50251,11 +50785,11 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50251
50785
|
closeDiscovery(entry);
|
|
50252
50786
|
discoveries.delete(chatId);
|
|
50253
50787
|
available();
|
|
50254
|
-
if (online &&
|
|
50788
|
+
if (online && observedChatIds().has(chatId))
|
|
50255
50789
|
readDiscovery(chatId).catch(() => {});
|
|
50256
50790
|
}
|
|
50257
50791
|
if (online)
|
|
50258
|
-
for (const chatId of
|
|
50792
|
+
for (const chatId of observedChatIds()) {
|
|
50259
50793
|
if (!discoveries.has(chatId) && !discoveryReads.has(chatId) && chat.getSnapshot().getOwnerChat(chatId)?.epochState) {
|
|
50260
50794
|
readDiscovery(chatId).catch((error) => diag?.("call.discovery.error", { stage: "prepare", code: error?.code || "calls/admission" }));
|
|
50261
50795
|
}
|
|
@@ -50273,8 +50807,10 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50273
50807
|
leave,
|
|
50274
50808
|
cancelJoin,
|
|
50275
50809
|
setMicrophoneVolume,
|
|
50276
|
-
startScreenShare,
|
|
50277
|
-
stopScreenShare,
|
|
50810
|
+
startScreenShare: (chatId) => startVisual("screen", chatId),
|
|
50811
|
+
stopScreenShare: () => stopVisual("screen"),
|
|
50812
|
+
startCamera: (chatId) => startVisual("camera", chatId),
|
|
50813
|
+
stopCamera: () => stopVisual("camera"),
|
|
50278
50814
|
async resolveActiveCall(chatId) {
|
|
50279
50815
|
const entry = await readDiscovery(chatId);
|
|
50280
50816
|
return entry.descriptor?.callId || null;
|
|
@@ -50289,6 +50825,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50289
50825
|
return Promise.reject(ended());
|
|
50290
50826
|
return join(chatId, callId, true);
|
|
50291
50827
|
},
|
|
50828
|
+
setAudio,
|
|
50292
50829
|
setMuted(value) {
|
|
50293
50830
|
return setAudio({ muted: value === true });
|
|
50294
50831
|
},
|
|
@@ -50322,6 +50859,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio,
|
|
|
50322
50859
|
});
|
|
50323
50860
|
closed = true;
|
|
50324
50861
|
const stopped = setSources(null);
|
|
50862
|
+
unsubscribeMessages();
|
|
50325
50863
|
unsubscribe();
|
|
50326
50864
|
listeners.clear();
|
|
50327
50865
|
Promise.resolve(stopped).finally(() => Promise.all([mls?.close?.(), mediaPort?.close?.()])).then(finish, reject);
|
|
@@ -53524,6 +54062,18 @@ function stablePeerMap(previous, next) {
|
|
|
53524
54062
|
}
|
|
53525
54063
|
return previous;
|
|
53526
54064
|
}
|
|
54065
|
+
function replaceIndexedPeers(index, updates, field) {
|
|
54066
|
+
let next = index;
|
|
54067
|
+
for (const peer of updates.values()) {
|
|
54068
|
+
const key = peer[field];
|
|
54069
|
+
if (!key || index.get(key)?.uid !== peer.uid)
|
|
54070
|
+
continue;
|
|
54071
|
+
if (next === index)
|
|
54072
|
+
next = new Map(index);
|
|
54073
|
+
next.set(key, peer);
|
|
54074
|
+
}
|
|
54075
|
+
return next;
|
|
54076
|
+
}
|
|
53527
54077
|
function stableValueSet(previous, next) {
|
|
53528
54078
|
if (previous === next)
|
|
53529
54079
|
return previous;
|
|
@@ -53551,12 +54101,11 @@ function peerProfileSnapshot(peer) {
|
|
|
53551
54101
|
active: !!peer.active,
|
|
53552
54102
|
presence: peer.presence,
|
|
53553
54103
|
accountType: peer.accountType || null,
|
|
53554
|
-
blocked: !!peer.blocked
|
|
53555
|
-
hidden: !!peer.hidden
|
|
54104
|
+
blocked: !!peer.blocked
|
|
53556
54105
|
};
|
|
53557
54106
|
}
|
|
53558
54107
|
function samePeerProfile(left, right) {
|
|
53559
|
-
return left === right || !!left && !!right && left.uid === right.uid && left.username === right.username && left.chatPK === right.chatPK && left.chatSigningPK === right.chatSigningPK && left.notificationPK === right.notificationPK && sameChatAdmission(left.chatAdmission, right.chatAdmission) && left.walletPK === right.walletPK && left.avatar === right.avatar && left.avatarVersion === right.avatarVersion && left.active === right.active && left.presence === right.presence && left.accountType === right.accountType && left.blocked === right.blocked
|
|
54108
|
+
return left === right || !!left && !!right && left.uid === right.uid && left.username === right.username && left.chatPK === right.chatPK && left.chatSigningPK === right.chatSigningPK && left.notificationPK === right.notificationPK && sameChatAdmission(left.chatAdmission, right.chatAdmission) && left.walletPK === right.walletPK && left.avatar === right.avatar && left.avatarVersion === right.avatarVersion && left.active === right.active && left.presence === right.presence && left.accountType === right.accountType && left.blocked === right.blocked;
|
|
53560
54109
|
}
|
|
53561
54110
|
function normalizeProfileIdentity(identity = {}) {
|
|
53562
54111
|
const uid = String(identity?.uid || "").trim();
|
|
@@ -53646,7 +54195,6 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
53646
54195
|
let blockedUids = [];
|
|
53647
54196
|
let blockedUidsKey = "";
|
|
53648
54197
|
let blockedUidSet = new Set;
|
|
53649
|
-
let hiddenPeerUidSet = new Set;
|
|
53650
54198
|
let seenWalletPKs = new Set;
|
|
53651
54199
|
let seenChatPKs = new Set;
|
|
53652
54200
|
let walletPKs = [];
|
|
@@ -53680,7 +54228,7 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
53680
54228
|
subscriberCount += 1;
|
|
53681
54229
|
if (!firstSubscriber)
|
|
53682
54230
|
return;
|
|
53683
|
-
unsubscribePresence = presence.subscribePeers(
|
|
54231
|
+
unsubscribePresence = presence.subscribePeers(updatePresence);
|
|
53684
54232
|
hydrateLocalAvatars();
|
|
53685
54233
|
const becameReady = startProfileLoad();
|
|
53686
54234
|
if (becameReady) {
|
|
@@ -53712,7 +54260,7 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
53712
54260
|
function resolveProfile(identity) {
|
|
53713
54261
|
if (!identity)
|
|
53714
54262
|
return null;
|
|
53715
|
-
const peer = (identity.uid ?
|
|
54263
|
+
const peer = (identity.uid ? allPeerByUid.get(identity.uid) : null) ?? (identity.username ? peerByUsername.get(identity.username) : null) ?? (identity.chatPK ? peerByChatPK.get(identity.chatPK) : null) ?? (identity.walletPK ? peerByWalletPK.get(identity.walletPK) : null) ?? null;
|
|
53716
54264
|
return peer?.uid ? peerProfilesByUid.get(peer.uid) || null : null;
|
|
53717
54265
|
}
|
|
53718
54266
|
function emitProfiles() {
|
|
@@ -53743,8 +54291,6 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
53743
54291
|
primePeer,
|
|
53744
54292
|
findPeer,
|
|
53745
54293
|
updatePeer,
|
|
53746
|
-
dropPeer,
|
|
53747
|
-
restorePeer,
|
|
53748
54294
|
loadBlockedPeers
|
|
53749
54295
|
};
|
|
53750
54296
|
}
|
|
@@ -53822,7 +54368,7 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
53822
54368
|
function syncPresencePeers() {
|
|
53823
54369
|
const candidates = new Map;
|
|
53824
54370
|
const add2 = (profile) => {
|
|
53825
|
-
if (!profile?.uid || profile.blocked ||
|
|
54371
|
+
if (!profile?.uid || profile.blocked || candidates.size >= RECENT_PEER_REFRESH_LIMIT)
|
|
53826
54372
|
return;
|
|
53827
54373
|
candidates.set(profile.uid, profile);
|
|
53828
54374
|
};
|
|
@@ -53846,33 +54392,61 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
53846
54392
|
function buildBasePeers() {
|
|
53847
54393
|
allPeers = assemblePeers(walletPeerSet, sources.chatPeers, discoveredPeerUids);
|
|
53848
54394
|
}
|
|
53849
|
-
function
|
|
53850
|
-
const
|
|
53851
|
-
|
|
53852
|
-
|
|
53853
|
-
|
|
54395
|
+
function projectPeer(peer) {
|
|
54396
|
+
const activity = presence.getPeer(peer);
|
|
54397
|
+
const next = {
|
|
54398
|
+
...peer,
|
|
54399
|
+
active: activity.availability === "online",
|
|
54400
|
+
presence: activity,
|
|
54401
|
+
blocked: blockedUidSet.has(peer.uid)
|
|
54402
|
+
};
|
|
54403
|
+
const previous = allPeerByUid.get(peer.uid);
|
|
54404
|
+
return samePeerRecord(previous, next) ? previous : next;
|
|
54405
|
+
}
|
|
54406
|
+
function updatePresence(changedUids) {
|
|
54407
|
+
const updates = new Map;
|
|
54408
|
+
for (const uid of changedUids) {
|
|
54409
|
+
const previous = allPeerByUid.get(uid);
|
|
54410
|
+
if (!previous)
|
|
54411
|
+
continue;
|
|
54412
|
+
const next = projectPeer(previous);
|
|
54413
|
+
if (next !== previous)
|
|
54414
|
+
updates.set(uid, next);
|
|
54415
|
+
}
|
|
54416
|
+
if (!updates.size)
|
|
54417
|
+
return;
|
|
54418
|
+
allPeerByUid = replaceIndexedPeers(allPeerByUid, updates, "uid");
|
|
54419
|
+
peerByUid = replaceIndexedPeers(peerByUid, updates, "uid");
|
|
54420
|
+
peerByWalletPK = replaceIndexedPeers(peerByWalletPK, updates, "walletPK");
|
|
54421
|
+
peerByChatPK = replaceIndexedPeers(peerByChatPK, updates, "chatPK");
|
|
54422
|
+
peerByUsername = replaceIndexedPeers(peerByUsername, updates, "username");
|
|
54423
|
+
const replaceList = (list) => stablePeerList(list, list.map((peer) => updates.get(peer.uid) || peer));
|
|
54424
|
+
visiblePeers = replaceList(visiblePeers);
|
|
54425
|
+
blockedPeerItems = replaceList(blockedPeerItems);
|
|
54426
|
+
const all = replaceList(recentPeers.all);
|
|
54427
|
+
const wallet = replaceList(recentPeers.wallet);
|
|
54428
|
+
const chat = replaceList(recentPeers.chat);
|
|
54429
|
+
if (all !== recentPeers.all || wallet !== recentPeers.wallet || chat !== recentPeers.chat)
|
|
54430
|
+
recentPeers = { all, wallet, chat };
|
|
54431
|
+
peerProfilesByUid = new Map(peerProfilesByUid);
|
|
54432
|
+
for (const peer of updates.values()) {
|
|
54433
|
+
const next = peerProfileSnapshot(peer);
|
|
54434
|
+
const previous = peerProfilesByUid.get(peer.uid);
|
|
54435
|
+
peerProfilesByUid.set(peer.uid, samePeerProfile(previous, next) ? previous : next);
|
|
53854
54436
|
}
|
|
54437
|
+
publish();
|
|
54438
|
+
}
|
|
54439
|
+
function buildPeerProjection() {
|
|
53855
54440
|
const peerMap = new Map;
|
|
53856
54441
|
for (const peer of allPeers)
|
|
53857
54442
|
mergePeerRecord(peerMap, peer);
|
|
53858
54443
|
for (const peer of blockedPeers)
|
|
53859
54444
|
mergePeerRecord(peerMap, peer);
|
|
53860
|
-
const taggedPeers = Array.from(peerMap.values()).map(
|
|
53861
|
-
|
|
53862
|
-
const next = {
|
|
53863
|
-
...peer,
|
|
53864
|
-
active: activity.availability === "online",
|
|
53865
|
-
presence: activity,
|
|
53866
|
-
blocked: blockedPeerUidSet.has(peer.uid) || hiddenPeerUidSet.has(peer.uid),
|
|
53867
|
-
hidden: hiddenPeerUidSet.has(peer.uid)
|
|
53868
|
-
};
|
|
53869
|
-
const previous = allPeerByUid.get(peer.uid);
|
|
53870
|
-
return samePeerRecord(previous, next) ? previous : next;
|
|
53871
|
-
});
|
|
53872
|
-
visiblePeers = stablePeerList(visiblePeers, taggedPeers.filter((peer) => !peer.blocked && !peer.hidden));
|
|
54445
|
+
const taggedPeers = Array.from(peerMap.values()).map(projectPeer);
|
|
54446
|
+
visiblePeers = stablePeerList(visiblePeers, taggedPeers.filter((peer) => !peer.blocked));
|
|
53873
54447
|
const blockedByUid = new Map(taggedPeers.filter((peer) => peer?.uid && peer.blocked).map((peer) => [peer.uid, peer]));
|
|
53874
54448
|
const orderedBlocked = blockedUids.map((uid) => blockedByUid.get(uid)).filter((peer) => peer?.uid);
|
|
53875
|
-
blockedPeerItems = stablePeerList(blockedPeerItems, orderedBlocked
|
|
54449
|
+
blockedPeerItems = stablePeerList(blockedPeerItems, orderedBlocked);
|
|
53876
54450
|
blockedChatPKSet = stableValueSet(blockedChatPKSet, new Set(taggedPeers.filter((peer) => peer.blocked && peer.chatPK).map((peer) => peer.chatPK)));
|
|
53877
54451
|
const allIndexes = indexPeers(taggedPeers);
|
|
53878
54452
|
const visibleIndexes = indexPeers(visiblePeers);
|
|
@@ -53985,7 +54559,6 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
53985
54559
|
blockedPeersReady = false;
|
|
53986
54560
|
discoveredPeerUids = new Set;
|
|
53987
54561
|
blockedPeers = [];
|
|
53988
|
-
hiddenPeerUidSet = new Set;
|
|
53989
54562
|
seenWalletPKs = new Set;
|
|
53990
54563
|
seenChatPKs = new Set;
|
|
53991
54564
|
allPeers = [];
|
|
@@ -54098,6 +54671,10 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
54098
54671
|
blockedUids = nextBlockedUids;
|
|
54099
54672
|
blockedUidsKey = nextBlockedUidsKey;
|
|
54100
54673
|
blockedUidSet = new Set(blockedUids);
|
|
54674
|
+
for (const uid of blockedUids) {
|
|
54675
|
+
if (allPeerByUid.has(uid))
|
|
54676
|
+
discoveredPeerUids.add(uid);
|
|
54677
|
+
}
|
|
54101
54678
|
if (blockedPeersReady) {
|
|
54102
54679
|
blockedPeers = blockedPeers.filter((peer) => blockedUidSet.has(peer?.uid));
|
|
54103
54680
|
}
|
|
@@ -54139,7 +54716,7 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
54139
54716
|
const enrichedPeer = await fetchAndCachePeer(partialProfile);
|
|
54140
54717
|
if (!isCurrent(session) || !enrichedPeer)
|
|
54141
54718
|
return null;
|
|
54142
|
-
if (blockedUidSet.has(enrichedPeer.uid)
|
|
54719
|
+
if (blockedUidSet.has(enrichedPeer.uid))
|
|
54143
54720
|
return null;
|
|
54144
54721
|
if (!discoveredPeerUids.has(enrichedPeer.uid)) {
|
|
54145
54722
|
discoveredPeerUids.add(enrichedPeer.uid);
|
|
@@ -54198,37 +54775,14 @@ function createPeerList({ peerApi, presence, diag, onMissingPeers }) {
|
|
|
54198
54775
|
if (requestedKey !== blockedUidsKey)
|
|
54199
54776
|
continue;
|
|
54200
54777
|
blockedPeers = requestedUids.map((uid) => nextByUid.get(uid)).filter((peer) => peer?.uid);
|
|
54778
|
+
for (const peer of blockedPeers)
|
|
54779
|
+
discoveredPeerUids.add(peer.uid);
|
|
54201
54780
|
blockedPeersReady = true;
|
|
54202
|
-
rebuild({
|
|
54781
|
+
rebuild({ base: true });
|
|
54203
54782
|
return blockedPeers;
|
|
54204
54783
|
}
|
|
54205
54784
|
return blockedPeers;
|
|
54206
54785
|
}
|
|
54207
|
-
function dropPeer(peer) {
|
|
54208
|
-
const uid = peerUid(peer);
|
|
54209
|
-
if (!uid)
|
|
54210
|
-
return;
|
|
54211
|
-
const discovered = discoveredPeerUids.has(uid);
|
|
54212
|
-
if (!hiddenPeerUidSet.has(uid)) {
|
|
54213
|
-
hiddenPeerUidSet.add(uid);
|
|
54214
|
-
}
|
|
54215
|
-
if (discovered) {
|
|
54216
|
-
discoveredPeerUids.delete(uid);
|
|
54217
|
-
}
|
|
54218
|
-
if (typeof peer === "object" && peer?.uid) {
|
|
54219
|
-
blockedPeers = [...blockedPeers.filter((item) => item?.uid !== uid), peer];
|
|
54220
|
-
}
|
|
54221
|
-
rebuild(discovered ? { base: true } : { projection: true });
|
|
54222
|
-
}
|
|
54223
|
-
function restorePeer(peer) {
|
|
54224
|
-
const uid = peerUid(peer);
|
|
54225
|
-
if (!uid)
|
|
54226
|
-
return;
|
|
54227
|
-
hiddenPeerUidSet.delete(uid);
|
|
54228
|
-
blockedPeers = blockedPeers.filter((item) => item?.uid !== uid);
|
|
54229
|
-
discoveredPeerUids.add(uid);
|
|
54230
|
-
rebuild({ base: true });
|
|
54231
|
-
}
|
|
54232
54786
|
snapshot = makeSnapshot();
|
|
54233
54787
|
return {
|
|
54234
54788
|
getSnapshot() {
|
|
@@ -56868,7 +57422,7 @@ function compareRecentTransfer(a, b) {
|
|
|
56868
57422
|
return String(b?.id || "").localeCompare(String(a?.id || ""));
|
|
56869
57423
|
}
|
|
56870
57424
|
function sameTransfer(left, right) {
|
|
56871
|
-
return left?.id === right?.id && left?.status === right?.status && txCreatedMs(left) === txCreatedMs(right) && txUpdatedMs(left) === txUpdatedMs(right) && left?.totalValue === right?.totalValue && left?.type === right?.type && left?.transferDirection === right?.transferDirection && left?.senderIdentityPublicKey === right?.senderIdentityPublicKey && left?.receiverIdentityPublicKey === right?.receiverIdentityPublicKey && left?.bitcoinAddress === right?.bitcoinAddress && left?.bitcoinTxid === right?.bitcoinTxid && left?.lightningAddress === right?.lightningAddress;
|
|
57425
|
+
return left?.id === right?.id && left?.status === right?.status && txCreatedMs(left) === txCreatedMs(right) && txUpdatedMs(left) === txUpdatedMs(right) && left?.totalValue === right?.totalValue && left?.tokenIdentifier === right?.tokenIdentifier && left?.amountUnits === right?.amountUnits && left?.network === right?.network && left?.type === right?.type && left?.transferDirection === right?.transferDirection && left?.senderIdentityPublicKey === right?.senderIdentityPublicKey && left?.receiverIdentityPublicKey === right?.receiverIdentityPublicKey && left?.bitcoinAddress === right?.bitcoinAddress && left?.bitcoinTxid === right?.bitcoinTxid && left?.lightningAddress === right?.lightningAddress;
|
|
56872
57426
|
}
|
|
56873
57427
|
function sameTransfers(a = [], b = []) {
|
|
56874
57428
|
if (a === b) {
|
|
@@ -56926,7 +57480,7 @@ function compactTransfer(tx, { incoming = false, bitcoinAddress = null, bitcoinT
|
|
|
56926
57480
|
senderIdentityPublicKey: hexKey(tx.senderIdentityPublicKey),
|
|
56927
57481
|
receiverIdentityPublicKey: hexKey(tx.receiverIdentityPublicKey),
|
|
56928
57482
|
status: enumName(tx.status, TRANSFER_STATUS_BY_CODE),
|
|
56929
|
-
totalValue: satValue(tx.totalValue),
|
|
57483
|
+
...type === "TOKEN_TRANSFER" ? { tokenIdentifier: tx.tokenIdentifier, amountUnits: tx.amountUnits, network: tx.network } : { totalValue: satValue(tx.totalValue) },
|
|
56930
57484
|
createdTime: tx.createdTime,
|
|
56931
57485
|
updatedTime: tx.updatedTime,
|
|
56932
57486
|
type,
|
|
@@ -57160,32 +57714,6 @@ function mergeTransferPageForWallet(currentTransfers = [], pageTransfers = [], w
|
|
|
57160
57714
|
const page = compactTransfersForWallet(pageTransfers, walletPK);
|
|
57161
57715
|
return mergeCompactedTransferPage(current, page, position);
|
|
57162
57716
|
}
|
|
57163
|
-
function mergeRecentSnapshotForWallet(currentTransfers = [], latestTransfers = [], walletPK) {
|
|
57164
|
-
const currentById = new Map(currentTransfers.map((tx) => [String(tx?.id || ""), tx]));
|
|
57165
|
-
const latest = mergeTransferPageForWallet([], latestTransfers, walletPK, "append").map((tx) => mergeTransferDetails(currentById.get(String(tx?.id || "")), tx));
|
|
57166
|
-
if (!latest.length) {
|
|
57167
|
-
return filterTransfersForWallet(currentTransfers, walletPK);
|
|
57168
|
-
}
|
|
57169
|
-
const latestIds = new Set(latest.map((tx) => String(tx.id)));
|
|
57170
|
-
const latestById = new Map(latest.map((tx) => [String(tx.id), tx]));
|
|
57171
|
-
const oldestLatestMs = getOldestTransferMs(latest);
|
|
57172
|
-
const olderCurrent = filterTransfersForWallet(currentTransfers, walletPK).filter((tx) => {
|
|
57173
|
-
const id = String(tx?.id || "");
|
|
57174
|
-
if (latestIds.has(id)) {
|
|
57175
|
-
const latestTx = latestById.get(id);
|
|
57176
|
-
if (isPendingTransfer(latestTx) && !isPendingTransfer(tx)) {
|
|
57177
|
-
return true;
|
|
57178
|
-
}
|
|
57179
|
-
return false;
|
|
57180
|
-
}
|
|
57181
|
-
if (isPendingTransfer(tx)) {
|
|
57182
|
-
return false;
|
|
57183
|
-
}
|
|
57184
|
-
const createdMs = txCreatedMs(tx);
|
|
57185
|
-
return oldestLatestMs == null || createdMs > 0 && createdMs < oldestLatestMs;
|
|
57186
|
-
});
|
|
57187
|
-
return mergeTransferPage(latest, olderCurrent, "append");
|
|
57188
|
-
}
|
|
57189
57717
|
function markClaimedTransfersCompleted(transfers = [], ids = []) {
|
|
57190
57718
|
const claimedIds = new Set((Array.isArray(ids) ? ids : [ids]).map((id) => String(id || "")).filter(Boolean));
|
|
57191
57719
|
if (!claimedIds.size)
|
|
@@ -57404,7 +57932,7 @@ async function refreshRequestedTransfers(owner, ids = []) {
|
|
|
57404
57932
|
const resolvedIds = requestedTransfers.filter((transfer) => !isPendingTransfer(transfer)).map((transfer) => String(transfer.id));
|
|
57405
57933
|
forgetPending(owner, resolvedIds);
|
|
57406
57934
|
if (nextTransfers !== currentTransfers) {
|
|
57407
|
-
await owner.snapshot.
|
|
57935
|
+
await owner.snapshot.commitHistory(overlayPending(owner, updates.map((transfer) => compactTransfer(transfer))), { position: "prepend" });
|
|
57408
57936
|
if (!isCurrentGeneration(owner, generation))
|
|
57409
57937
|
return [];
|
|
57410
57938
|
owner.snapshot.replace(mergeTransferPageForWallet(owner.snapshot.getTransfers(), updates, owner.walletPK, "append"));
|
|
@@ -57522,8 +58050,7 @@ async function commitPendingReconciliation(owner, plan, result, generation) {
|
|
|
57522
58050
|
const nextTransfers = result.updates.length ? overlayPending(owner, mergeTransferPageForWallet(current, result.updates, owner.walletPK, "append")) : current;
|
|
57523
58051
|
forgetPending(owner, result.resolvedIds);
|
|
57524
58052
|
if (nextTransfers !== current) {
|
|
57525
|
-
|
|
57526
|
-
await owner.snapshot.commitConfirmed(nextTransfers.filter((transfer) => selectedIds.has(String(transfer?.id)) && !isPendingTransfer(transfer)), { position: "prepend" });
|
|
58053
|
+
await owner.snapshot.commitHistory(overlayPending(owner, result.updates.map((transfer) => compactTransfer(transfer))), { position: "prepend" });
|
|
57527
58054
|
if (!isCurrentGeneration(owner, generation))
|
|
57528
58055
|
return null;
|
|
57529
58056
|
owner.snapshot.replace(mergeTransferPageForWallet(owner.snapshot.getTransfers(), result.updates, owner.walletPK, "append"));
|
|
@@ -57725,13 +58252,17 @@ function hydrateRecentHistory(owner) {
|
|
|
57725
58252
|
return owner.hydratePromise;
|
|
57726
58253
|
const generation = owner.generation;
|
|
57727
58254
|
const startedAt = Date.now();
|
|
57728
|
-
const request = owner.historyStore.query({ limit: WORKING_CONFIRMED_TRANSFER_LIMIT }).then((result) => {
|
|
57729
|
-
|
|
58255
|
+
const request = owner.historyStore.query({ limit: WORKING_CONFIRMED_TRANSFER_LIMIT }).then(async (result) => {
|
|
58256
|
+
const loadedIds = new Set(result.transfers.map((tx) => String(tx.id)));
|
|
58257
|
+
const missingPending = Object.keys(owner.historyStore.getSnapshot().projection?.pending || {}).filter((id) => !loadedIds.has(id));
|
|
58258
|
+
const pending = await Promise.all(missingPending.map((id) => owner.historyStore.getById(id)));
|
|
58259
|
+
const transfers = pending.length ? mergeTransferPage(result.transfers, pending.filter(Boolean), "prepend") : result.transfers;
|
|
58260
|
+
if (!isCurrentGeneration2(owner, generation) || owner.snapshot.getTransfers().length || !transfers.length)
|
|
57730
58261
|
return false;
|
|
57731
58262
|
owner.nextOffset = owner.historyStore.getSnapshot().nextOffset;
|
|
57732
|
-
owner.snapshot.replace(
|
|
58263
|
+
owner.snapshot.replace(transfers);
|
|
57733
58264
|
owner.snapshot.setReady();
|
|
57734
|
-
markDone(owner.diag, "wallet.history.hydrate", startedAt, { count:
|
|
58265
|
+
markDone(owner.diag, "wallet.history.hydrate", startedAt, { count: transfers.length, pending: pendingTransfers(transfers).length });
|
|
57735
58266
|
return true;
|
|
57736
58267
|
}).catch((error) => {
|
|
57737
58268
|
if (isCurrentGeneration2(owner, generation))
|
|
@@ -57790,13 +58321,12 @@ async function fetchHistoryPages(owner, options, generation) {
|
|
|
57790
58321
|
completeOnStop: !!completeOnStop
|
|
57791
58322
|
});
|
|
57792
58323
|
try {
|
|
57793
|
-
let stagedTransfers = owner.snapshot.getTransfers();
|
|
57794
58324
|
let oldestFetchedMs = null;
|
|
57795
58325
|
let stoppedAtKnownTransfer = false;
|
|
57796
58326
|
while ((!owner.historyStore.getSnapshot().complete || force) && pages < maxPages) {
|
|
57797
58327
|
if (!isCurrentGeneration2(owner, generation) || owner.backgroundSignal?.aborted === true)
|
|
57798
58328
|
break;
|
|
57799
|
-
if (!allHistory && Number.isFinite(sinceMs) && stagedHistoryCoverage(owner,
|
|
58329
|
+
if (!allHistory && Number.isFinite(sinceMs) && stagedHistoryCoverage(owner, owner.snapshot.getTransfers(), oldestFetchedMs, sinceMs, force))
|
|
57800
58330
|
break;
|
|
57801
58331
|
const offset = owner.nextOffset;
|
|
57802
58332
|
const rawPage = await owner.readPage(TRANSFER_PAGE_LIMIT, offset, undefined, new Date(boundaryMs));
|
|
@@ -57807,12 +58337,11 @@ async function fetchHistoryPages(owner, options, generation) {
|
|
|
57807
58337
|
const pageHitStopId = stopIds.size > 0 && page.transfers.some((transfer) => stopIds.has(String(transfer?.id)));
|
|
57808
58338
|
if (page.transfers.length) {
|
|
57809
58339
|
owner.pending.forgetResolvedFetched(page.transfers);
|
|
57810
|
-
stagedTransfers = boundedWorkingTransfers(mergeTransferPageForWallet(stagedTransfers, page.transfers, owner.walletPK, "append"));
|
|
57811
58340
|
const oldestPageMs = getOldestTransferMs(page.transfers);
|
|
57812
58341
|
if (oldestPageMs != null)
|
|
57813
58342
|
oldestFetchedMs = oldestFetchedMs == null ? oldestPageMs : Math.min(oldestFetchedMs, oldestPageMs);
|
|
57814
58343
|
}
|
|
57815
|
-
await owner.
|
|
58344
|
+
await owner.commitHistory(page.transfers, {
|
|
57816
58345
|
position: "append",
|
|
57817
58346
|
nextOffset: page.nextOffset,
|
|
57818
58347
|
boundaryMs,
|
|
@@ -57820,6 +58349,7 @@ async function fetchHistoryPages(owner, options, generation) {
|
|
|
57820
58349
|
});
|
|
57821
58350
|
if (!isCurrentGeneration2(owner, generation))
|
|
57822
58351
|
break;
|
|
58352
|
+
owner.snapshot.replace(mergeTransferPageForWallet(owner.snapshot.getTransfers(), page.transfers, owner.walletPK, "append"));
|
|
57823
58353
|
owner.nextOffset = Math.max(owner.nextOffset, page.nextOffset);
|
|
57824
58354
|
await yieldToUi();
|
|
57825
58355
|
if (pageHitStopId) {
|
|
@@ -57830,7 +58360,6 @@ async function fetchHistoryPages(owner, options, generation) {
|
|
|
57830
58360
|
break;
|
|
57831
58361
|
}
|
|
57832
58362
|
if (isCurrentGeneration2(owner, generation)) {
|
|
57833
|
-
owner.snapshot.replace(stagedTransfers);
|
|
57834
58363
|
markDone(owner.diag, label, startedAt, {
|
|
57835
58364
|
pages,
|
|
57836
58365
|
loaded: owner.snapshot.getTransfers().length,
|
|
@@ -57927,13 +58456,12 @@ async function refreshCachedHistory(owner, includePending = true, reason = "cach
|
|
|
57927
58456
|
deltaAfterMs = Math.max(0, deltaState.newestMs - 1);
|
|
57928
58457
|
deltaBeforeMs = Date.now();
|
|
57929
58458
|
offset = 0;
|
|
57930
|
-
await owner.
|
|
58459
|
+
await owner.commitHistory([], { deltaAfterMs, deltaBeforeMs, deltaNextOffset: 0 });
|
|
57931
58460
|
if (!isCurrentGeneration2(owner, generation))
|
|
57932
58461
|
return null;
|
|
57933
58462
|
}
|
|
57934
58463
|
const createdAfter = new Date(deltaAfterMs);
|
|
57935
58464
|
const createdBefore = new Date(deltaBeforeMs);
|
|
57936
|
-
let stagedTransfers = owner.snapshot.getTransfers();
|
|
57937
58465
|
let rawCount = 0;
|
|
57938
58466
|
let pages = 0;
|
|
57939
58467
|
let complete = false;
|
|
@@ -57945,10 +58473,7 @@ async function refreshCachedHistory(owner, includePending = true, reason = "cach
|
|
|
57945
58473
|
rawCount += page.transfers.length;
|
|
57946
58474
|
pages += 1;
|
|
57947
58475
|
complete = !page.hasMore;
|
|
57948
|
-
|
|
57949
|
-
stagedTransfers = boundedWorkingTransfers(mergeTransferPageForWallet(stagedTransfers, page.transfers, walletKey, "prepend"));
|
|
57950
|
-
}
|
|
57951
|
-
await owner.commitConfirmed(page.transfers, {
|
|
58476
|
+
await owner.commitHistory(page.transfers, {
|
|
57952
58477
|
position: "prepend",
|
|
57953
58478
|
deltaAfterMs: complete ? null : deltaAfterMs,
|
|
57954
58479
|
deltaBeforeMs: complete ? null : deltaBeforeMs,
|
|
@@ -57956,6 +58481,8 @@ async function refreshCachedHistory(owner, includePending = true, reason = "cach
|
|
|
57956
58481
|
});
|
|
57957
58482
|
if (!isCurrentGeneration2(owner, generation))
|
|
57958
58483
|
return null;
|
|
58484
|
+
owner.pending.forgetResolvedFetched(page.transfers);
|
|
58485
|
+
owner.snapshot.replace(mergeTransferPageForWallet(owner.snapshot.getTransfers(), page.transfers, walletKey, "prepend"));
|
|
57959
58486
|
offset = page.nextOffset;
|
|
57960
58487
|
if (complete)
|
|
57961
58488
|
break;
|
|
@@ -57964,10 +58491,14 @@ async function refreshCachedHistory(owner, includePending = true, reason = "cach
|
|
|
57964
58491
|
if (!isCurrentGeneration2(owner, generation) || owner.backgroundSignal?.aborted === true)
|
|
57965
58492
|
return null;
|
|
57966
58493
|
const pending = includePending ? await owner.pending.query() : [];
|
|
58494
|
+
if (!isCurrentGeneration2(owner, generation))
|
|
58495
|
+
return null;
|
|
58496
|
+
if (pending.length)
|
|
58497
|
+
await owner.commitHistory(pending, { position: "prepend" });
|
|
57967
58498
|
if (!isCurrentGeneration2(owner, generation))
|
|
57968
58499
|
return null;
|
|
57969
58500
|
owner.pending.remember(pending.map((transfer) => transfer.id));
|
|
57970
|
-
const nextTransfers = pending.length ? mergeTransferPageForWallet(
|
|
58501
|
+
const nextTransfers = pending.length ? mergeTransferPageForWallet(owner.snapshot.getTransfers(), pending, walletKey, "prepend") : owner.snapshot.getTransfers();
|
|
57971
58502
|
owner.snapshot.replace(nextTransfers);
|
|
57972
58503
|
const visibleCount = Math.min(nextTransfers.length, Math.max(owner.snapshot.getTransfers().length, RECENT_TRANSFER_LIMIT));
|
|
57973
58504
|
owner.snapshot.publish(nextTransfers.slice(0, visibleCount));
|
|
@@ -58056,7 +58587,7 @@ async function loadRecentTransfers(owner, options = {}, generation = owner.gener
|
|
|
58056
58587
|
const loadedPendingIncoming = includePending ? owner.pending.claimCandidates(latestTransfers) : [];
|
|
58057
58588
|
const latestIds = new Set(latestTransfers.map((transfer) => String(transfer?.id)).filter(Boolean));
|
|
58058
58589
|
const reconcileIds = previousHistoryComplete ? [] : previousTransfers.filter((transfer) => transfer?.id && !latestIds.has(String(transfer.id)) && !isPendingTransfer(transfer)).map((transfer) => String(transfer.id));
|
|
58059
|
-
await owner.
|
|
58590
|
+
await owner.commitHistory([...pending, ...page.transfers], {
|
|
58060
58591
|
position: "prepend",
|
|
58061
58592
|
nextOffset: Math.max(owner.historyStore.getSnapshot().nextOffset, page.nextOffset),
|
|
58062
58593
|
boundaryMs,
|
|
@@ -58064,12 +58595,11 @@ async function loadRecentTransfers(owner, options = {}, generation = owner.gener
|
|
|
58064
58595
|
});
|
|
58065
58596
|
if (!isCurrentGeneration2(owner, generation))
|
|
58066
58597
|
return null;
|
|
58067
|
-
const fetchedTransfers = page.hasMore ? mergeRecentSnapshotForWallet(previousTransfers, latestTransfers, owner.walletPK) : mergeTransferPageForWallet([], latestTransfers, owner.walletPK);
|
|
58068
|
-
const committedTransfers = boundedWorkingTransfers(mergeTransferPageForWallet(owner.snapshot.getTransfers(), fetchedTransfers, owner.walletPK, "append"));
|
|
58069
58598
|
if (!showLoading)
|
|
58070
58599
|
await yieldToUi();
|
|
58071
58600
|
if (!isCurrentGeneration2(owner, generation))
|
|
58072
58601
|
return null;
|
|
58602
|
+
const committedTransfers = boundedWorkingTransfers(mergeTransferPageForWallet(owner.snapshot.getTransfers(), [...pending, ...page.transfers], owner.walletPK, "append"));
|
|
58073
58603
|
owner.nextOffset = Math.max(owner.nextOffset, page.nextOffset);
|
|
58074
58604
|
const committedHistory = owner.historyStore.getSnapshot();
|
|
58075
58605
|
const visibleCount = Math.min(committedTransfers.length, Math.max(owner.snapshot.getTransfers().length, RECENT_TRANSFER_LIMIT));
|
|
@@ -58119,8 +58649,8 @@ function getRecentTransfers(owner, options = {}) {
|
|
|
58119
58649
|
owner.recentPromise = request;
|
|
58120
58650
|
return request;
|
|
58121
58651
|
}
|
|
58122
|
-
function createTransferHistorySync({ walletPK, historyStore, initialOffset = 0, snapshot, readPage,
|
|
58123
|
-
if (!walletPK || !historyStore || !snapshot || typeof readPage !== "function" || typeof
|
|
58652
|
+
function createTransferHistorySync({ walletPK, historyStore, initialOffset = 0, snapshot, readPage, commitHistory, pending, waitForWork, backgroundSignal = null, isClosed, diag = null } = {}) {
|
|
58653
|
+
if (!walletPK || !historyStore || !snapshot || typeof readPage !== "function" || typeof commitHistory !== "function" || !pending || typeof waitForWork !== "function" || typeof isClosed !== "function") {
|
|
58124
58654
|
throw new Error("transfer history sync requires history, snapshot, pending, and read ports");
|
|
58125
58655
|
}
|
|
58126
58656
|
const owner = {
|
|
@@ -58128,7 +58658,7 @@ function createTransferHistorySync({ walletPK, historyStore, initialOffset = 0,
|
|
|
58128
58658
|
historyStore,
|
|
58129
58659
|
snapshot,
|
|
58130
58660
|
readPage,
|
|
58131
|
-
|
|
58661
|
+
commitHistory,
|
|
58132
58662
|
pending,
|
|
58133
58663
|
waitForWork,
|
|
58134
58664
|
backgroundSignal,
|
|
@@ -58185,6 +58715,7 @@ var ID_INDEX_PREFIX = "whi";
|
|
|
58185
58715
|
var PEER_INDEX_PREFIX = "whp";
|
|
58186
58716
|
var REPOSITORY_VERSION = 2;
|
|
58187
58717
|
var SEGMENT_SIZE = 100;
|
|
58718
|
+
var WALLET_HISTORY_HEAD_SIZE = SEGMENT_SIZE;
|
|
58188
58719
|
var SEGMENT_CACHE_LIMIT = 5;
|
|
58189
58720
|
var ID_BUCKET_COUNT = 4096;
|
|
58190
58721
|
var PEER_BUCKET_COUNT = 256;
|
|
@@ -58222,13 +58753,20 @@ function compareTransfers(left, right) {
|
|
|
58222
58753
|
const delta = txCreatedMs(right) - txCreatedMs(left);
|
|
58223
58754
|
return delta || transferId(right).localeCompare(transferId(left));
|
|
58224
58755
|
}
|
|
58756
|
+
function mergeHistoryTransfer(current, next) {
|
|
58757
|
+
if (!current)
|
|
58758
|
+
return next;
|
|
58759
|
+
if (!isPendingTransfer(current) && isPendingTransfer(next) || txUpdatedMs(next) < txUpdatedMs(current))
|
|
58760
|
+
return current;
|
|
58761
|
+
return { ...current, ...next };
|
|
58762
|
+
}
|
|
58225
58763
|
function normalizeTransfers(transfers, walletPK) {
|
|
58226
58764
|
const byId = new Map;
|
|
58227
58765
|
for (const transfer of Array.isArray(transfers) ? transfers : EMPTY_TRANSFERS) {
|
|
58228
58766
|
const id = transferId(transfer);
|
|
58229
58767
|
if (!id || !isVisibleTransfer(transfer) || !transferBelongsToWallet(transfer, walletPK))
|
|
58230
58768
|
continue;
|
|
58231
|
-
byId.set(id, jsonClone(transfer));
|
|
58769
|
+
byId.set(id, mergeHistoryTransfer(byId.get(id), jsonClone(transfer)));
|
|
58232
58770
|
}
|
|
58233
58771
|
return [...byId.values()].sort(compareTransfers);
|
|
58234
58772
|
}
|
|
@@ -58253,7 +58791,8 @@ function emptyProjection() {
|
|
|
58253
58791
|
days: {},
|
|
58254
58792
|
hours: {},
|
|
58255
58793
|
months: {},
|
|
58256
|
-
peers: {}
|
|
58794
|
+
peers: {},
|
|
58795
|
+
pending: {}
|
|
58257
58796
|
};
|
|
58258
58797
|
}
|
|
58259
58798
|
function cleanStats(value) {
|
|
@@ -58268,6 +58807,7 @@ function cleanStats(value) {
|
|
|
58268
58807
|
}
|
|
58269
58808
|
function cleanProjection(value) {
|
|
58270
58809
|
const projection = emptyProjection();
|
|
58810
|
+
projection.pending = { ...value?.pending };
|
|
58271
58811
|
projection.all = cleanStats(value?.all);
|
|
58272
58812
|
for (const [key, stats] of Object.entries(value?.days || {}))
|
|
58273
58813
|
projection.days[key] = cleanStats(stats);
|
|
@@ -58304,6 +58844,8 @@ function applyStats(stats, transfer, sign2, { excludeFunding = false } = {}) {
|
|
|
58304
58844
|
const amount = Number(transfer?.totalValue) || 0;
|
|
58305
58845
|
const incoming = isIncomingTransfer(transfer);
|
|
58306
58846
|
stats.count += sign2;
|
|
58847
|
+
if (incoming && isPendingTransfer(transfer))
|
|
58848
|
+
return;
|
|
58307
58849
|
stats.vol += sign2 * amount;
|
|
58308
58850
|
if (incoming) {
|
|
58309
58851
|
stats.received += sign2 * amount;
|
|
@@ -58322,6 +58864,11 @@ function normalizeStats(stats) {
|
|
|
58322
58864
|
return stats;
|
|
58323
58865
|
}
|
|
58324
58866
|
function applyProjection(projection, transfer, sign2) {
|
|
58867
|
+
const id = transferId(transfer);
|
|
58868
|
+
if (sign2 > 0 && isPendingTransfer(transfer))
|
|
58869
|
+
projection.pending[id] = txCreatedMs(transfer);
|
|
58870
|
+
else
|
|
58871
|
+
delete projection.pending[id];
|
|
58325
58872
|
const { day, hour, month } = timeKeys(transfer);
|
|
58326
58873
|
projection.days[day] ||= emptyStats();
|
|
58327
58874
|
projection.hours[hour] ||= emptyStats();
|
|
@@ -58407,11 +58954,12 @@ function normalizeManifest(value, walletPK) {
|
|
|
58407
58954
|
projection: cleanProjection(value.projection)
|
|
58408
58955
|
};
|
|
58409
58956
|
}
|
|
58410
|
-
function repositorySnapshot(manifest, ready, syncing = false) {
|
|
58957
|
+
function repositorySnapshot(manifest, ready, syncing = false, head = EMPTY_TRANSFERS) {
|
|
58411
58958
|
return Object.freeze({
|
|
58412
58959
|
walletPK: manifest.walletPK,
|
|
58413
58960
|
ready,
|
|
58414
58961
|
syncing,
|
|
58962
|
+
head,
|
|
58415
58963
|
count: manifest.count,
|
|
58416
58964
|
complete: manifest.complete,
|
|
58417
58965
|
bitcoinOldestMs: manifest.bitcoinOldestMs,
|
|
@@ -58459,8 +59007,14 @@ function buildSegmentCommitPlan({ manifest, incoming, removedIds, mappings, oldS
|
|
|
58459
59007
|
const incomingById = new Map(incoming.map((transfer) => [transferId(transfer), transfer]));
|
|
58460
59008
|
const working = new Map;
|
|
58461
59009
|
for (const [segmentId, segmentTransfers] of oldSegments) {
|
|
58462
|
-
for (const transfer of segmentTransfers)
|
|
58463
|
-
|
|
59010
|
+
for (const transfer of segmentTransfers) {
|
|
59011
|
+
const id = transferId(transfer);
|
|
59012
|
+
previousById.set(id, transfer);
|
|
59013
|
+
const next2 = incomingById.get(id);
|
|
59014
|
+
if (!next2)
|
|
59015
|
+
continue;
|
|
59016
|
+
incomingById.set(id, mergeHistoryTransfer(transfer, next2));
|
|
59017
|
+
}
|
|
58464
59018
|
const next = segmentTransfers.filter((transfer) => !removedIds.has(transferId(transfer))).map((transfer) => incomingById.get(transferId(transfer)) || transfer);
|
|
58465
59019
|
working.set(segmentId, normalizeTransfers(next, walletPK));
|
|
58466
59020
|
}
|
|
@@ -58676,6 +59230,15 @@ async function openWalletHistoryRepository({ localCache, walletPK } = {}) {
|
|
|
58676
59230
|
}
|
|
58677
59231
|
return rememberSegment(id, transfers);
|
|
58678
59232
|
};
|
|
59233
|
+
const readHead = async () => {
|
|
59234
|
+
const transfers = [];
|
|
59235
|
+
for (const segment of manifest.segments) {
|
|
59236
|
+
transfers.push(...await readSegment(segment.id));
|
|
59237
|
+
if (transfers.length >= WALLET_HISTORY_HEAD_SIZE)
|
|
59238
|
+
break;
|
|
59239
|
+
}
|
|
59240
|
+
return transfers.length ? Object.freeze(transfers.slice(0, WALLET_HISTORY_HEAD_SIZE)) : EMPTY_TRANSFERS;
|
|
59241
|
+
};
|
|
58679
59242
|
const readIdMappings = async (ids, generation = manifest.generation) => {
|
|
58680
59243
|
const grouped = new Map;
|
|
58681
59244
|
for (const id of ids) {
|
|
@@ -58720,7 +59283,7 @@ async function openWalletHistoryRepository({ localCache, walletPK } = {}) {
|
|
|
58720
59283
|
if (!wrote)
|
|
58721
59284
|
throw new Error("wallet history checkpoint write failed");
|
|
58722
59285
|
manifest = normalizeManifest(nextManifest, requestedWalletPK);
|
|
58723
|
-
snapshot = repositorySnapshot(manifest, true);
|
|
59286
|
+
snapshot = repositorySnapshot(manifest, true, false, await readHead());
|
|
58724
59287
|
emit(listeners);
|
|
58725
59288
|
};
|
|
58726
59289
|
const pruneSegments = async () => {
|
|
@@ -58738,6 +59301,13 @@ async function openWalletHistoryRepository({ localCache, walletPK } = {}) {
|
|
|
58738
59301
|
};
|
|
58739
59302
|
const prunePromise = pruneSegments();
|
|
58740
59303
|
await prunePromise;
|
|
59304
|
+
const initialHead = await readHead().catch(async (error) => {
|
|
59305
|
+
if (!recoveryPromise)
|
|
59306
|
+
throw error;
|
|
59307
|
+
await recoveryPromise;
|
|
59308
|
+
return EMPTY_TRANSFERS;
|
|
59309
|
+
});
|
|
59310
|
+
snapshot = repositorySnapshot(manifest, true, false, initialHead);
|
|
58741
59311
|
const readCommitSource = async (incoming, removedIds, position) => {
|
|
58742
59312
|
const ids = [...new Set([...incoming.map(transferId), ...removedIds])];
|
|
58743
59313
|
const { mappings, known, buckets: idBuckets } = await readIdMappings(ids);
|
|
@@ -58809,7 +59379,9 @@ async function openWalletHistoryRepository({ localCache, walletPK } = {}) {
|
|
|
58809
59379
|
if (!wrote)
|
|
58810
59380
|
throw new Error("wallet history checkpoint write failed");
|
|
58811
59381
|
manifest = normalizeManifest(nextManifest, requestedWalletPK);
|
|
58812
|
-
|
|
59382
|
+
for (const record of plan.newSegmentRecords)
|
|
59383
|
+
rememberSegment(record.descriptor.id, record.transfers);
|
|
59384
|
+
snapshot = repositorySnapshot(manifest, true, false, await readHead());
|
|
58813
59385
|
emit(listeners);
|
|
58814
59386
|
} else {
|
|
58815
59387
|
for (const record of plan.newSegmentRecords) {
|
|
@@ -58836,6 +59408,10 @@ async function openWalletHistoryRepository({ localCache, walletPK } = {}) {
|
|
|
58836
59408
|
await prunePromise;
|
|
58837
59409
|
const incoming = normalizeTransfers(transfers, requestedWalletPK);
|
|
58838
59410
|
const removedIds = new Set((Array.isArray(options.removedIds) ? options.removedIds : []).map(String).filter(Boolean));
|
|
59411
|
+
for (const transfer of transfers) {
|
|
59412
|
+
if (transfer?.id && transferBelongsToWallet(transfer, requestedWalletPK) && !isVisibleTransfer(transfer))
|
|
59413
|
+
removedIds.add(String(transfer.id));
|
|
59414
|
+
}
|
|
58839
59415
|
const bitcoinTimes = incoming.filter((tx) => !tx.tokenIdentifier).map(txCreatedMs);
|
|
58840
59416
|
if (bitcoinTimes.length)
|
|
58841
59417
|
options = { ...options, bitcoinOldestMs: Math.min(manifest.bitcoinOldestMs ?? Infinity, ...bitcoinTimes) };
|
|
@@ -58869,13 +59445,13 @@ async function openWalletHistoryRepository({ localCache, walletPK } = {}) {
|
|
|
58869
59445
|
commitPage(transfers, options = {}) {
|
|
58870
59446
|
if (corruptionDetected)
|
|
58871
59447
|
return Promise.reject(new Error("wallet history cache recovery in progress"));
|
|
58872
|
-
snapshot = repositorySnapshot(manifest, true, true);
|
|
59448
|
+
snapshot = repositorySnapshot(manifest, true, true, snapshot.head);
|
|
58873
59449
|
emit(listeners);
|
|
58874
59450
|
const run = () => commit(transfers, options);
|
|
58875
59451
|
const result = writeTail.then(run, run);
|
|
58876
59452
|
writeTail = result.catch(() => {}).finally(() => {
|
|
58877
59453
|
if (!closed) {
|
|
58878
|
-
snapshot = repositorySnapshot(manifest, true, false);
|
|
59454
|
+
snapshot = repositorySnapshot(manifest, true, false, snapshot.head);
|
|
58879
59455
|
emit(listeners);
|
|
58880
59456
|
}
|
|
58881
59457
|
});
|
|
@@ -59174,9 +59750,9 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59174
59750
|
const snapshot = transferStore.getSnapshot();
|
|
59175
59751
|
return lowerText(snapshot.walletPK) === walletKey && snapshot.ready === true;
|
|
59176
59752
|
};
|
|
59177
|
-
const
|
|
59178
|
-
const
|
|
59179
|
-
return historyStore.commitPage(
|
|
59753
|
+
const commitHistoryPage = (pageTransfers, options = {}) => {
|
|
59754
|
+
const updates = pageTransfers.map((transfer) => compactTransfer(transfer)).filter((transfer) => transfer && transferBelongsToWallet(transfer, walletKey));
|
|
59755
|
+
return historyStore.commitPage(updates, options);
|
|
59180
59756
|
};
|
|
59181
59757
|
const forgetPendingLookupIds = (ids = []) => pendingReconciliation.forget(ids);
|
|
59182
59758
|
const rememberPendingTransferIds = (ids = []) => pendingReconciliation.remember(ids);
|
|
@@ -59212,9 +59788,6 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59212
59788
|
if (closed)
|
|
59213
59789
|
return current;
|
|
59214
59790
|
let ownedTransfers = owned ? nextTransfers : filterTransfersForWallet(nextTransfers, walletKey);
|
|
59215
|
-
if (current.length && ownedTransfers.length && ownedTransfers.length < current.length) {
|
|
59216
|
-
ownedTransfers = mergeTransferPageForWallet(ownedTransfers, current, walletKey, "append");
|
|
59217
|
-
}
|
|
59218
59791
|
ownedTransfers = boundedWorkingTransfers(applyTransferStatusOverlays(ownedTransfers));
|
|
59219
59792
|
const committed = sameTransfers(current, ownedTransfers) ? current : ownedTransfers;
|
|
59220
59793
|
transferStore.setHistory({ transfers: committed, walletPK: walletKey });
|
|
@@ -59351,7 +59924,7 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59351
59924
|
return [];
|
|
59352
59925
|
const nextTransfers = markClaimedTransfersCompleted(current, knownIds);
|
|
59353
59926
|
if (nextTransfers !== current) {
|
|
59354
|
-
|
|
59927
|
+
commitHistoryPage(nextTransfers.filter((transfer) => knownIds.includes(String(transfer?.id))), { position: "prepend" });
|
|
59355
59928
|
commitTransferSnapshot(nextTransfers, { publish: true, owned: true });
|
|
59356
59929
|
setTxReadyValue(true);
|
|
59357
59930
|
}
|
|
@@ -59376,7 +59949,7 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59376
59949
|
snapshot: {
|
|
59377
59950
|
getTransfers: currentTransfers,
|
|
59378
59951
|
replace: (transfers) => commitTransferSnapshot(transfers, { publish: true, owned: true }),
|
|
59379
|
-
|
|
59952
|
+
commitHistory: commitHistoryPage,
|
|
59380
59953
|
setReady: () => setTxReadyValue(true)
|
|
59381
59954
|
},
|
|
59382
59955
|
reads: {
|
|
@@ -59400,7 +59973,7 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59400
59973
|
setLoading: setIsTxLoading
|
|
59401
59974
|
},
|
|
59402
59975
|
readPage: fetchTransfersPage,
|
|
59403
|
-
|
|
59976
|
+
commitHistory: commitHistoryPage,
|
|
59404
59977
|
pending: {
|
|
59405
59978
|
query: getPendingTransfersSnapshot,
|
|
59406
59979
|
isQuerying: () => !!pendingQueryPromiseRef.current,
|
|
@@ -59457,7 +60030,10 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59457
60030
|
const current = currentTransfers();
|
|
59458
60031
|
const nextTransfers = mergeTransferPageForWallet(current, incomingTransfers, walletKey, "prepend");
|
|
59459
60032
|
if (nextTransfers !== current) {
|
|
59460
|
-
|
|
60033
|
+
await commitHistoryPage(incomingTransfers, { position: "prepend" });
|
|
60034
|
+
if (closed)
|
|
60035
|
+
return [];
|
|
60036
|
+
commitTransferSnapshot(mergeTransferPageForWallet(currentTransfers(), incomingTransfers, walletKey, "prepend"), { publish: true, owned: true });
|
|
59461
60037
|
setTxReadyValue(true);
|
|
59462
60038
|
}
|
|
59463
60039
|
}
|
|
@@ -59499,10 +60075,8 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59499
60075
|
forgetPendingLookupIds(resolvedUpdateIds);
|
|
59500
60076
|
}
|
|
59501
60077
|
}
|
|
59502
|
-
|
|
59503
|
-
|
|
59504
|
-
await commitConfirmedHistoryPage(updates, { position: "prepend" });
|
|
59505
|
-
commitTransferSnapshot(nextTransfers, { publish: true, owned: true });
|
|
60078
|
+
await commitHistoryPage(updates, { position: "prepend" });
|
|
60079
|
+
commitTransferSnapshot(mergeTransferPageForWallet(currentTransfers(), updates, walletKey, "prepend"), { publish: true, owned: true });
|
|
59506
60080
|
setTxReadyValue(true);
|
|
59507
60081
|
markDone(diag, "wallet.claimedTxs", startedAt2, { found: true, count: updates.length });
|
|
59508
60082
|
return updates;
|
|
@@ -59527,10 +60101,8 @@ function createWalletTransferSession({ wallet, walletPK, claimIncomingTransfers,
|
|
|
59527
60101
|
forgetPendingLookupIds([compact.id]);
|
|
59528
60102
|
}
|
|
59529
60103
|
}
|
|
59530
|
-
|
|
59531
|
-
|
|
59532
|
-
await commitConfirmedHistoryPage([compact], { position: "prepend" });
|
|
59533
|
-
commitTransferSnapshot(nextTransfers, { publish: true, owned: true });
|
|
60104
|
+
await commitHistoryPage([compact], { position: "prepend" });
|
|
60105
|
+
commitTransferSnapshot(mergeTransferPageForWallet(currentTransfers(), [compact], walletKey, "prepend"), { publish: true, owned: true });
|
|
59534
60106
|
setTxReadyValue(true);
|
|
59535
60107
|
markDone(diag, "wallet.claimedTx", startedAt, { found: true });
|
|
59536
60108
|
return compact;
|
|
@@ -60337,27 +60909,21 @@ function tokenHistoryBalance(tokens, unitsByToken = {}) {
|
|
|
60337
60909
|
}
|
|
60338
60910
|
|
|
60339
60911
|
// ../../core/wallet/txlist.js
|
|
60340
|
-
async function readTxListPage(
|
|
60912
|
+
async function readTxListPage(queryHistory, { offset = 0, limit = 100 } = {}) {
|
|
60341
60913
|
const start2 = Math.max(0, Math.floor(offset));
|
|
60342
60914
|
const count = Math.max(1, Math.min(500, Math.floor(limit)));
|
|
60343
|
-
const
|
|
60344
|
-
|
|
60345
|
-
|
|
60346
|
-
|
|
60347
|
-
}
|
|
60348
|
-
async function findTxListPeriod(pending, history, queryHistory, { sinceMs, untilMs, hourly }) {
|
|
60349
|
-
const pendingIndex = pending.findIndex((tx) => tx.createdMs >= sinceMs && tx.createdMs < untilMs);
|
|
60350
|
-
if (pendingIndex >= 0)
|
|
60351
|
-
return pendingIndex;
|
|
60915
|
+
const history = await queryHistory({ offset: start2, limit: count });
|
|
60916
|
+
return { offset: start2, transfers: history.transfers };
|
|
60917
|
+
}
|
|
60918
|
+
async function findTxListPeriod(history, queryHistory, { sinceMs, hourly }) {
|
|
60352
60919
|
const key = (hourly ? dayHourKey : dayKey)(new Date(sinceMs));
|
|
60353
60920
|
const buckets = history.projection[hourly ? "hours" : "days"];
|
|
60354
60921
|
const newer = Object.entries(buckets).reduce((count, [period, stats]) => count + (period > key ? stats.count : 0), 0);
|
|
60355
60922
|
if (buckets[key]?.count)
|
|
60356
|
-
return
|
|
60923
|
+
return newer;
|
|
60357
60924
|
const offset = Math.max(0, newer - 1);
|
|
60358
60925
|
const { transfers } = await queryHistory({ offset, limit: 2 });
|
|
60359
|
-
const candidates =
|
|
60360
|
-
transfers.forEach((tx, index) => candidates.push({ tx, index: pending.length + offset + index }));
|
|
60926
|
+
const candidates = transfers.map((tx, index) => ({ tx, index: offset + index }));
|
|
60361
60927
|
candidates.sort((a, b) => Math.abs(a.tx.createdMs - sinceMs) - Math.abs(b.tx.createdMs - sinceMs));
|
|
60362
60928
|
return candidates[0]?.index ?? -1;
|
|
60363
60929
|
}
|
|
@@ -60596,14 +61162,14 @@ function mergeTxSearchResults(loaded, found, limit = TX_SEARCH_DEFAULT_LIMIT) {
|
|
|
60596
61162
|
}
|
|
60597
61163
|
return [...byId.values()].sort(byRecentTx).slice(0, max2);
|
|
60598
61164
|
}
|
|
60599
|
-
function getDailySeries({ days, transactions, aggregatedData, balance, historyComplete, oldestMs, cache }) {
|
|
61165
|
+
function getDailySeries({ days, transactions, aggregatedData, balance, historyComplete, oldestMs, cache, nowMs: nowMs2 = Date.now() }) {
|
|
60600
61166
|
if (!transactions.length || balance == null)
|
|
60601
61167
|
return [];
|
|
60602
61168
|
const cacheKey = `daily-${days}`;
|
|
60603
|
-
|
|
60604
|
-
|
|
60605
|
-
|
|
60606
|
-
const today = new Date;
|
|
61169
|
+
const clock = Math.floor(nowMs2 / 60000);
|
|
61170
|
+
if (cache.get(cacheKey)?.clock === clock)
|
|
61171
|
+
return cache.get(cacheKey).series;
|
|
61172
|
+
const today = new Date(nowMs2);
|
|
60607
61173
|
today.setHours(0, 0, 0, 0);
|
|
60608
61174
|
const series = [];
|
|
60609
61175
|
let runningBalance = Number(balance);
|
|
@@ -60614,84 +61180,44 @@ function getDailySeries({ days, transactions, aggregatedData, balance, historyCo
|
|
|
60614
61180
|
const d = new Date(today);
|
|
60615
61181
|
d.setDate(d.getDate() - i2);
|
|
60616
61182
|
const key = dayKey(d);
|
|
60617
|
-
|
|
61183
|
+
const end = new Date(d);
|
|
61184
|
+
end.setDate(end.getDate() + 1);
|
|
61185
|
+
series.push({ date: key, timestamp: i2 === 0 ? nowMs2 : end.getTime(), now: i2 === 0, balance: runningBalance });
|
|
60618
61186
|
const dayData = aggregatedData.dayMap.get(key);
|
|
60619
61187
|
if (dayData) {
|
|
60620
61188
|
runningBalance -= dayData.net;
|
|
60621
61189
|
}
|
|
60622
61190
|
}
|
|
60623
61191
|
const result = series.reverse();
|
|
60624
|
-
cache.set(cacheKey, result);
|
|
61192
|
+
cache.set(cacheKey, { clock, series: result });
|
|
60625
61193
|
return result;
|
|
60626
61194
|
}
|
|
60627
|
-
function getHourlySeriesForData({ hours, prefix = "today", transactions, aggregatedData, balance, historyComplete, oldestMs, cache }) {
|
|
61195
|
+
function getHourlySeriesForData({ hours, prefix = "today", transactions, aggregatedData, balance, historyComplete, oldestMs, cache, nowMs: nowMs2 = Date.now() }) {
|
|
60628
61196
|
if (!transactions.length || balance == null)
|
|
60629
61197
|
return [];
|
|
60630
61198
|
const cacheKey = `hourly-${hours}-${prefix}`;
|
|
60631
|
-
|
|
60632
|
-
|
|
60633
|
-
|
|
60634
|
-
const now = new Date;
|
|
60635
|
-
const series = [];
|
|
61199
|
+
const clock = Math.floor(nowMs2 / 60000);
|
|
61200
|
+
if (cache.get(cacheKey)?.clock === clock)
|
|
61201
|
+
return cache.get(cacheKey).series;
|
|
61202
|
+
const now = new Date(nowMs2);
|
|
61203
|
+
const series = [{ hour: "now", timestamp: nowMs2, now: true, balance: Number(balance) }];
|
|
60636
61204
|
let runningBalance = Number(balance);
|
|
60637
|
-
const today =
|
|
60638
|
-
|
|
61205
|
+
const today = new Date(now);
|
|
61206
|
+
today.setHours(0, 0, 0, 0);
|
|
60639
61207
|
const coveredHours = historyComplete ? hours : coveredUnitsSince(oldestMs, HOUR_MS, now.getTime());
|
|
60640
61208
|
const actualHours = Math.min(hours, coveredHours);
|
|
60641
|
-
|
|
60642
|
-
|
|
60643
|
-
|
|
60644
|
-
|
|
60645
|
-
|
|
60646
|
-
|
|
60647
|
-
|
|
60648
|
-
|
|
60649
|
-
|
|
60650
|
-
|
|
60651
|
-
|
|
60652
|
-
|
|
60653
|
-
series.push({ hour, balance: runningBalance });
|
|
60654
|
-
const key = `${today}-${hour}`;
|
|
60655
|
-
const hourData = aggregatedData.hourMap.get(key);
|
|
60656
|
-
if (hourData) {
|
|
60657
|
-
runningBalance += hourData.net;
|
|
60658
|
-
}
|
|
60659
|
-
}
|
|
60660
|
-
series.push({ hour: "now", balance: Number(balance) });
|
|
60661
|
-
} else if (prefix === "24h") {
|
|
60662
|
-
const currentHour = now.getHours();
|
|
60663
|
-
const firstOffset = Math.max(0, 24 - actualHours);
|
|
60664
|
-
for (let i2 = 0;i2 <= currentHour; i2++) {
|
|
60665
|
-
const key = `${today}-${hourKey(i2)}`;
|
|
60666
|
-
const hourData = aggregatedData.hourMap.get(key);
|
|
60667
|
-
if (hourData)
|
|
60668
|
-
runningBalance -= hourData.net;
|
|
60669
|
-
}
|
|
60670
|
-
for (let i2 = Math.max(currentHour + 1, firstOffset);i2 < 24; i2++) {
|
|
60671
|
-
const key = `${yesterday}-${hourKey(i2)}`;
|
|
60672
|
-
const hourData = aggregatedData.hourMap.get(key);
|
|
60673
|
-
if (hourData)
|
|
60674
|
-
runningBalance -= hourData.net;
|
|
60675
|
-
}
|
|
60676
|
-
for (let i2 = Math.max(currentHour + 1, firstOffset);i2 < 24; i2++) {
|
|
60677
|
-
const hour = hourKey(i2);
|
|
60678
|
-
series.push({ hour, balance: runningBalance });
|
|
60679
|
-
const key = `${yesterday}-${hour}`;
|
|
60680
|
-
const hourData = aggregatedData.hourMap.get(key);
|
|
60681
|
-
if (hourData)
|
|
60682
|
-
runningBalance += hourData.net;
|
|
60683
|
-
}
|
|
60684
|
-
for (let i2 = firstOffset > currentHour ? firstOffset : 0;i2 <= currentHour; i2++) {
|
|
60685
|
-
const hour = hourKey(i2);
|
|
60686
|
-
series.push({ hour, balance: runningBalance });
|
|
60687
|
-
const key = `${today}-${hour}`;
|
|
60688
|
-
const hourData = aggregatedData.hourMap.get(key);
|
|
60689
|
-
if (hourData)
|
|
60690
|
-
runningBalance += hourData.net;
|
|
60691
|
-
}
|
|
60692
|
-
}
|
|
60693
|
-
cache.set(cacheKey, series);
|
|
60694
|
-
return series;
|
|
61209
|
+
const endHour = new Date(now);
|
|
61210
|
+
endHour.setMinutes(0, 0, 0);
|
|
61211
|
+
for (let offset = 0;offset < actualHours; offset++) {
|
|
61212
|
+
const date = new Date(endHour.getTime() - offset * HOUR_MS);
|
|
61213
|
+
if (prefix === "today" && date < today)
|
|
61214
|
+
break;
|
|
61215
|
+
runningBalance -= aggregatedData.hourMap.get(dayHourKey(date))?.net || 0;
|
|
61216
|
+
series.push({ hour: hourKey(date), timestamp: date.getTime(), balance: runningBalance });
|
|
61217
|
+
}
|
|
61218
|
+
const result = series.reverse();
|
|
61219
|
+
cache.set(cacheKey, { clock, series: result });
|
|
61220
|
+
return result;
|
|
60695
61221
|
}
|
|
60696
61222
|
function getTxsInRangeFrom(sortedTransactions, timeRange) {
|
|
60697
61223
|
if (!sortedTransactions.length)
|
|
@@ -60899,11 +61425,8 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
60899
61425
|
transfers: result.transfers.map((transfer) => getVisibleWalletTransfer(transfer, sources.walletPK)).filter(Boolean)
|
|
60900
61426
|
};
|
|
60901
61427
|
}
|
|
60902
|
-
function pendingList() {
|
|
60903
|
-
return readCurrentAggregatedData(sources.walletPK).sortedTxs.filter((tx) => tx.pending);
|
|
60904
|
-
}
|
|
60905
61428
|
function getTxListPage(options) {
|
|
60906
|
-
return readTxListPage(
|
|
61429
|
+
return readTxListPage(queryHistory, options);
|
|
60907
61430
|
}
|
|
60908
61431
|
async function findTxListPeriod2(period) {
|
|
60909
61432
|
const { sinceMs, untilMs } = period;
|
|
@@ -60921,7 +61444,7 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
60921
61444
|
return -1;
|
|
60922
61445
|
if (!covered())
|
|
60923
61446
|
throw new Error("transaction history is unavailable for this date");
|
|
60924
|
-
return findTxListPeriod(
|
|
61447
|
+
return findTxListPeriod(historyStore.getSnapshot(), queryHistory, period);
|
|
60925
61448
|
}
|
|
60926
61449
|
async function getHistoryById(txId, tokenIdentifier) {
|
|
60927
61450
|
const { walletPK, lookupTxById } = sources;
|
|
@@ -60941,31 +61464,17 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
60941
61464
|
const peerKey = lowerText(peerPK);
|
|
60942
61465
|
const limit = Math.max(1, Math.min(500, Math.floor(Number(options.limit) || 100)));
|
|
60943
61466
|
const offset = Math.max(0, Math.floor(Number(options.offset) || 0));
|
|
60944
|
-
const
|
|
60945
|
-
const pending = pendingData.txs;
|
|
60946
|
-
const pendingPage = pending.slice(offset, offset + limit);
|
|
60947
|
-
const historyOffset = Math.max(0, offset - pending.length);
|
|
60948
|
-
const historyLimit = Math.max(0, limit - pendingPage.length);
|
|
60949
|
-
const result = historyLimit ? await queryHistory({ ...options, peerPK: peerKey, offset: historyOffset, limit: historyLimit }) : { transfers: [], cursor: null };
|
|
61467
|
+
const result = await queryHistory({ ...options, peerPK: peerKey, offset, limit });
|
|
60950
61468
|
const storedStats = historyStore.getSnapshot().projection?.peers?.[peerKey];
|
|
60951
|
-
const
|
|
61469
|
+
const stats = {
|
|
60952
61470
|
sent: Number(storedStats?.sent) || 0,
|
|
60953
61471
|
received: Number(storedStats?.received) || 0,
|
|
60954
61472
|
net: Number(storedStats?.net) || 0,
|
|
60955
61473
|
vol: Number(storedStats?.vol) || 0,
|
|
60956
61474
|
cnt: Number(storedStats?.count) || 0
|
|
60957
61475
|
};
|
|
60958
|
-
const
|
|
60959
|
-
|
|
60960
|
-
sent: confirmedStats.sent + pendingStats.sent,
|
|
60961
|
-
received: confirmedStats.received + pendingStats.received,
|
|
60962
|
-
net: confirmedStats.net + pendingStats.net,
|
|
60963
|
-
vol: confirmedStats.vol + pendingStats.vol,
|
|
60964
|
-
cnt: confirmedStats.cnt + pendingStats.cnt
|
|
60965
|
-
} : null;
|
|
60966
|
-
const txs = [...pendingPage, ...result.transfers];
|
|
60967
|
-
const total = confirmedStats.cnt + pending.length;
|
|
60968
|
-
return { ...result, transfers: txs, txs, stats, total, hasMore: offset + txs.length < total };
|
|
61476
|
+
const txs = result.transfers;
|
|
61477
|
+
return { ...result, txs, stats: stats.cnt ? stats : null, total: stats.cnt, hasMore: offset + txs.length < stats.cnt };
|
|
60969
61478
|
}
|
|
60970
61479
|
async function getHistorySummary(timeRange = "all-time") {
|
|
60971
61480
|
const result = await historyStore.summarize({ sinceMs: getTxRangeStartMs(timeRange) });
|
|
@@ -61021,31 +61530,16 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
61021
61530
|
}
|
|
61022
61531
|
return [...combined.values()].sort(byRecentTx).slice(0, limit);
|
|
61023
61532
|
}
|
|
61024
|
-
function addCashSeries(series, hourly,
|
|
61025
|
-
const history = historyStore.getSnapshot();
|
|
61026
|
-
const tokens = sources.tokens;
|
|
61533
|
+
function addCashSeries(series, hourly, history, tokens) {
|
|
61027
61534
|
const coverage = !tokens.length || history.tokenComplete;
|
|
61028
|
-
const now = new Date;
|
|
61029
61535
|
const buckets = history.projection[hourly ? "hours" : "days"];
|
|
61030
61536
|
return series.map((point) => {
|
|
61031
|
-
|
|
61032
|
-
|
|
61033
|
-
timestamp = new Date(now);
|
|
61034
|
-
if (point.hour !== "now") {
|
|
61035
|
-
const hour = Number(point.hour.slice(0, 2));
|
|
61036
|
-
timestamp.setHours(hour, 0, 0, 0);
|
|
61037
|
-
if (prefix === "24h" && hour > now.getHours())
|
|
61038
|
-
timestamp.setDate(timestamp.getDate() - 1);
|
|
61039
|
-
}
|
|
61040
|
-
} else {
|
|
61041
|
-
timestamp = new Date(`${point.date}T00:00:00`);
|
|
61042
|
-
timestamp.setDate(timestamp.getDate() + 1);
|
|
61043
|
-
}
|
|
61044
|
-
const known = coverage || history.tokenOldestMs != null && timestamp.getTime() > history.tokenOldestMs;
|
|
61537
|
+
const timestamp = new Date(point.timestamp);
|
|
61538
|
+
const known = point.now || coverage || history.tokenOldestMs != null && timestamp.getTime() > history.tokenOldestMs;
|
|
61045
61539
|
const key = (hourly ? dayHourKey : dayKey)(timestamp);
|
|
61046
61540
|
const deltas = {};
|
|
61047
|
-
if (point.
|
|
61048
|
-
for (const [bucketKey, stats] of Object.entries(buckets)) {
|
|
61541
|
+
if (!point.now)
|
|
61542
|
+
for (const [bucketKey, stats] of Object.entries(buckets || {})) {
|
|
61049
61543
|
if (bucketKey < key)
|
|
61050
61544
|
continue;
|
|
61051
61545
|
for (const [id, units] of Object.entries(stats.tokens || {}))
|
|
@@ -61055,14 +61549,14 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
61055
61549
|
});
|
|
61056
61550
|
}
|
|
61057
61551
|
function buildSnapshot() {
|
|
61058
|
-
const { balance, walletPK, oldestTxMs, txReady, isTxLoading, ensureTxCoverage, loadMoreTxs } = sources;
|
|
61552
|
+
const { balance, tokens, walletPK, oldestTxMs, txReady, isTxLoading, ensureTxCoverage, loadMoreTxs } = sources;
|
|
61059
61553
|
const storeSnapshot = transferStore.getSnapshot();
|
|
61060
61554
|
const historySnapshot = historyStore.getSnapshot();
|
|
61061
61555
|
observedStoreSnapshot = storeSnapshot;
|
|
61062
61556
|
observedHistorySnapshot = historySnapshot;
|
|
61063
61557
|
const transfers = storeSnapshot.transfers;
|
|
61064
61558
|
const aggregatedData = deferAggregation ? EMPTY_AGG : readCurrentAggregatedData(walletPK);
|
|
61065
|
-
const projectedData = projectionData(historySnapshot);
|
|
61559
|
+
const projectedData = historySnapshot.walletPK === walletPK ? projectionData(historySnapshot) : null;
|
|
61066
61560
|
const transactions = aggregatedData.enrichedTxs;
|
|
61067
61561
|
const sortedTransactions = aggregatedData.sortedTxs;
|
|
61068
61562
|
const readLiveTransfers = () => deferAggregation ? transferStore.getSnapshot().transfers : transfers;
|
|
@@ -61132,16 +61626,17 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
61132
61626
|
cache: seriesCache.current
|
|
61133
61627
|
});
|
|
61134
61628
|
};
|
|
61135
|
-
const getHistorySeries = (days) => addCashSeries(getDailySeries({
|
|
61629
|
+
const getHistorySeries = (days, nowMs2 = Date.now()) => !projectedData ? [] : addCashSeries(getDailySeries({
|
|
61136
61630
|
days,
|
|
61137
61631
|
transactions: projectedData.enrichedTxs,
|
|
61138
61632
|
aggregatedData: projectedData,
|
|
61139
61633
|
balance,
|
|
61140
61634
|
historyComplete: historySnapshot.complete,
|
|
61141
61635
|
oldestMs: historySnapshot.bitcoinOldestMs,
|
|
61142
|
-
cache: historySeriesCache.current
|
|
61143
|
-
|
|
61144
|
-
|
|
61636
|
+
cache: historySeriesCache.current,
|
|
61637
|
+
nowMs: nowMs2
|
|
61638
|
+
}), false, historySnapshot, tokens);
|
|
61639
|
+
const getHistoryHourlySeries = (hours, prefix = "today", nowMs2 = Date.now()) => !projectedData ? [] : addCashSeries(getHourlySeriesForData({
|
|
61145
61640
|
hours,
|
|
61146
61641
|
prefix,
|
|
61147
61642
|
transactions: projectedData.enrichedTxs,
|
|
@@ -61149,8 +61644,9 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
61149
61644
|
balance,
|
|
61150
61645
|
historyComplete: historySnapshot.complete,
|
|
61151
61646
|
oldestMs: historySnapshot.bitcoinOldestMs,
|
|
61152
|
-
cache: historySeriesCache.current
|
|
61153
|
-
|
|
61647
|
+
cache: historySeriesCache.current,
|
|
61648
|
+
nowMs: nowMs2
|
|
61649
|
+
}), true, historySnapshot, tokens);
|
|
61154
61650
|
const getTxById = (txId) => getVisibleTransferById(readLiveTransfers(), walletPK, txId, transferByIdRef);
|
|
61155
61651
|
const getPeerTxs = (peerPK) => peerPK ? readPeerData(peerPK).txs : EMPTY_TXS2;
|
|
61156
61652
|
const getPeerStats = (peerPK) => peerPK ? readPeerData(peerPK).stats : null;
|
|
@@ -61183,7 +61679,7 @@ function createTxData({ transferStore, historyStore, diag = null, deferAggregati
|
|
|
61183
61679
|
queryHistory,
|
|
61184
61680
|
getTxListPage,
|
|
61185
61681
|
findTxListPeriod: findTxListPeriod2,
|
|
61186
|
-
txListCount: historySnapshot.
|
|
61682
|
+
txListCount: historySnapshot.walletPK === walletPK ? historySnapshot.count : 0,
|
|
61187
61683
|
getTxById,
|
|
61188
61684
|
searchTxs,
|
|
61189
61685
|
getPeerTxs,
|
|
@@ -61790,8 +62286,7 @@ function openAccount(options = {}) {
|
|
|
61790
62286
|
getPeerProfile: getChatPeerProfile,
|
|
61791
62287
|
refreshPeerProfile: refreshChatPeerProfile,
|
|
61792
62288
|
resolvePeerProfile: resolveChatPeerProfile,
|
|
61793
|
-
maintenance: messageMaintenance
|
|
61794
|
-
resolveActiveCall: (chatId) => calls.resolveActiveCall(chatId)
|
|
62289
|
+
maintenance: messageMaintenance
|
|
61795
62290
|
}, chatSources());
|
|
61796
62291
|
const calls = createCallSession({
|
|
61797
62292
|
cloud,
|
|
@@ -61818,11 +62313,11 @@ function openAccount(options = {}) {
|
|
|
61818
62313
|
function chatSources(sessionOverride = chatSession) {
|
|
61819
62314
|
const currentUser = state.user || {};
|
|
61820
62315
|
const session = sessionOverride;
|
|
61821
|
-
const cachedBlacklist = session?.localCache?.read?.()?.blacklist;
|
|
61822
62316
|
return {
|
|
61823
62317
|
uid: currentUser.uid || "",
|
|
61824
|
-
blocked: currentUser.
|
|
61825
|
-
|
|
62318
|
+
blocked: currentUser.blocked,
|
|
62319
|
+
blockedPending: currentUser.blockedPending,
|
|
62320
|
+
blockedReady: currentUser.blockedReady,
|
|
61826
62321
|
online: state.online,
|
|
61827
62322
|
chatPK: session?.chatPK || currentUser.chatPK || "",
|
|
61828
62323
|
chatSigningPK: session?.chatSigningPK || currentUser.chatSigningPK || "",
|
|
@@ -61855,13 +62350,12 @@ function openAccount(options = {}) {
|
|
|
61855
62350
|
}
|
|
61856
62351
|
function syncPeers() {
|
|
61857
62352
|
const currentUser = state.user || {};
|
|
61858
|
-
const cachedBlacklist = state.session?.localCache?.read?.()?.blacklist;
|
|
61859
62353
|
const chatSnapshot = chat.getSnapshot();
|
|
61860
62354
|
peers.setSources({
|
|
61861
62355
|
chatPeers: chatSnapshot.peers,
|
|
61862
62356
|
chats: chatSnapshot.chats,
|
|
61863
|
-
blocked: currentUser.
|
|
61864
|
-
blockedReady: currentUser.blockedReady
|
|
62357
|
+
blocked: currentUser.blocked,
|
|
62358
|
+
blockedReady: currentUser.blockedReady,
|
|
61865
62359
|
chatPK: state.session?.chatPK || currentUser.chatPK || "",
|
|
61866
62360
|
chatSigningPK: state.session?.chatSigningPK || currentUser.chatSigningPK || "",
|
|
61867
62361
|
walletPeers: wallet.getPeers(),
|
|
@@ -62146,8 +62640,9 @@ function openAccount(options = {}) {
|
|
|
62146
62640
|
hasVault: !!vault,
|
|
62147
62641
|
fromCache: !!info.fromCache
|
|
62148
62642
|
});
|
|
62643
|
+
const currentVault = state.vault && vault && equalBytes(state.vault, vault) ? state.vault : vault || null;
|
|
62149
62644
|
emit2({
|
|
62150
|
-
vault:
|
|
62645
|
+
vault: currentVault,
|
|
62151
62646
|
vaultFromCache: false,
|
|
62152
62647
|
vaultReady: state.vaultReady || !info.fromCache,
|
|
62153
62648
|
vaultError: info.fromCache ? state.vaultError : null
|
|
@@ -62257,7 +62752,7 @@ function openAccount(options = {}) {
|
|
|
62257
62752
|
function syncBootstrap() {
|
|
62258
62753
|
if (closed || !state.session || state.session.closed || !serverReady())
|
|
62259
62754
|
return;
|
|
62260
|
-
if (state.user.blockedReady) {
|
|
62755
|
+
if (state.user.blockedReady && !state.user.blockedPending?.length) {
|
|
62261
62756
|
const cache = state.session.localCache;
|
|
62262
62757
|
const blocked = state.user.blocked;
|
|
62263
62758
|
if (JSON.stringify(cache?.read?.()?.blacklist) !== JSON.stringify(blocked)) {
|
|
@@ -62397,6 +62892,7 @@ function openAccount(options = {}) {
|
|
|
62397
62892
|
});
|
|
62398
62893
|
if (!isCurrent())
|
|
62399
62894
|
throw new Error("account changed during unlock");
|
|
62895
|
+
user.restoreBlocked({ uid, blocked: session.localCache?.read?.()?.blacklist });
|
|
62400
62896
|
chatSession = session;
|
|
62401
62897
|
chat.setSources(chatSources());
|
|
62402
62898
|
unlockStage = "launching";
|
|
@@ -62591,15 +63087,21 @@ function openAccount(options = {}) {
|
|
|
62591
63087
|
if (!observedVault)
|
|
62592
63088
|
throw new Error("vault not ready");
|
|
62593
63089
|
const isCurrent = () => !closed && !operationBarrier.blocksOperations && state.uid === uid && state.session === session && state.vault === observedVault && !session.closed && cloud.auth.user?.uid === uid;
|
|
63090
|
+
markDiag(diag, "account.delete.phase", { phase: "verify-vault" });
|
|
62594
63091
|
const verified = await vaultCrypto.verifyVaultPassword(observedVault, password);
|
|
62595
63092
|
if (!verified)
|
|
62596
63093
|
throw new Error("incorrect password");
|
|
62597
63094
|
if (!isCurrent())
|
|
62598
63095
|
throw new Error("account changed during deletion");
|
|
63096
|
+
await waitForOnlineDeletion(isCurrent);
|
|
63097
|
+
markDiag(diag, "account.delete.phase", { phase: "retire-chats" });
|
|
62599
63098
|
const chats = await chat.getSnapshot().deleteAllChats();
|
|
62600
63099
|
if (!isCurrent())
|
|
62601
63100
|
throw new Error("account changed during deletion");
|
|
63101
|
+
await waitForOnlineDeletion(isCurrent);
|
|
63102
|
+
markDiag(diag, "account.delete.phase", { phase: "delete-account" });
|
|
62602
63103
|
await cloud.user.delete();
|
|
63104
|
+
markDiag(diag, "account.delete.phase", { phase: "clear-local-state" });
|
|
62603
63105
|
try {
|
|
62604
63106
|
await localCache?.clear?.();
|
|
62605
63107
|
} catch (error) {
|
|
@@ -62623,6 +63125,31 @@ function openAccount(options = {}) {
|
|
|
62623
63125
|
finishOperation();
|
|
62624
63126
|
}
|
|
62625
63127
|
}
|
|
63128
|
+
function waitForOnlineDeletion(isCurrent) {
|
|
63129
|
+
if (!isCurrent())
|
|
63130
|
+
return Promise.reject(new Error("account changed during deletion"));
|
|
63131
|
+
if (state.online)
|
|
63132
|
+
return Promise.resolve();
|
|
63133
|
+
return new Promise((resolve, reject) => {
|
|
63134
|
+
const finish = (error) => {
|
|
63135
|
+
clearTimeout(timer);
|
|
63136
|
+
listeners.delete(check);
|
|
63137
|
+
if (error)
|
|
63138
|
+
reject(error);
|
|
63139
|
+
else
|
|
63140
|
+
resolve();
|
|
63141
|
+
};
|
|
63142
|
+
const check = () => {
|
|
63143
|
+
if (!isCurrent())
|
|
63144
|
+
finish(new Error("account changed during deletion"));
|
|
63145
|
+
else if (state.online)
|
|
63146
|
+
finish();
|
|
63147
|
+
};
|
|
63148
|
+
const timer = setTimeout(() => finish(new Error("couldnt connect to server")), ACCOUNT_CONNECTION_UNAVAILABLE_MS);
|
|
63149
|
+
listeners.add(check);
|
|
63150
|
+
check();
|
|
63151
|
+
});
|
|
63152
|
+
}
|
|
62626
63153
|
async function switchNetwork(network) {
|
|
62627
63154
|
if (state.lockState !== "unlocked") {
|
|
62628
63155
|
throw new Error("unlock the vault before switching networks");
|