@glyphteck/veyl 0.73.0 → 0.74.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 +524 -182
- package/dist/accountprofiles.js +1 -0
- package/dist/auth.js +3 -1
- package/dist/cli.js +745 -229
- package/dist/index.js +745 -229
- package/examples/bot-fleet/voice.js +4 -1
- package/package.json +1 -1
package/dist/account.js
CHANGED
|
@@ -16818,6 +16818,7 @@ var WALLET_PENDING_TRANSFER_STALE_RETRY_MS = 2 * MINUTE_MS;
|
|
|
16818
16818
|
var WALLET_PENDING_TRANSFER_STUCK_RETRY_MS = 10 * MINUTE_MS;
|
|
16819
16819
|
var WALLET_PENDING_TRANSFER_DORMANT_RETRY_MS = HOUR_MS;
|
|
16820
16820
|
var WALLET_TRANSFER_CACHE_WRITE_DELAY_MS = 3 * MS_PER_SECOND;
|
|
16821
|
+
var BITCOIN_FEES_MAX_AGE_MS = 5 * 60000;
|
|
16821
16822
|
|
|
16822
16823
|
// ../../core/calls/wire.js
|
|
16823
16824
|
var CALL_REQUEST_MAX_BYTES = 192 * 1024;
|
|
@@ -17672,7 +17673,8 @@ var domains = Object.freeze({
|
|
|
17672
17673
|
veyl: `veyl.${ROOT_DOMAIN}`,
|
|
17673
17674
|
veylDev: `dev.veyl.${ROOT_DOMAIN}`,
|
|
17674
17675
|
live: `live.veyl.${ROOT_DOMAIN}`,
|
|
17675
|
-
liveDev: `live.dev.veyl.${ROOT_DOMAIN}
|
|
17676
|
+
liveDev: `live.dev.veyl.${ROOT_DOMAIN}`,
|
|
17677
|
+
bitcoin: `bitcoin.veyl.${ROOT_DOMAIN}`
|
|
17676
17678
|
});
|
|
17677
17679
|
function getVeylDevWebOrigin(port) {
|
|
17678
17680
|
const value = Number(port);
|
|
@@ -17693,6 +17695,7 @@ var liveEndpoints = Object.freeze({
|
|
|
17693
17695
|
prod: `wss://${domains.live}`,
|
|
17694
17696
|
dev: `wss://${domains.liveDev}`
|
|
17695
17697
|
});
|
|
17698
|
+
var bitcoinEndpoint = `https://${domains.bitcoin}/current`;
|
|
17696
17699
|
var appDomains = Object.freeze([
|
|
17697
17700
|
domains.veyl
|
|
17698
17701
|
]);
|
|
@@ -17726,9 +17729,11 @@ function isAddressOnNetwork(address, network) {
|
|
|
17726
17729
|
|
|
17727
17730
|
// ../../core/settings.js
|
|
17728
17731
|
var SEND_ON_SCAN_ENABLED = false;
|
|
17732
|
+
var WEB_LAYOUTS = ["floating", "sidebar"];
|
|
17729
17733
|
var WALLET_NETWORKS = new Set([MAINNET_NETWORK, REGTEST_NETWORK]);
|
|
17730
17734
|
var defaultSettings = {
|
|
17731
17735
|
glass: true,
|
|
17736
|
+
webLayout: "floating",
|
|
17732
17737
|
moneyFormat: "usd",
|
|
17733
17738
|
ghostWallet: true,
|
|
17734
17739
|
showChatPreviews: true,
|
|
@@ -17736,6 +17741,8 @@ var defaultSettings = {
|
|
|
17736
17741
|
confirmSend: false,
|
|
17737
17742
|
faceID: null,
|
|
17738
17743
|
walletNetwork: null,
|
|
17744
|
+
peerAudio: {},
|
|
17745
|
+
callAudio: { muted: false, deafened: false },
|
|
17739
17746
|
autolock: {
|
|
17740
17747
|
timer: "never",
|
|
17741
17748
|
onHide: false,
|
|
@@ -17797,9 +17804,14 @@ function normalizeSettings(settings, base = defaultSettings) {
|
|
|
17797
17804
|
};
|
|
17798
17805
|
next.autolock = normalizeAutolock(settings.autolock, current.autolock);
|
|
17799
17806
|
next.walletNetwork = normalizeWalletNetworkSetting(next.walletNetwork);
|
|
17807
|
+
next.peerAudio = normalizePeerAudio(settings.peerAudio, current.peerAudio);
|
|
17808
|
+
next.callAudio = normalizeCallAudio(settings.callAudio, current.callAudio);
|
|
17800
17809
|
if (!MONEY_FORMATS.includes(next.moneyFormat)) {
|
|
17801
17810
|
throw new Error("bad moneyFormat");
|
|
17802
17811
|
}
|
|
17812
|
+
if (!WEB_LAYOUTS.includes(next.webLayout)) {
|
|
17813
|
+
throw new Error("bad webLayout");
|
|
17814
|
+
}
|
|
17803
17815
|
if (typeof next.glass !== "boolean") {
|
|
17804
17816
|
throw new Error("glass must be boolean");
|
|
17805
17817
|
}
|
|
@@ -17823,6 +17835,32 @@ function normalizeSettings(settings, base = defaultSettings) {
|
|
|
17823
17835
|
}
|
|
17824
17836
|
return next;
|
|
17825
17837
|
}
|
|
17838
|
+
function normalizeCallAudio(patch, base = defaultSettings.callAudio) {
|
|
17839
|
+
if (patch !== undefined && (!patch || typeof patch !== "object" || Array.isArray(patch)))
|
|
17840
|
+
throw new Error("bad call audio settings");
|
|
17841
|
+
const { muted, deafened } = { ...defaultSettings.callAudio, ...base, ...patch };
|
|
17842
|
+
if (typeof muted !== "boolean" || typeof deafened !== "boolean")
|
|
17843
|
+
throw new Error("bad call audio settings");
|
|
17844
|
+
return { muted, deafened };
|
|
17845
|
+
}
|
|
17846
|
+
function normalizePeerAudio(patch, base = {}) {
|
|
17847
|
+
if (patch === undefined)
|
|
17848
|
+
return base;
|
|
17849
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch))
|
|
17850
|
+
throw new Error("bad peer audio settings");
|
|
17851
|
+
const entries = new Map(Object.entries(base));
|
|
17852
|
+
for (const [chatPK, audio] of Object.entries(patch)) {
|
|
17853
|
+
if (!/^[a-f0-9]{64}$/u.test(chatPK) || !audio || typeof audio !== "object" || Array.isArray(audio)) {
|
|
17854
|
+
throw new Error("bad peer audio settings");
|
|
17855
|
+
}
|
|
17856
|
+
const { volume = 100, muted = false } = { ...entries.get(chatPK), ...audio };
|
|
17857
|
+
if (!Number.isFinite(volume) || volume < 1 || volume > 200 || typeof muted !== "boolean") {
|
|
17858
|
+
throw new Error("bad peer audio settings");
|
|
17859
|
+
}
|
|
17860
|
+
entries.set(chatPK, { volume, muted });
|
|
17861
|
+
}
|
|
17862
|
+
return Object.fromEntries(entries);
|
|
17863
|
+
}
|
|
17826
17864
|
|
|
17827
17865
|
// ../../node_modules/.bun/@noble+ciphers@2.4.0/node_modules/@noble/ciphers/_arx.js
|
|
17828
17866
|
var encodeStr = (str) => Uint8Array.from(str.split(""), (c) => c.charCodeAt(0));
|
|
@@ -18716,6 +18754,9 @@ function normalizeBitcoinPaymentFeeQuote(feeQuote) {
|
|
|
18716
18754
|
};
|
|
18717
18755
|
}
|
|
18718
18756
|
function getFeeRateSatsPerVbyte(bitcoin, speed = DEFAULT_FEE_RATE_SPEED) {
|
|
18757
|
+
const observedAt = Date.parse(bitcoin?.fees?.updatedAtIso);
|
|
18758
|
+
if (!Number.isFinite(observedAt) || Date.now() - observedAt > BITCOIN_FEES_MAX_AGE_MS || observedAt > Date.now() + 60000)
|
|
18759
|
+
return null;
|
|
18719
18760
|
const rates = bitcoin?.fees?.satPerVbyte;
|
|
18720
18761
|
const key = String(speed || DEFAULT_FEE_RATE_SPEED);
|
|
18721
18762
|
const keys = FEE_RATE_FALLBACKS[key] ?? [key, ...FEE_RATE_FALLBACKS.default];
|
|
@@ -19746,6 +19787,9 @@ function readAccountType(profile) {
|
|
|
19746
19787
|
function hasPeerKeys(profile) {
|
|
19747
19788
|
return !!(profile?.walletPK || profile?.chatPK);
|
|
19748
19789
|
}
|
|
19790
|
+
function hasChatIdentity(profile) {
|
|
19791
|
+
return !!(profile?.uid && profile?.chatPK && profile?.chatSigningPK && profile?.notificationPK);
|
|
19792
|
+
}
|
|
19749
19793
|
function readProfileChatIdentity(profile) {
|
|
19750
19794
|
const chat = profile?.identities?.chat;
|
|
19751
19795
|
return {
|
|
@@ -19760,7 +19804,7 @@ function peerUid(peer) {
|
|
|
19760
19804
|
return typeof peer === "string" ? cleanText(peer) : cleanText(peer?.uid);
|
|
19761
19805
|
}
|
|
19762
19806
|
function isFullProfile(profile) {
|
|
19763
|
-
return !!(profile?.uid && (
|
|
19807
|
+
return !!(profile?.uid && hasPeerKeys(profile) && ["username", "walletPK", "chatPK", "chatSigningPK", "notificationPK"].every((key) => (key in profile)) && (!profile.chatPK || hasChatIdentity(profile)));
|
|
19764
19808
|
}
|
|
19765
19809
|
function normalizeProfile(profile, uid = profile?.uid || null) {
|
|
19766
19810
|
return {
|
|
@@ -19861,6 +19905,8 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
19861
19905
|
let authSession = 0;
|
|
19862
19906
|
let userUid = null;
|
|
19863
19907
|
let settingsKey = null;
|
|
19908
|
+
let settingsWriter = null;
|
|
19909
|
+
let openedSettingsBody;
|
|
19864
19910
|
let agreementStored = null;
|
|
19865
19911
|
let agreementOverride = null;
|
|
19866
19912
|
let agreementAcceptance = null;
|
|
@@ -20249,6 +20295,7 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20249
20295
|
settingsBody: privateData.settings ?? null,
|
|
20250
20296
|
settingsFromCache: false
|
|
20251
20297
|
}));
|
|
20298
|
+
refreshUnlockedSettings();
|
|
20252
20299
|
}), current((error) => {
|
|
20253
20300
|
markError(diag, "user.settings.snapshot", authStartedAt, error);
|
|
20254
20301
|
if (revokeAuthenticationError(authUser, error, "settings-listener"))
|
|
@@ -20464,10 +20511,12 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20464
20511
|
throw new Error("settings key required");
|
|
20465
20512
|
if (state.settingsBody === undefined)
|
|
20466
20513
|
throw new Error("settings not available");
|
|
20467
|
-
const
|
|
20514
|
+
const body = state.settingsBody;
|
|
20515
|
+
const nextSettings = body === null ? settingsState() : await openSettings(key, uid, body);
|
|
20468
20516
|
if (!isCurrentUser(uid, session))
|
|
20469
20517
|
return nextSettings;
|
|
20470
20518
|
setSettingsKey(key);
|
|
20519
|
+
openedSettingsBody = body;
|
|
20471
20520
|
setState((user) => ({
|
|
20472
20521
|
...user,
|
|
20473
20522
|
settings: settingsState(nextSettings)
|
|
@@ -20488,15 +20537,59 @@ function createUser({ cloud, network, avatarCache = null, diag = null, onSession
|
|
|
20488
20537
|
throw new Error("auth");
|
|
20489
20538
|
if (!settingsKey)
|
|
20490
20539
|
throw new Error("settings locked");
|
|
20491
|
-
|
|
20492
|
-
if (!
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
|
|
20499
|
-
|
|
20540
|
+
normalizeSettings(patch, state.settings);
|
|
20541
|
+
if (!settingsWriter || settingsWriter.key !== settingsKey) {
|
|
20542
|
+
settingsWriter = { key: settingsKey, uid, session, pending: [], running: false };
|
|
20543
|
+
}
|
|
20544
|
+
const writer = settingsWriter;
|
|
20545
|
+
const result = new Promise((resolve, reject) => writer.pending.push({ patch, resolve, reject }));
|
|
20546
|
+
writeSettings(writer);
|
|
20547
|
+
return result;
|
|
20548
|
+
}
|
|
20549
|
+
async function writeSettings(writer) {
|
|
20550
|
+
if (writer.running)
|
|
20551
|
+
return;
|
|
20552
|
+
writer.running = true;
|
|
20553
|
+
const current = () => settingsKey === writer.key && isCurrentUser(writer.uid, writer.session);
|
|
20554
|
+
while (writer.pending.length) {
|
|
20555
|
+
const batch = writer.pending.splice(0);
|
|
20556
|
+
try {
|
|
20557
|
+
if (!current())
|
|
20558
|
+
throw new Error("settings locked");
|
|
20559
|
+
const desired = batch.reduce((settings2, { patch }) => normalizeSettings(patch, settings2), state.settings);
|
|
20560
|
+
const { settings, body } = await cloud.user.settings.write(writer.uid, desired, { currentSettings: state.settings, key: writer.key });
|
|
20561
|
+
if (!current())
|
|
20562
|
+
throw new Error("settings locked");
|
|
20563
|
+
openedSettingsBody = body;
|
|
20564
|
+
setState((user) => ({ ...user, settingsReady: true, settings: settingsState(settings), settingsBody: body }));
|
|
20565
|
+
for (const request of batch)
|
|
20566
|
+
request.resolve(settings);
|
|
20567
|
+
} catch (error) {
|
|
20568
|
+
for (const request of batch)
|
|
20569
|
+
request.reject(error);
|
|
20570
|
+
}
|
|
20571
|
+
}
|
|
20572
|
+
writer.running = false;
|
|
20573
|
+
if (settingsWriter === writer)
|
|
20574
|
+
settingsWriter = null;
|
|
20575
|
+
refreshUnlockedSettings();
|
|
20576
|
+
}
|
|
20577
|
+
async function refreshUnlockedSettings() {
|
|
20578
|
+
const key = settingsKey;
|
|
20579
|
+
const body = state.settingsBody;
|
|
20580
|
+
if (!key || body === undefined || body === openedSettingsBody || settingsWriter?.key === key)
|
|
20581
|
+
return;
|
|
20582
|
+
const uid = state.uid;
|
|
20583
|
+
try {
|
|
20584
|
+
const settings = body === null ? settingsState() : await openSettings(key, uid, body);
|
|
20585
|
+
if (settingsKey !== key || state.settingsBody !== body || settingsWriter?.key === key)
|
|
20586
|
+
return;
|
|
20587
|
+
openedSettingsBody = body;
|
|
20588
|
+
setState((user) => ({ ...user, settings: settingsState(settings) }));
|
|
20589
|
+
} catch (error) {
|
|
20590
|
+
if (settingsKey === key && state.settingsBody === body)
|
|
20591
|
+
markError(diag, "user.settings.open", Date.now(), error);
|
|
20592
|
+
}
|
|
20500
20593
|
}
|
|
20501
20594
|
function isBlocked(peer) {
|
|
20502
20595
|
const nextPeerUid = peerUid(peer);
|
|
@@ -25059,7 +25152,7 @@ function groupEvidence(messages, byKey, memberStatesByEpoch) {
|
|
|
25059
25152
|
const target = byKey.get(reference);
|
|
25060
25153
|
addEvidence(evidence, messageEpochId(target), actor, messageOrderMs(target), seenAt);
|
|
25061
25154
|
}
|
|
25062
|
-
if (canShowMsg(message)
|
|
25155
|
+
if (canShowMsg(message)) {
|
|
25063
25156
|
addEvidence(evidence, messageEpochId(message), actor, seenAt, seenAt);
|
|
25064
25157
|
}
|
|
25065
25158
|
}
|
|
@@ -25087,11 +25180,12 @@ function analyzeMessageExpiry(messages, selfChatPublicKey, _peerChatPublicKey, o
|
|
|
25087
25180
|
const shorten = [];
|
|
25088
25181
|
let nextExpiryAt = null;
|
|
25089
25182
|
const latestStart = latestCallStartKey(projectedMessages.filter(isServerConfirmedMsg));
|
|
25183
|
+
const endedCalls = new Set(projectedMessages.filter(isServerConfirmedMsg).filter((message) => readCallMessage(message)?.event === "ended").map((message) => message.callId));
|
|
25090
25184
|
for (const message of projectedMessages) {
|
|
25091
25185
|
if (!isServerConfirmedMsg(message) || !canShowMsg(message) || message.ttl == null)
|
|
25092
25186
|
continue;
|
|
25093
25187
|
const call = readCallMessage(message);
|
|
25094
|
-
if (call?.event === "started" && getMessageKey(message) === latestStart && (options.activeCallId === undefined || options.activeCallId === call.callId))
|
|
25188
|
+
if (call?.event === "started" && getMessageKey(message) === latestStart && (options.activeCallId === undefined && !endedCalls.has(call.callId) || options.activeCallId === call.callId))
|
|
25095
25189
|
continue;
|
|
25096
25190
|
const messageMs = messageOrderMs(message);
|
|
25097
25191
|
const epochId = messageEpochId(message);
|
|
@@ -26446,7 +26540,7 @@ function partitionExpiredMessageRecords(records, expiredKeys, cache, now = Date.
|
|
|
26446
26540
|
return { activeRecords, expiredRecords };
|
|
26447
26541
|
}
|
|
26448
26542
|
function canHideExpiredMessage(message, chatPK) {
|
|
26449
|
-
return isServerConfirmedMsg(message) && isPeerMsg(message, chatPK) && canShowMsg(message) &&
|
|
26543
|
+
return isServerConfirmedMsg(message) && isPeerMsg(message, chatPK) && canShowMsg(message) && message.ttl != null && message.permanent !== true;
|
|
26450
26544
|
}
|
|
26451
26545
|
function hiddenMessageKeys(messages, chatPK, peerChatPK, options = {}) {
|
|
26452
26546
|
if (!chatPK || !peerChatPK || !Array.isArray(messages) || !messages.length)
|
|
@@ -40686,27 +40780,23 @@ function createChatSessionLaunches({
|
|
|
40686
40780
|
const openDirectChat = async (profile) => {
|
|
40687
40781
|
const operation = operationContext();
|
|
40688
40782
|
const { chatPK, blocked } = operation.identity;
|
|
40689
|
-
|
|
40690
|
-
|
|
40783
|
+
if (!profile?.uid || !profile?.chatPK)
|
|
40784
|
+
throw new Error("peer identity required");
|
|
40785
|
+
if (profile.chatPK === chatPK)
|
|
40691
40786
|
return openNotesChat(operation);
|
|
40692
|
-
if (new Set(blocked).has(
|
|
40787
|
+
if (new Set(blocked).has(profile.uid))
|
|
40693
40788
|
throw new Error("blocked peer cannot be opened");
|
|
40694
40789
|
assertDirectAdmission(profile, operation);
|
|
40695
|
-
const existingChatId = getPeerChatId(
|
|
40790
|
+
const existingChatId = getPeerChatId(profile.chatPK, operation);
|
|
40696
40791
|
if (existingChatId) {
|
|
40697
40792
|
setSelectedChat(existingChatId);
|
|
40698
40793
|
return existingChatId;
|
|
40699
40794
|
}
|
|
40795
|
+
const member = { uid: profile.uid, chatPK: profile.chatPK };
|
|
40700
40796
|
const routeId = directRouteIdForPeer(member.chatPK, operation);
|
|
40701
40797
|
if (operation.pendingOwner.getPendingLaunch()?.id === routeId)
|
|
40702
40798
|
return routeId;
|
|
40703
|
-
operation.pendingOwner.showEmpty(routeId, [
|
|
40704
|
-
...member,
|
|
40705
|
-
username: profile?.username || null,
|
|
40706
|
-
avatar: profile?.avatar || null,
|
|
40707
|
-
accountType: profile?.accountType || null,
|
|
40708
|
-
chatAdmission: normalizeChatAdmission(profile?.chatAdmission)
|
|
40709
|
-
}], [member], "direct");
|
|
40799
|
+
operation.pendingOwner.showEmpty(routeId, [profile], [member], "direct");
|
|
40710
40800
|
const materialization = directMaterializations.get(routeId);
|
|
40711
40801
|
if (materialization && isCurrent(materialization.operation)) {
|
|
40712
40802
|
materialization.promise.then((chatId) => {
|
|
@@ -46189,6 +46279,11 @@ function openCallMailbox({ cloud, capability, endpoint, protocol, onChange, onEr
|
|
|
46189
46279
|
}
|
|
46190
46280
|
return Object.freeze({
|
|
46191
46281
|
getSnapshot: () => candidate || record,
|
|
46282
|
+
async ready() {
|
|
46283
|
+
const response = await channel.ready();
|
|
46284
|
+
await consume(response.record);
|
|
46285
|
+
return record;
|
|
46286
|
+
},
|
|
46192
46287
|
async read() {
|
|
46193
46288
|
const response = await channel.request({ type: "read", cursor, headDigest: record?.headDigest ?? null });
|
|
46194
46289
|
await consume(response.record);
|
|
@@ -46286,6 +46381,7 @@ function openAccountCallLease({ cloud, capability: accountCapability, endpoint,
|
|
|
46286
46381
|
let expiry = null;
|
|
46287
46382
|
let generation = 0;
|
|
46288
46383
|
let acquiring = false;
|
|
46384
|
+
let rotating = Promise.resolve();
|
|
46289
46385
|
const attempts = new Set;
|
|
46290
46386
|
const waiters = new Set;
|
|
46291
46387
|
const current = () => !closed;
|
|
@@ -46386,6 +46482,7 @@ function openAccountCallLease({ cloud, capability: accountCapability, endpoint,
|
|
|
46386
46482
|
async function grant(command) {
|
|
46387
46483
|
if (closed)
|
|
46388
46484
|
throw new Error("call lease closed");
|
|
46485
|
+
const requestedAt = clock();
|
|
46389
46486
|
const attempt = createCallLeaseAttempt(command, clock);
|
|
46390
46487
|
const started = generation;
|
|
46391
46488
|
attempts.add(attempt);
|
|
@@ -46405,6 +46502,8 @@ function openAccountCallLease({ cloud, capability: accountCapability, endpoint,
|
|
|
46405
46502
|
onLost?.();
|
|
46406
46503
|
}, Math.max(0, deadline.wallDeadline - Date.now()));
|
|
46407
46504
|
clearTimeout(renewal);
|
|
46505
|
+
const now = clock();
|
|
46506
|
+
const elapsed = Math.max(now.wall - requestedAt.wall, now.monotonic - requestedAt.monotonic);
|
|
46408
46507
|
renewal = setTimeout(() => {
|
|
46409
46508
|
if (!owned)
|
|
46410
46509
|
return;
|
|
@@ -46416,14 +46515,35 @@ function openAccountCallLease({ cloud, capability: accountCapability, endpoint,
|
|
|
46416
46515
|
onLost?.();
|
|
46417
46516
|
onError?.(error);
|
|
46418
46517
|
});
|
|
46419
|
-
}, 5000);
|
|
46518
|
+
}, Math.max(0, 5000 - elapsed));
|
|
46420
46519
|
return value;
|
|
46421
46520
|
} finally {
|
|
46422
46521
|
attempts.delete(attempt);
|
|
46423
46522
|
attempt.cancel();
|
|
46424
46523
|
}
|
|
46425
46524
|
}
|
|
46525
|
+
async function rotateMedia() {
|
|
46526
|
+
if (closed || acquiring || !owned || !renewalRemaining(generation))
|
|
46527
|
+
throw new Error("call ownership unavailable");
|
|
46528
|
+
const previous = stop();
|
|
46529
|
+
const started = generation;
|
|
46530
|
+
await request({ type: "release", holder: endpoint.holder, revision: previous.revision });
|
|
46531
|
+
if (closed || started !== generation)
|
|
46532
|
+
throw new Error("call transfer cancelled");
|
|
46533
|
+
return grant({
|
|
46534
|
+
type: "claim",
|
|
46535
|
+
holder: endpoint.holder,
|
|
46536
|
+
expectedRevision: previous.revision,
|
|
46537
|
+
payload: previous.payload,
|
|
46538
|
+
takeover: false
|
|
46539
|
+
});
|
|
46540
|
+
}
|
|
46426
46541
|
return Object.freeze({
|
|
46542
|
+
rotateMedia() {
|
|
46543
|
+
const next = rotating.then(rotateMedia);
|
|
46544
|
+
rotating = next.catch(() => {});
|
|
46545
|
+
return next;
|
|
46546
|
+
},
|
|
46427
46547
|
async acquire(descriptor) {
|
|
46428
46548
|
if (closed)
|
|
46429
46549
|
throw new Error("call lease closed");
|
|
@@ -46432,7 +46552,7 @@ function openAccountCallLease({ cloud, capability: accountCapability, endpoint,
|
|
|
46432
46552
|
acquiring = true;
|
|
46433
46553
|
const started = generation;
|
|
46434
46554
|
try {
|
|
46435
|
-
await
|
|
46555
|
+
await accept((await channel.ready()).record);
|
|
46436
46556
|
const payload = await capability.seal(descriptor);
|
|
46437
46557
|
if (closed || started !== generation)
|
|
46438
46558
|
throw new Error("call transfer cancelled");
|
|
@@ -46872,7 +46992,7 @@ function openCallRoom({
|
|
|
46872
46992
|
pulseTimer = setTimeout(pulse, 1e4);
|
|
46873
46993
|
}
|
|
46874
46994
|
async function beginStart(create) {
|
|
46875
|
-
const record = await mailbox.
|
|
46995
|
+
const record = await mailbox.ready();
|
|
46876
46996
|
if (closed || departing)
|
|
46877
46997
|
throw cancelled2();
|
|
46878
46998
|
if (!create && !record?.head)
|
|
@@ -47026,7 +47146,7 @@ function createCallActivity({ holder, onSpeaking, now = Date.now, setTimer = set
|
|
|
47026
47146
|
return;
|
|
47027
47147
|
const admitted = new Map(peers.map((peer) => [peer.holder, peer]));
|
|
47028
47148
|
local = admitted.has(holder) && !muted && !deafened;
|
|
47029
|
-
direct = mode === "direct" && directPeer !== holder && admitted.has(directPeer)
|
|
47149
|
+
direct = mode === "direct" && directPeer !== holder && admitted.has(directPeer) ? directPeer : null;
|
|
47030
47150
|
const nextSources = new Map(local ? [[holder, "local"]] : []);
|
|
47031
47151
|
const nextRoutes2 = new Map;
|
|
47032
47152
|
const ambiguous = new Set;
|
|
@@ -47034,7 +47154,7 @@ function createCallActivity({ holder, onSpeaking, now = Date.now, setTimer = set
|
|
|
47034
47154
|
nextSources.set(direct, "direct");
|
|
47035
47155
|
if (mode === "group") {
|
|
47036
47156
|
for (const [id, mid] of received) {
|
|
47037
|
-
if (id === holder || !admitted.has(id) ||
|
|
47157
|
+
if (id === holder || !admitted.has(id) || typeof mid !== "string" || !mid)
|
|
47038
47158
|
continue;
|
|
47039
47159
|
if (nextRoutes2.has(mid)) {
|
|
47040
47160
|
nextSources.delete(nextRoutes2.get(mid));
|
|
@@ -47101,13 +47221,14 @@ var validMid = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,32}$/u.
|
|
|
47101
47221
|
var MAX_PENDING_SIGNALS = 128;
|
|
47102
47222
|
var CONNECTION_TIMEOUT_MS = 15000;
|
|
47103
47223
|
var retiredConnection = new Error("call connection retired");
|
|
47104
|
-
function createCallMediaSession({ port, mode, holder, provider, signal, publish, onState, onError, onExpired, onSpeaking }) {
|
|
47224
|
+
function createCallMediaSession({ port, mode, holder, preparation, provider, signal, publish, onState, onError, onExpired, onSpeaking, diag }) {
|
|
47105
47225
|
let media = null;
|
|
47106
47226
|
let closed = false;
|
|
47107
47227
|
let deadline = null;
|
|
47108
47228
|
let keys = null;
|
|
47109
47229
|
let peers = [];
|
|
47110
47230
|
let sessionId = null;
|
|
47231
|
+
let published = false;
|
|
47111
47232
|
let started = false;
|
|
47112
47233
|
let directPeer = null;
|
|
47113
47234
|
let offered = false;
|
|
@@ -47230,7 +47351,11 @@ function createCallMediaSession({ port, mode, holder, provider, signal, publish,
|
|
|
47230
47351
|
for (const id of pendingPeerAudio.keys())
|
|
47231
47352
|
settlePeerAudio(id, new Error("call media closed"));
|
|
47232
47353
|
pendingSignals.length = incomingSignals.length = 0;
|
|
47233
|
-
const operations = [
|
|
47354
|
+
const operations = [
|
|
47355
|
+
disposeConnection(previous),
|
|
47356
|
+
retiringConnection,
|
|
47357
|
+
openingConnection?.then((opened) => opened !== previous ? disposeConnection(opened) : undefined, () => {})
|
|
47358
|
+
];
|
|
47234
47359
|
closing = Promise.allSettled(operations).then((results) => {
|
|
47235
47360
|
const failed = results.find((result) => result.status === "rejected");
|
|
47236
47361
|
if (failed)
|
|
@@ -47243,22 +47368,24 @@ function createCallMediaSession({ port, mode, holder, provider, signal, publish,
|
|
|
47243
47368
|
const live = () => !closed && generation === connectionGeneration;
|
|
47244
47369
|
await retiringConnection;
|
|
47245
47370
|
current(generation);
|
|
47246
|
-
|
|
47247
|
-
|
|
47248
|
-
|
|
47249
|
-
iceServers = configuration.iceServers;
|
|
47250
|
-
}
|
|
47371
|
+
const startedAt = Date.now();
|
|
47372
|
+
const prepared = preparation;
|
|
47373
|
+
preparation = null;
|
|
47251
47374
|
const opening = port.open({
|
|
47252
47375
|
id: holder,
|
|
47253
47376
|
mode,
|
|
47254
|
-
|
|
47377
|
+
preparation: prepared,
|
|
47255
47378
|
onState: (value) => {
|
|
47256
47379
|
if (!live())
|
|
47257
47380
|
return;
|
|
47258
47381
|
connectionState = value.state;
|
|
47259
|
-
if (connectionState === "connected")
|
|
47382
|
+
if (connectionState === "connected") {
|
|
47260
47383
|
settleConnection();
|
|
47261
|
-
|
|
47384
|
+
serialize(async () => {
|
|
47385
|
+
if (live())
|
|
47386
|
+
await media?.startSource?.();
|
|
47387
|
+
});
|
|
47388
|
+
} else if (["failed", "closed"].includes(connectionState))
|
|
47262
47389
|
settleConnection(new Error("call connection failed"));
|
|
47263
47390
|
onState?.(value);
|
|
47264
47391
|
},
|
|
@@ -47295,34 +47422,51 @@ function createCallMediaSession({ port, mode, holder, provider, signal, publish,
|
|
|
47295
47422
|
}
|
|
47296
47423
|
});
|
|
47297
47424
|
openingConnection = opening;
|
|
47298
|
-
|
|
47425
|
+
const preparing = opening.then(async (opened) => {
|
|
47426
|
+
current(generation);
|
|
47427
|
+
media = opened;
|
|
47428
|
+
await opened.mute(muted);
|
|
47429
|
+
current(generation);
|
|
47430
|
+
await opened.deafen(deafened);
|
|
47431
|
+
current(generation);
|
|
47432
|
+
if (microphoneVolume !== 1) {
|
|
47433
|
+
await opened.setMicrophoneVolume(microphoneVolume);
|
|
47434
|
+
current(generation);
|
|
47435
|
+
}
|
|
47436
|
+
await opened.prepare();
|
|
47437
|
+
current(generation);
|
|
47438
|
+
markDone(diag, "call.media.setup", startedAt, { stage: "local" });
|
|
47439
|
+
return opened;
|
|
47440
|
+
});
|
|
47299
47441
|
try {
|
|
47300
|
-
opened = await
|
|
47301
|
-
|
|
47302
|
-
|
|
47303
|
-
|
|
47304
|
-
|
|
47305
|
-
|
|
47306
|
-
|
|
47442
|
+
const [opened, configuration] = await Promise.all([
|
|
47443
|
+
preparing,
|
|
47444
|
+
Promise.resolve().then(() => iceServers ? { iceServers, sessionId } : provider(mode === "group" ? "session/new" : "turn", {})).then((value) => {
|
|
47445
|
+
current(generation);
|
|
47446
|
+
markDone(diag, "call.media.setup", startedAt, { stage: "provider" });
|
|
47447
|
+
return value;
|
|
47448
|
+
})
|
|
47449
|
+
]);
|
|
47307
47450
|
current(generation);
|
|
47308
|
-
|
|
47309
|
-
|
|
47310
|
-
|
|
47311
|
-
|
|
47312
|
-
await opened.deafen(deafened);
|
|
47313
|
-
current(generation);
|
|
47314
|
-
if (microphoneVolume !== 1) {
|
|
47315
|
-
await opened.setMicrophoneVolume(microphoneVolume);
|
|
47451
|
+
iceServers = configuration.iceServers;
|
|
47452
|
+
if (mode === "group")
|
|
47453
|
+
sessionId = configuration.sessionId;
|
|
47454
|
+
await opened.configure(iceServers);
|
|
47316
47455
|
current(generation);
|
|
47317
|
-
|
|
47318
|
-
|
|
47319
|
-
|
|
47320
|
-
|
|
47321
|
-
await
|
|
47456
|
+
if (deadline) {
|
|
47457
|
+
await opened.grant(deadline);
|
|
47458
|
+
current(generation);
|
|
47459
|
+
}
|
|
47460
|
+
await updateKeys();
|
|
47322
47461
|
current(generation);
|
|
47462
|
+
markDone(diag, "call.media.setup", startedAt, { stage: "ready" });
|
|
47463
|
+
} catch (error) {
|
|
47464
|
+
await disposeConnection(await opening.catch(() => null));
|
|
47465
|
+
throw error;
|
|
47466
|
+
} finally {
|
|
47467
|
+
if (openingConnection === opening)
|
|
47468
|
+
openingConnection = null;
|
|
47323
47469
|
}
|
|
47324
|
-
await updateKeys();
|
|
47325
|
-
current(generation);
|
|
47326
47470
|
}
|
|
47327
47471
|
async function applyDescription(result, required = false) {
|
|
47328
47472
|
current();
|
|
@@ -47385,10 +47529,7 @@ function createCallMediaSession({ port, mode, holder, provider, signal, publish,
|
|
|
47385
47529
|
if (!started || !keys)
|
|
47386
47530
|
return;
|
|
47387
47531
|
if (mode === "group") {
|
|
47388
|
-
if (!
|
|
47389
|
-
const session = await provider("session/new", {});
|
|
47390
|
-
current();
|
|
47391
|
-
sessionId = session.sessionId;
|
|
47532
|
+
if (!published) {
|
|
47392
47533
|
const offer = await media.offer();
|
|
47393
47534
|
current();
|
|
47394
47535
|
const sendMid = media.sendMid;
|
|
@@ -47407,48 +47548,15 @@ function createCallMediaSession({ port, mode, holder, provider, signal, publish,
|
|
|
47407
47548
|
current();
|
|
47408
47549
|
await connected();
|
|
47409
47550
|
current();
|
|
47410
|
-
await
|
|
47411
|
-
|
|
47412
|
-
|
|
47413
|
-
|
|
47414
|
-
|
|
47415
|
-
|
|
47416
|
-
|
|
47417
|
-
|
|
47418
|
-
|
|
47419
|
-
sessionId: peer.media.sessionId,
|
|
47420
|
-
trackName: peer.media.trackName
|
|
47421
|
-
})) });
|
|
47422
|
-
current();
|
|
47423
|
-
if (!Array.isArray(result.tracks) || result.tracks.length !== additions.length)
|
|
47424
|
-
throw new Error("peer audio subscription failed");
|
|
47425
|
-
const mappings = [];
|
|
47426
|
-
const occupied = new Set([media.sendMid, ...received.values()]);
|
|
47427
|
-
for (const peer of additions) {
|
|
47428
|
-
const track = result.tracks?.find((item) => item.sessionId === peer.media.sessionId && item.trackName === peer.media.trackName);
|
|
47429
|
-
if (!validMid(track?.mid) || track.errorCode || occupied.has(track.mid))
|
|
47430
|
-
throw new Error("peer audio subscription failed");
|
|
47431
|
-
occupied.add(track.mid);
|
|
47432
|
-
mappings.push([peer.holder, track.mid]);
|
|
47433
|
-
}
|
|
47434
|
-
for (const [peer, mid] of mappings)
|
|
47435
|
-
received.set(peer, mid);
|
|
47436
|
-
updateActivity();
|
|
47437
|
-
await applyPeerAudio();
|
|
47438
|
-
current();
|
|
47439
|
-
await updateKeys();
|
|
47440
|
-
current();
|
|
47441
|
-
await applyDescription(result);
|
|
47442
|
-
}
|
|
47443
|
-
const removals = [...received].filter(([peer]) => !peers.some((item) => item.holder === peer));
|
|
47444
|
-
if (removals.length) {
|
|
47445
|
-
for (const [peer] of removals)
|
|
47446
|
-
received.delete(peer);
|
|
47447
|
-
updateActivity();
|
|
47448
|
-
await updateKeys();
|
|
47449
|
-
current();
|
|
47450
|
-
const result = await provider("tracks/close", { sessionId, tracks: removals.map(([, mid]) => ({ mid })) });
|
|
47451
|
-
await applyDescription(result);
|
|
47551
|
+
await Promise.all([
|
|
47552
|
+
publish({ sessionId, trackName }).then(() => {
|
|
47553
|
+
current();
|
|
47554
|
+
published = true;
|
|
47555
|
+
}),
|
|
47556
|
+
receiveGroupTracks()
|
|
47557
|
+
]);
|
|
47558
|
+
} else {
|
|
47559
|
+
await receiveGroupTracks();
|
|
47452
47560
|
}
|
|
47453
47561
|
} else {
|
|
47454
47562
|
const peer = peers.find((item) => item.holder !== holder);
|
|
@@ -47476,6 +47584,48 @@ function createCallMediaSession({ port, mode, holder, provider, signal, publish,
|
|
|
47476
47584
|
}
|
|
47477
47585
|
}
|
|
47478
47586
|
}
|
|
47587
|
+
async function receiveGroupTracks() {
|
|
47588
|
+
const additions = peers.filter((peer) => peer.holder !== holder && peer.media?.sessionId && peer.media?.trackName && !received.has(peer.holder));
|
|
47589
|
+
if (additions.length) {
|
|
47590
|
+
await connected();
|
|
47591
|
+
current();
|
|
47592
|
+
const result = await provider("tracks/new", { sessionId, tracks: additions.map((peer) => ({
|
|
47593
|
+
location: "remote",
|
|
47594
|
+
sessionId: peer.media.sessionId,
|
|
47595
|
+
trackName: peer.media.trackName
|
|
47596
|
+
})) });
|
|
47597
|
+
current();
|
|
47598
|
+
if (!Array.isArray(result.tracks) || result.tracks.length !== additions.length)
|
|
47599
|
+
throw new Error("peer audio subscription failed");
|
|
47600
|
+
const mappings = [];
|
|
47601
|
+
const occupied = new Set([media.sendMid, ...received.values()]);
|
|
47602
|
+
for (const peer of additions) {
|
|
47603
|
+
const track = result.tracks?.find((item) => item.sessionId === peer.media.sessionId && item.trackName === peer.media.trackName);
|
|
47604
|
+
if (!validMid(track?.mid) || track.errorCode || occupied.has(track.mid))
|
|
47605
|
+
throw new Error("peer audio subscription failed");
|
|
47606
|
+
occupied.add(track.mid);
|
|
47607
|
+
mappings.push([peer.holder, track.mid]);
|
|
47608
|
+
}
|
|
47609
|
+
for (const [peer, mid] of mappings)
|
|
47610
|
+
received.set(peer, mid);
|
|
47611
|
+
updateActivity();
|
|
47612
|
+
await applyPeerAudio();
|
|
47613
|
+
current();
|
|
47614
|
+
await updateKeys();
|
|
47615
|
+
current();
|
|
47616
|
+
await applyDescription(result);
|
|
47617
|
+
}
|
|
47618
|
+
const removals = [...received].filter(([peer]) => !peers.some((item) => item.holder === peer));
|
|
47619
|
+
if (removals.length) {
|
|
47620
|
+
for (const [peer] of removals)
|
|
47621
|
+
received.delete(peer);
|
|
47622
|
+
updateActivity();
|
|
47623
|
+
await updateKeys();
|
|
47624
|
+
current();
|
|
47625
|
+
const result = await provider("tracks/close", { sessionId, tracks: removals.map(([, mid]) => ({ mid })) });
|
|
47626
|
+
await applyDescription(result);
|
|
47627
|
+
}
|
|
47628
|
+
}
|
|
47479
47629
|
return Object.freeze({
|
|
47480
47630
|
async open() {
|
|
47481
47631
|
current();
|
|
@@ -47531,13 +47681,19 @@ function createCallMediaSession({ port, mode, holder, provider, signal, publish,
|
|
|
47531
47681
|
},
|
|
47532
47682
|
mute(value) {
|
|
47533
47683
|
current();
|
|
47534
|
-
|
|
47684
|
+
const next = value === true;
|
|
47685
|
+
if (muted === next)
|
|
47686
|
+
return;
|
|
47687
|
+
muted = next;
|
|
47535
47688
|
updateActivity();
|
|
47536
47689
|
return media?.mute(muted);
|
|
47537
47690
|
},
|
|
47538
47691
|
deafen(value) {
|
|
47539
47692
|
current();
|
|
47540
|
-
|
|
47693
|
+
const next = value === true;
|
|
47694
|
+
if (deafened === next)
|
|
47695
|
+
return;
|
|
47696
|
+
deafened = next;
|
|
47541
47697
|
updateActivity();
|
|
47542
47698
|
return media?.deafen(deafened);
|
|
47543
47699
|
},
|
|
@@ -47722,7 +47878,7 @@ var initial = () => ({ phase: "idle", chatId: null, callId: null, joiningChatId:
|
|
|
47722
47878
|
var cancelled2 = () => Object.assign(new Error("call cancelled"), { code: "calls/cancelled" });
|
|
47723
47879
|
var ended = () => Object.assign(new Error("this call has ended"), { code: "calls/ended" });
|
|
47724
47880
|
var callError = (chatId, error) => ({ chatId, code: error?.code || "calls/failed", message: error?.message || "could not connect the call" });
|
|
47725
|
-
function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
47881
|
+
function createCallSession({ cloud, chat, media: mediaPort, mls, savePeerAudio, saveAudio, diag }) {
|
|
47726
47882
|
const listeners = new Set;
|
|
47727
47883
|
const discoveries = new Map;
|
|
47728
47884
|
const discoveryReads = new Map;
|
|
@@ -47730,11 +47886,16 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
47730
47886
|
const retiring = new Set;
|
|
47731
47887
|
let state = initial();
|
|
47732
47888
|
const confirmedPeerAudio = new Map;
|
|
47889
|
+
const pendingPeerAudio = new Map;
|
|
47890
|
+
let peerAudioSource = null;
|
|
47891
|
+
let audioSource = normalizeCallAudio();
|
|
47892
|
+
let pendingAudio = null;
|
|
47733
47893
|
let identity = null;
|
|
47734
47894
|
let endpoint = null;
|
|
47735
47895
|
let leaseEndpoint = null;
|
|
47736
47896
|
let lease = null;
|
|
47737
47897
|
let active = null;
|
|
47898
|
+
let participation = null;
|
|
47738
47899
|
let focused = null;
|
|
47739
47900
|
let generation = 0;
|
|
47740
47901
|
let joinIntent = 0;
|
|
@@ -47809,6 +47970,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
47809
47970
|
emit({ available: next });
|
|
47810
47971
|
}
|
|
47811
47972
|
function closeDiscovery(entry) {
|
|
47973
|
+
if ([...retiring].some((call) => call.discovery === entry))
|
|
47974
|
+
return;
|
|
47812
47975
|
entry.mailbox.close();
|
|
47813
47976
|
entry.observation?.close();
|
|
47814
47977
|
entry.observation = null;
|
|
@@ -47934,8 +48097,14 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
47934
48097
|
throw Object.assign(new Error("chat unavailable"), { code: "unavailable" });
|
|
47935
48098
|
if (!epochState)
|
|
47936
48099
|
throw Object.assign(new Error("chat unavailable"), { code: "calls/chat-not-ready" });
|
|
48100
|
+
if (chat.getSnapshot().getOwnerChat(chatId)?.epochState?.manifest.epochId !== epochId)
|
|
48101
|
+
throw cancelled2();
|
|
48102
|
+
const existing = discoveries.get(chatId);
|
|
47937
48103
|
const entry = discovery(chatId, epochState);
|
|
47938
|
-
|
|
48104
|
+
if (entry === existing)
|
|
48105
|
+
await entry.mailbox.read();
|
|
48106
|
+
else
|
|
48107
|
+
await entry.mailbox.ready();
|
|
47939
48108
|
current(token);
|
|
47940
48109
|
return entry;
|
|
47941
48110
|
}).finally(() => {
|
|
@@ -47969,49 +48138,68 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
47969
48138
|
prune();
|
|
47970
48139
|
};
|
|
47971
48140
|
}
|
|
47972
|
-
async function joinAttempt(chatId, intent, expectedCallId, onlyExisting) {
|
|
48141
|
+
async function joinAttempt(chatId, intent, expectedCallId, onlyExisting, continuation = null) {
|
|
47973
48142
|
if (!identity || !mediaPort || !mls || !cloud.calls)
|
|
47974
48143
|
throw new Error("calling unavailable");
|
|
47975
|
-
if (!foreground)
|
|
47976
|
-
throw new Error("open veyl to join a call");
|
|
47977
48144
|
if (active?.chatId === chatId)
|
|
47978
48145
|
return chatId;
|
|
47979
48146
|
const token = generation;
|
|
47980
48147
|
const startedAt = Date.now();
|
|
47981
48148
|
const step = (stage) => diag?.("call.join.stage", { stage, elapsedMs: Date.now() - startedAt });
|
|
48149
|
+
let preflightActive = true;
|
|
47982
48150
|
const check = () => {
|
|
47983
48151
|
current(token);
|
|
47984
|
-
if (intent !== joinIntent
|
|
48152
|
+
if (!preflightActive || intent !== joinIntent)
|
|
47985
48153
|
throw cancelled2();
|
|
47986
48154
|
};
|
|
47987
|
-
|
|
47988
|
-
|
|
47989
|
-
check();
|
|
47990
|
-
step("permission");
|
|
47991
|
-
if (!onlyExisting)
|
|
47992
|
-
chatId = await chat.getSnapshot().materializeChat(chatId);
|
|
47993
|
-
check();
|
|
47994
|
-
joining.chatId = chatId;
|
|
47995
|
-
step("chat");
|
|
48155
|
+
const operation = joining;
|
|
48156
|
+
let preparation;
|
|
47996
48157
|
let call;
|
|
47997
48158
|
try {
|
|
47998
|
-
|
|
47999
|
-
|
|
48000
|
-
|
|
48159
|
+
emit({ ...!active && !continuation ? { phase: "joining", chatId } : {}, error: null });
|
|
48160
|
+
const permission = (async () => {
|
|
48161
|
+
const value = continuation ? undefined : await mediaPort.prepare?.();
|
|
48162
|
+
try {
|
|
48163
|
+
check();
|
|
48164
|
+
} catch (error) {
|
|
48165
|
+
value?.close();
|
|
48166
|
+
throw error;
|
|
48167
|
+
}
|
|
48168
|
+
preparation = value;
|
|
48169
|
+
operation.preparation = value;
|
|
48170
|
+
step("permission");
|
|
48171
|
+
})();
|
|
48172
|
+
const destination = (async () => {
|
|
48173
|
+
if (!onlyExisting && !continuation)
|
|
48174
|
+
chatId = await chat.getSnapshot().materializeChat(chatId);
|
|
48175
|
+
check();
|
|
48176
|
+
joining.chatId = chatId;
|
|
48177
|
+
step("chat");
|
|
48178
|
+
const epochState = continuation ? chat.getSnapshot().getOwnerChat(chatId)?.epochState : await chat.getSnapshot().prepareCall(chatId);
|
|
48179
|
+
check();
|
|
48180
|
+
if (!epochState)
|
|
48181
|
+
throw cancelled2();
|
|
48182
|
+
const entry2 = await readDiscovery(chatId, epochState);
|
|
48183
|
+
check();
|
|
48184
|
+
step("discovery");
|
|
48185
|
+
return entry2;
|
|
48186
|
+
})();
|
|
48187
|
+
const [, entry] = await Promise.all([permission, destination]);
|
|
48001
48188
|
check();
|
|
48002
48189
|
const discoveryRecord = entry.mailbox.getSnapshot();
|
|
48003
|
-
step("discovery");
|
|
48004
48190
|
const create = !entry.descriptor;
|
|
48005
48191
|
if (expectedCallId && (onlyExisting || entry.epochState.manifest.lineage === "direct") && entry.descriptor?.callId !== expectedCallId)
|
|
48006
48192
|
throw ended();
|
|
48007
48193
|
const descriptor = entry.descriptor || {
|
|
48008
|
-
callId: toHex(randomBytes3(32)),
|
|
48194
|
+
callId: continuation?.callId || toHex(randomBytes3(32)),
|
|
48009
48195
|
secret: toHex(randomBytes3(32)),
|
|
48010
48196
|
mode: entry.epochState.manifest.lineage,
|
|
48011
48197
|
participants: 1,
|
|
48012
|
-
startedAt: Date.now()
|
|
48198
|
+
startedAt: continuation?.startedAt ?? Date.now()
|
|
48013
48199
|
};
|
|
48014
|
-
|
|
48200
|
+
if (continuation && descriptor.callId !== continuation.callId)
|
|
48201
|
+
throw ended();
|
|
48202
|
+
const callEndpoint = continuation?.endpoint || createCallEndpoint();
|
|
48015
48203
|
call = {
|
|
48016
48204
|
chatId,
|
|
48017
48205
|
callId: descriptor.callId,
|
|
@@ -48032,6 +48220,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48032
48220
|
muteTail: Promise.resolve()
|
|
48033
48221
|
};
|
|
48034
48222
|
joining.candidate = call;
|
|
48223
|
+
call.owner = continuation?.owner || createLease(identity, callEndpoint);
|
|
48035
48224
|
const protocol = createCallProtocol({ realm: cloud.environment, epochState: entry.epochState, identity, endpoint: callEndpoint, callId: descriptor.callId });
|
|
48036
48225
|
call.protocol = protocol;
|
|
48037
48226
|
const ownMember = (value) => {
|
|
@@ -48051,6 +48240,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48051
48240
|
if (!create)
|
|
48052
48241
|
cleanBytes(callMember.snapshot);
|
|
48053
48242
|
check();
|
|
48243
|
+
step("identity");
|
|
48054
48244
|
const admission = protocol.sign("join", { keyPackage: member.keyPackage ? encodeCallBytes(member.keyPackage) : "", signaturePK: toHex(member.signaturePK) });
|
|
48055
48245
|
const secret = fromHexBytes(descriptor.secret);
|
|
48056
48246
|
const capability = createCallCapability(secret, cloud.environment, "signaling", [descriptor.callId]);
|
|
@@ -48082,8 +48272,10 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48082
48272
|
};
|
|
48083
48273
|
call.media = createCallMediaSession({
|
|
48084
48274
|
port: mediaPort,
|
|
48275
|
+
preparation,
|
|
48085
48276
|
mode: descriptor.mode,
|
|
48086
48277
|
holder: callEndpoint.holder,
|
|
48278
|
+
diag,
|
|
48087
48279
|
provider: (...args) => call.owner.provider(...args),
|
|
48088
48280
|
signal: (...args) => call.room.signal(...args),
|
|
48089
48281
|
publish: (value) => call.room.publishMedia(value),
|
|
@@ -48098,6 +48290,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48098
48290
|
participants: call.participants.length
|
|
48099
48291
|
});
|
|
48100
48292
|
if (value.state === "connected") {
|
|
48293
|
+
if (state.phase !== "connected")
|
|
48294
|
+
step("connected");
|
|
48101
48295
|
clearTimeout(call.joinTimer);
|
|
48102
48296
|
call.joinTimer = null;
|
|
48103
48297
|
emit({ phase: "connected", transport: value.transport || (descriptor.mode === "group" ? "relay" : null) });
|
|
@@ -48194,7 +48388,8 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48194
48388
|
throw error;
|
|
48195
48389
|
}
|
|
48196
48390
|
checkCall();
|
|
48197
|
-
|
|
48391
|
+
if (!continuation)
|
|
48392
|
+
announce(chatId, descriptor.callId, "started");
|
|
48198
48393
|
} else {
|
|
48199
48394
|
await entry.mailbox.read();
|
|
48200
48395
|
checkCall();
|
|
@@ -48204,16 +48399,21 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48204
48399
|
throw Object.assign(new Error("the call room changed"), { code: "calls/discovery-conflict" });
|
|
48205
48400
|
}
|
|
48206
48401
|
}
|
|
48402
|
+
step("publication");
|
|
48207
48403
|
call.published = true;
|
|
48208
|
-
|
|
48209
|
-
|
|
48210
|
-
|
|
48211
|
-
|
|
48404
|
+
if (!continuation) {
|
|
48405
|
+
await stopCall();
|
|
48406
|
+
checkCall();
|
|
48407
|
+
mountLease(identity, callEndpoint, call.owner);
|
|
48408
|
+
participation = { chatId, callId: descriptor.callId, startedAt: descriptor.startedAt, endpoint: callEndpoint, owner: lease };
|
|
48409
|
+
}
|
|
48410
|
+
step("predecessor");
|
|
48411
|
+
call.owner = participation.owner;
|
|
48212
48412
|
active = call;
|
|
48213
48413
|
observeRoster(entry);
|
|
48214
48414
|
available();
|
|
48215
48415
|
emit({
|
|
48216
|
-
phase: state.elsewhere ? "transferring" : "joining",
|
|
48416
|
+
phase: continuation ? "connecting" : state.elsewhere ? "transferring" : "joining",
|
|
48217
48417
|
chatId,
|
|
48218
48418
|
callId: descriptor.callId,
|
|
48219
48419
|
mode: descriptor.mode,
|
|
@@ -48226,7 +48426,10 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48226
48426
|
if (preference)
|
|
48227
48427
|
call.media.setPeerAudio(peer.holder, { volume: preference.volume / 100, muted: preference.muted }).catch((error) => diag?.("call.peer.audio.error", { code: error?.code || "calls/audio" }));
|
|
48228
48428
|
}
|
|
48229
|
-
|
|
48429
|
+
if (continuation)
|
|
48430
|
+
await participation.owner.rotateMedia();
|
|
48431
|
+
else
|
|
48432
|
+
await lease.acquire({ chatId, callId: descriptor.callId });
|
|
48230
48433
|
check();
|
|
48231
48434
|
step("ownership");
|
|
48232
48435
|
if (!valid())
|
|
@@ -48286,6 +48489,11 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48286
48489
|
throw Object.assign(new Error("the call room is restarting"), { code: "calls/discovery-conflict" });
|
|
48287
48490
|
}
|
|
48288
48491
|
throw error;
|
|
48492
|
+
} finally {
|
|
48493
|
+
preflightActive = false;
|
|
48494
|
+
preparation?.close();
|
|
48495
|
+
if (operation.preparation === preparation)
|
|
48496
|
+
operation.preparation = null;
|
|
48289
48497
|
}
|
|
48290
48498
|
}
|
|
48291
48499
|
async function join(chatId, expectedCallId = null, onlyExisting = false) {
|
|
@@ -48294,21 +48502,31 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48294
48502
|
throw ended();
|
|
48295
48503
|
return chatId;
|
|
48296
48504
|
}
|
|
48505
|
+
if (!foreground)
|
|
48506
|
+
throw new Error("open veyl to join a call");
|
|
48507
|
+
return runJoin(chatId, expectedCallId, onlyExisting);
|
|
48508
|
+
}
|
|
48509
|
+
async function runJoin(chatId, expectedCallId = null, onlyExisting = false, continuation = null) {
|
|
48297
48510
|
const intent = ++joinIntent;
|
|
48298
|
-
const operation = { predecessor: active, chatId, candidate: null };
|
|
48511
|
+
const operation = { predecessor: active, chatId, candidate: null, continuation };
|
|
48299
48512
|
const previous = joining;
|
|
48300
48513
|
joining = operation;
|
|
48514
|
+
previous?.preparation?.close();
|
|
48301
48515
|
emit({ joiningChatId: chatId });
|
|
48302
48516
|
if (previous?.candidate && previous.candidate !== active)
|
|
48303
48517
|
disposeCall(previous.candidate);
|
|
48304
48518
|
try {
|
|
48305
48519
|
for (let attempt = 0;attempt < 3; attempt += 1) {
|
|
48306
48520
|
try {
|
|
48307
|
-
return await joinAttempt(chatId, intent, expectedCallId, onlyExisting);
|
|
48521
|
+
return await joinAttempt(chatId, intent, expectedCallId, onlyExisting, continuation);
|
|
48308
48522
|
} catch (error) {
|
|
48309
48523
|
if (intent !== joinIntent || !identity || closed)
|
|
48310
48524
|
throw cancelled2();
|
|
48311
48525
|
if (error?.code !== "calls/discovery-conflict" || attempt === 2) {
|
|
48526
|
+
if (continuation) {
|
|
48527
|
+
fail(error);
|
|
48528
|
+
throw error;
|
|
48529
|
+
}
|
|
48312
48530
|
const target = operation.chatId;
|
|
48313
48531
|
emit({ ...!active ? { phase: "error", chatId: target } : {}, error: callError(chatId, error) });
|
|
48314
48532
|
throw error;
|
|
@@ -48325,6 +48543,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48325
48543
|
}
|
|
48326
48544
|
function leave() {
|
|
48327
48545
|
joinIntent += 1;
|
|
48546
|
+
joining?.preparation?.close();
|
|
48328
48547
|
const candidate = joining?.candidate;
|
|
48329
48548
|
joining = null;
|
|
48330
48549
|
emit({ joiningChatId: null });
|
|
@@ -48339,6 +48558,7 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48339
48558
|
if (!operation)
|
|
48340
48559
|
return Promise.resolve();
|
|
48341
48560
|
joinIntent += 1;
|
|
48561
|
+
operation.preparation?.close();
|
|
48342
48562
|
joining = null;
|
|
48343
48563
|
emit({ joiningChatId: null });
|
|
48344
48564
|
const pending = operation.candidate && operation.candidate !== active ? disposeCall(operation.candidate) : null;
|
|
@@ -48348,6 +48568,14 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48348
48568
|
}
|
|
48349
48569
|
function stopCall() {
|
|
48350
48570
|
const call = active;
|
|
48571
|
+
const authority = participation;
|
|
48572
|
+
participation = null;
|
|
48573
|
+
if (!call && authority) {
|
|
48574
|
+
authority.owner.retire();
|
|
48575
|
+
const retired = [...retiring].filter((room) => room.endpoint === authority.endpoint);
|
|
48576
|
+
const released = Promise.all(retired.map((room) => room.stopped)).then(() => authority.owner.release());
|
|
48577
|
+
leaving = retired.length ? Promise.race([released, Promise.all(retired.map((room) => room.closing))]) : released;
|
|
48578
|
+
}
|
|
48351
48579
|
if (!call) {
|
|
48352
48580
|
emit({ phase: "idle", chatId: null, callId: null, mode: null, transport: null, participants: [], speaking: [] });
|
|
48353
48581
|
return leaving;
|
|
@@ -48367,7 +48595,9 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48367
48595
|
return;
|
|
48368
48596
|
try {
|
|
48369
48597
|
await entry.mailbox.commit(null, [], { expectedRevision: snapshot.revision });
|
|
48370
|
-
|
|
48598
|
+
if (chat.getSnapshot().getOwnerChat(call.chatId)?.epochState?.manifest.epochId === entry.epochState.manifest.epochId) {
|
|
48599
|
+
announce(call.chatId, call.callId, "ended");
|
|
48600
|
+
}
|
|
48371
48601
|
} catch (error) {
|
|
48372
48602
|
if (error?.code !== "calls/conflict")
|
|
48373
48603
|
throw error;
|
|
@@ -48387,11 +48617,13 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48387
48617
|
clearTimeout(call.timer);
|
|
48388
48618
|
clearTimeout(call.joinTimer);
|
|
48389
48619
|
const stopping = call.media?.close();
|
|
48390
|
-
call.
|
|
48620
|
+
call.stopped = Promise.resolve(stopping);
|
|
48621
|
+
const releasedOwner = participation?.endpoint === call.endpoint ? null : call.owner;
|
|
48622
|
+
releasedOwner?.retire();
|
|
48391
48623
|
const departing = call.room?.leave();
|
|
48392
48624
|
const draining = (async () => {
|
|
48393
48625
|
try {
|
|
48394
|
-
const [, departure] = await Promise.allSettled([Promise.resolve(stopping).then(() =>
|
|
48626
|
+
const [, departure] = await Promise.allSettled([Promise.resolve(stopping).then(() => releasedOwner?.release()), departing]);
|
|
48395
48627
|
if (departure.status === "fulfilled" && departure.value?.ended && call.published && call.discovery.descriptor?.callId === call.callId) {
|
|
48396
48628
|
await retireDiscovery(call);
|
|
48397
48629
|
}
|
|
@@ -48411,9 +48643,13 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48411
48643
|
call.room?.close();
|
|
48412
48644
|
call.protocol?.close();
|
|
48413
48645
|
cleanBytes(call.member?.snapshot);
|
|
48646
|
+
if (call.owner !== lease && call.owner !== participation?.owner)
|
|
48647
|
+
call.owner?.close();
|
|
48414
48648
|
if (call.endpoint !== leaseEndpoint)
|
|
48415
48649
|
call.endpoint.close();
|
|
48416
48650
|
retiring.delete(call);
|
|
48651
|
+
if (discoveries.get(call.chatId) !== call.discovery)
|
|
48652
|
+
closeDiscovery(call.discovery);
|
|
48417
48653
|
prune();
|
|
48418
48654
|
});
|
|
48419
48655
|
return call.closing;
|
|
@@ -48429,6 +48665,27 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48429
48665
|
return result;
|
|
48430
48666
|
}
|
|
48431
48667
|
async function setAudio(patch) {
|
|
48668
|
+
const value = normalizeCallAudio(patch, state);
|
|
48669
|
+
if (value.muted === state.muted && value.deafened === state.deafened && value.muted === audioSource.muted && value.deafened === audioSource.deafened)
|
|
48670
|
+
return;
|
|
48671
|
+
const token = generation;
|
|
48672
|
+
pendingAudio = value;
|
|
48673
|
+
const saving = Promise.resolve().then(() => {
|
|
48674
|
+
if (generation !== token)
|
|
48675
|
+
throw cancelled2();
|
|
48676
|
+
return saveAudio(value);
|
|
48677
|
+
}).then(() => {
|
|
48678
|
+
if (generation === token)
|
|
48679
|
+
audioSource = value;
|
|
48680
|
+
});
|
|
48681
|
+
try {
|
|
48682
|
+
await Promise.all([saving, applyAudioPreferences(value)]);
|
|
48683
|
+
} finally {
|
|
48684
|
+
if (generation === token && pendingAudio === value)
|
|
48685
|
+
pendingAudio = null;
|
|
48686
|
+
}
|
|
48687
|
+
}
|
|
48688
|
+
async function applyAudioPreferences(patch) {
|
|
48432
48689
|
emit(patch);
|
|
48433
48690
|
const calls = new Set([active, joining?.candidate].filter((call) => call?.media && call.room && !call.leaving));
|
|
48434
48691
|
await Promise.all([...calls].map((call) => applyAudio(call).catch((error) => {
|
|
@@ -48439,6 +48696,13 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48439
48696
|
throw error;
|
|
48440
48697
|
})));
|
|
48441
48698
|
}
|
|
48699
|
+
function syncAudio(source) {
|
|
48700
|
+
audioSource = normalizeCallAudio(source);
|
|
48701
|
+
const value = pendingAudio || audioSource;
|
|
48702
|
+
if (state.muted === value.muted && state.deafened === value.deafened)
|
|
48703
|
+
return;
|
|
48704
|
+
applyAudioPreferences(value).catch((error) => diag?.("call.audio.settings.error", { code: error?.code || "calls/audio" }));
|
|
48705
|
+
}
|
|
48442
48706
|
async function setPeerAudio(chatPK, patch) {
|
|
48443
48707
|
const call = active;
|
|
48444
48708
|
if (!identity || typeof chatPK !== "string" || !chatPK || chatPK === identity.chatPK)
|
|
@@ -48447,11 +48711,15 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48447
48711
|
const peer = call?.participants.find((peer2) => peer2.chatPK === chatPK);
|
|
48448
48712
|
const previous = state.peerAudio[chatPK];
|
|
48449
48713
|
const value = { volume: 100, muted: false, ...previous, ...patch };
|
|
48714
|
+
pendingPeerAudio.set(chatPK, value);
|
|
48450
48715
|
emit({ peerAudio: { ...state.peerAudio, [chatPK]: value } });
|
|
48451
48716
|
try {
|
|
48452
48717
|
if (peer)
|
|
48453
48718
|
await call.media.setPeerAudio(peer.holder, { volume: value.volume / 100, muted: value.muted });
|
|
48454
|
-
if (generation
|
|
48719
|
+
if (generation !== token || pendingPeerAudio.get(chatPK) !== value)
|
|
48720
|
+
return;
|
|
48721
|
+
await savePeerAudio(chatPK, value);
|
|
48722
|
+
if (generation === token)
|
|
48455
48723
|
confirmedPeerAudio.set(chatPK, value);
|
|
48456
48724
|
} catch (error) {
|
|
48457
48725
|
if (generation === token && state.peerAudio[chatPK] === value) {
|
|
@@ -48462,8 +48730,39 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48462
48730
|
else
|
|
48463
48731
|
delete peerAudio[chatPK];
|
|
48464
48732
|
emit({ peerAudio });
|
|
48733
|
+
if (peer && active === call)
|
|
48734
|
+
call.media.setPeerAudio(peer.holder, {
|
|
48735
|
+
volume: (confirmed?.volume ?? 100) / 100,
|
|
48736
|
+
muted: confirmed?.muted ?? false
|
|
48737
|
+
}).catch(() => {});
|
|
48465
48738
|
}
|
|
48466
48739
|
throw error;
|
|
48740
|
+
} finally {
|
|
48741
|
+
if (generation === token && pendingPeerAudio.get(chatPK) === value)
|
|
48742
|
+
pendingPeerAudio.delete(chatPK);
|
|
48743
|
+
}
|
|
48744
|
+
}
|
|
48745
|
+
function syncPeerAudio(source = {}) {
|
|
48746
|
+
if (source === peerAudioSource)
|
|
48747
|
+
return;
|
|
48748
|
+
peerAudioSource = source;
|
|
48749
|
+
const peerAudio = { ...source };
|
|
48750
|
+
for (const [chatPK, value] of pendingPeerAudio)
|
|
48751
|
+
peerAudio[chatPK] = value;
|
|
48752
|
+
confirmedPeerAudio.clear();
|
|
48753
|
+
for (const [chatPK, value] of Object.entries(source))
|
|
48754
|
+
confirmedPeerAudio.set(chatPK, value);
|
|
48755
|
+
const changed = new Set([...Object.keys(state.peerAudio), ...Object.keys(peerAudio)].filter((chatPK) => state.peerAudio[chatPK]?.volume !== peerAudio[chatPK]?.volume || state.peerAudio[chatPK]?.muted !== peerAudio[chatPK]?.muted));
|
|
48756
|
+
if (!changed.size)
|
|
48757
|
+
return;
|
|
48758
|
+
emit({ peerAudio });
|
|
48759
|
+
for (const call of new Set([active, joining?.candidate].filter((call2) => call2?.media && !call2.leaving))) {
|
|
48760
|
+
for (const peer of call.participants) {
|
|
48761
|
+
if (!changed.has(peer.chatPK))
|
|
48762
|
+
continue;
|
|
48763
|
+
const audio = peerAudio[peer.chatPK];
|
|
48764
|
+
call.media.setPeerAudio(peer.holder, { volume: (audio?.volume ?? 100) / 100, muted: audio?.muted ?? false }).catch((error) => diag?.("call.peer.audio.error", { code: error?.code || "calls/audio" }));
|
|
48765
|
+
}
|
|
48467
48766
|
}
|
|
48468
48767
|
}
|
|
48469
48768
|
async function setMicrophoneVolume(volume) {
|
|
@@ -48481,13 +48780,9 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48481
48780
|
throw error;
|
|
48482
48781
|
}
|
|
48483
48782
|
}
|
|
48484
|
-
function
|
|
48485
|
-
lease?.close();
|
|
48486
|
-
if (leaseEndpoint && leaseEndpoint !== endpoint && leaseEndpoint !== callEndpoint)
|
|
48487
|
-
leaseEndpoint.close();
|
|
48488
|
-
leaseEndpoint = callEndpoint;
|
|
48783
|
+
function createLease(session, callEndpoint) {
|
|
48489
48784
|
const ownsLease = () => identity === session && leaseEndpoint === callEndpoint;
|
|
48490
|
-
|
|
48785
|
+
return openAccountCallLease({
|
|
48491
48786
|
cloud,
|
|
48492
48787
|
capability: session.callIdentity,
|
|
48493
48788
|
endpoint: callEndpoint,
|
|
@@ -48512,9 +48807,22 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48512
48807
|
}
|
|
48513
48808
|
});
|
|
48514
48809
|
}
|
|
48515
|
-
function
|
|
48516
|
-
|
|
48810
|
+
function mountLease(session, callEndpoint = endpoint, prepared = null) {
|
|
48811
|
+
lease?.close();
|
|
48812
|
+
if (leaseEndpoint && leaseEndpoint !== endpoint && leaseEndpoint !== callEndpoint)
|
|
48813
|
+
leaseEndpoint.close();
|
|
48814
|
+
leaseEndpoint = callEndpoint;
|
|
48815
|
+
lease = prepared || createLease(session, callEndpoint);
|
|
48816
|
+
}
|
|
48817
|
+
function setSources(session, settings = {}) {
|
|
48818
|
+
const peerAudio = settings.peerAudio || {};
|
|
48819
|
+
if (identity === session) {
|
|
48820
|
+
if (session) {
|
|
48821
|
+
syncPeerAudio(peerAudio);
|
|
48822
|
+
syncAudio(settings.callAudio);
|
|
48823
|
+
}
|
|
48517
48824
|
return drainingSources;
|
|
48825
|
+
}
|
|
48518
48826
|
const token = ++generation;
|
|
48519
48827
|
const previousLease = lease;
|
|
48520
48828
|
const previousEndpoint = endpoint;
|
|
@@ -48546,7 +48854,13 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48546
48854
|
if (generation !== token || identity !== session)
|
|
48547
48855
|
return retirement;
|
|
48548
48856
|
confirmedPeerAudio.clear();
|
|
48549
|
-
|
|
48857
|
+
pendingPeerAudio.clear();
|
|
48858
|
+
pendingAudio = null;
|
|
48859
|
+
audioSource = normalizeCallAudio(settings.callAudio);
|
|
48860
|
+
peerAudioSource = session ? peerAudio : null;
|
|
48861
|
+
for (const [chatPK, value] of Object.entries(peerAudioSource || {}))
|
|
48862
|
+
confirmedPeerAudio.set(chatPK, value);
|
|
48863
|
+
emit({ ...initial(), ...session ? audioSource : {}, peerAudio: peerAudioSource || {} });
|
|
48550
48864
|
available();
|
|
48551
48865
|
if (generation !== token || identity !== session)
|
|
48552
48866
|
return retirement;
|
|
@@ -48567,10 +48881,35 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48567
48881
|
if (!identity)
|
|
48568
48882
|
return;
|
|
48569
48883
|
for (const [chatId, entry] of discoveries) {
|
|
48570
|
-
const
|
|
48571
|
-
if (
|
|
48884
|
+
const epochState = chat.getSnapshot().getOwnerChat(chatId)?.epochState;
|
|
48885
|
+
if (epochState?.manifest.epochId === entry.epochState.manifest.epochId)
|
|
48572
48886
|
continue;
|
|
48573
|
-
|
|
48887
|
+
const retained = epochState?.manifest.members.some((member) => member.chatSigningPK === identity.chatSigningPK);
|
|
48888
|
+
if (participation?.chatId === chatId && retained) {
|
|
48889
|
+
const continuation = participation;
|
|
48890
|
+
const intent = ++joinIntent;
|
|
48891
|
+
const previous = active || joining?.candidate;
|
|
48892
|
+
active = null;
|
|
48893
|
+
joining = null;
|
|
48894
|
+
if (previous)
|
|
48895
|
+
disposeCall(previous);
|
|
48896
|
+
const members = new Set(epochState.manifest.members.map((member) => member.chatPK));
|
|
48897
|
+
emit({
|
|
48898
|
+
phase: "connecting",
|
|
48899
|
+
joiningChatId: chatId,
|
|
48900
|
+
speaking: [],
|
|
48901
|
+
participants: state.participants.filter((member) => members.has(member.chatPK))
|
|
48902
|
+
});
|
|
48903
|
+
diag?.("call.epoch.transition", { stage: "starting", participants: state.participants.length });
|
|
48904
|
+
Promise.resolve(previous?.stopped).then(() => {
|
|
48905
|
+
if (intent !== joinIntent || participation !== continuation)
|
|
48906
|
+
return;
|
|
48907
|
+
return runJoin(chatId, null, false, continuation);
|
|
48908
|
+
}).catch((error) => {
|
|
48909
|
+
if (intent === joinIntent && participation === continuation)
|
|
48910
|
+
fail(error);
|
|
48911
|
+
});
|
|
48912
|
+
} else if (active?.chatId === chatId || participation?.chatId === chatId)
|
|
48574
48913
|
leave();
|
|
48575
48914
|
else if (joining?.chatId === chatId)
|
|
48576
48915
|
cancelJoin();
|
|
@@ -48629,8 +48968,6 @@ function createCallSession({ cloud, chat, media: mediaPort, mls, diag }) {
|
|
|
48629
48968
|
},
|
|
48630
48969
|
setForeground(value) {
|
|
48631
48970
|
foreground = value === true;
|
|
48632
|
-
if (!foreground && joining)
|
|
48633
|
-
cancelJoin();
|
|
48634
48971
|
},
|
|
48635
48972
|
setOnline(value) {
|
|
48636
48973
|
const reconnecting = !online && value === true;
|
|
@@ -51652,15 +51989,17 @@ var DEFAULT_BITCOIN = Object.freeze({
|
|
|
51652
51989
|
ready: false,
|
|
51653
51990
|
error: null
|
|
51654
51991
|
});
|
|
51655
|
-
function normalizeBitcoinData(data, current = DEFAULT_BITCOIN
|
|
51992
|
+
function normalizeBitcoinData(data, current = DEFAULT_BITCOIN) {
|
|
51656
51993
|
if (!data)
|
|
51657
51994
|
return current;
|
|
51995
|
+
const observedAt = timestampMs(data.priceUpdatedAt, null, { positive: true });
|
|
51996
|
+
const usePrice = hasBitcoinPrice(data.price) && observedAt != null && observedAt >= (current.priceUpdatedAt ?? 0);
|
|
51658
51997
|
return {
|
|
51659
|
-
price:
|
|
51660
|
-
priceUpdatedAt:
|
|
51661
|
-
priceFromCache:
|
|
51998
|
+
price: usePrice ? data.price : current.price,
|
|
51999
|
+
priceUpdatedAt: usePrice ? observedAt : current.priceUpdatedAt,
|
|
52000
|
+
priceFromCache: usePrice ? false : current.priceFromCache,
|
|
51662
52001
|
block: data.block ?? current?.block ?? null,
|
|
51663
|
-
fees: data.fees ??
|
|
52002
|
+
fees: data.fees ?? null,
|
|
51664
52003
|
updatedAt: data.updatedAt ?? current?.updatedAt ?? null,
|
|
51665
52004
|
ready: true,
|
|
51666
52005
|
error: null
|
|
@@ -51710,20 +52049,15 @@ function createBitcoin({ cloud, priceStorage = null, diag = null }) {
|
|
|
51710
52049
|
const priceUpdatedAt = timestampMs(cached.updatedAt, null, { positive: true });
|
|
51711
52050
|
if (!priceUpdatedAt)
|
|
51712
52051
|
return;
|
|
51713
|
-
if (bitcoin.priceUpdatedAt != null &&
|
|
52052
|
+
if (bitcoin.priceUpdatedAt != null && bitcoin.priceUpdatedAt >= priceUpdatedAt)
|
|
51714
52053
|
return;
|
|
51715
52054
|
publish({ ...bitcoin, price: cached.price, priceUpdatedAt, priceFromCache: true });
|
|
51716
52055
|
}).catch((error) => diag?.("bitcoin.price.cache.read.error", { code: error?.code || "" }));
|
|
51717
|
-
unsubscribe = cloud.bitcoin.watch((data
|
|
51718
|
-
if (session !== generation
|
|
52056
|
+
unsubscribe = cloud.bitcoin.watch((data) => {
|
|
52057
|
+
if (session !== generation)
|
|
51719
52058
|
return;
|
|
51720
|
-
const next = normalizeBitcoinData(data, bitcoin
|
|
51721
|
-
if (
|
|
51722
|
-
next.price = bitcoin.price;
|
|
51723
|
-
next.priceUpdatedAt = bitcoin.priceUpdatedAt;
|
|
51724
|
-
next.priceFromCache = bitcoin.priceFromCache;
|
|
51725
|
-
}
|
|
51726
|
-
if (priceStorage && hasBitcoinPrice(data?.price) && !info.fromCache && (next.price !== bitcoin.price || next.priceUpdatedAt !== bitcoin.priceUpdatedAt || bitcoin.priceFromCache)) {
|
|
52059
|
+
const next = normalizeBitcoinData(data, bitcoin);
|
|
52060
|
+
if (priceStorage && next.priceUpdatedAt != null && !next.priceFromCache && (next.price !== bitcoin.price || next.priceUpdatedAt !== bitcoin.priceUpdatedAt || bitcoin.priceFromCache)) {
|
|
51727
52061
|
const cached = { price: next.price, updatedAt: next.priceUpdatedAt };
|
|
51728
52062
|
priceWrite = priceWrite.catch(NOOP3).then(() => priceStorage.write(cached));
|
|
51729
52063
|
priceWrite.catch((error) => diag?.("bitcoin.price.cache.write.error", { code: error?.code || "" }));
|
|
@@ -52854,8 +53188,9 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
|
|
|
52854
53188
|
async function fetchProfileByUid(uid) {
|
|
52855
53189
|
if (!uid)
|
|
52856
53190
|
return null;
|
|
52857
|
-
|
|
52858
|
-
|
|
53191
|
+
const cached = profileCache.get(uid);
|
|
53192
|
+
if (isFullProfile(cached))
|
|
53193
|
+
return cached;
|
|
52859
53194
|
try {
|
|
52860
53195
|
const record = await cloud.peer.read(uid);
|
|
52861
53196
|
if (!record)
|
|
@@ -52903,11 +53238,11 @@ function createPeersApi({ cloud, network, avatarCache = null }) {
|
|
|
52903
53238
|
return null;
|
|
52904
53239
|
if (field === "walletPK" && walletToUid.has(value)) {
|
|
52905
53240
|
const uid = walletToUid.get(value);
|
|
52906
|
-
return
|
|
53241
|
+
return fetchProfileByUid(uid);
|
|
52907
53242
|
}
|
|
52908
53243
|
if (field === "chatPK" && chatToUid.has(value)) {
|
|
52909
53244
|
const uid = chatToUid.get(value);
|
|
52910
|
-
return
|
|
53245
|
+
return fetchProfileByUid(uid);
|
|
52911
53246
|
}
|
|
52912
53247
|
try {
|
|
52913
53248
|
const record = field === "walletPK" ? await cloud.search.peer.byWalletPK(value, { network: walletNetwork }) : field === "chatPK" ? await cloud.search.peer.byChatPK(value) : await cloud.search.peer.byUsername(value);
|
|
@@ -60309,7 +60644,14 @@ function openAccount(options = {}) {
|
|
|
60309
60644
|
maintenance: messageMaintenance,
|
|
60310
60645
|
resolveActiveCall: (chatId) => calls.resolveActiveCall(chatId)
|
|
60311
60646
|
}, chatSources());
|
|
60312
|
-
const calls = createCallSession({
|
|
60647
|
+
const calls = createCallSession({
|
|
60648
|
+
cloud,
|
|
60649
|
+
chat,
|
|
60650
|
+
...options.calls,
|
|
60651
|
+
diag,
|
|
60652
|
+
savePeerAudio: (chatPK, audio) => user.getSnapshot().updateSettings({ peerAudio: { [chatPK]: audio } }),
|
|
60653
|
+
saveAudio: (callAudio) => user.getSnapshot().updateSettings({ callAudio })
|
|
60654
|
+
});
|
|
60313
60655
|
const wallet = openWallet({
|
|
60314
60656
|
...options.wallet || {},
|
|
60315
60657
|
network: state.network,
|
|
@@ -60383,7 +60725,7 @@ function openAccount(options = {}) {
|
|
|
60383
60725
|
syncPresence();
|
|
60384
60726
|
syncPeers();
|
|
60385
60727
|
calls.setOnline(state.online);
|
|
60386
|
-
calls.setSources(state.lockState === "unlocked" ? state.session : null);
|
|
60728
|
+
calls.setSources(state.lockState === "unlocked" ? state.session : null, state.user?.settings);
|
|
60387
60729
|
}
|
|
60388
60730
|
function syncPresence() {
|
|
60389
60731
|
const session = state.session;
|