Cleanup stale ringing calls
This commit is contained in:
parent
a751eab67d
commit
e69826dcc6
4 changed files with 200 additions and 2 deletions
|
@ -351,7 +351,8 @@ export class CallingClass {
|
|||
reduxInterface.setPresenting();
|
||||
});
|
||||
|
||||
void this.cleanExpiredGroupCallRingsAndLoop();
|
||||
drop(this.cleanExpiredGroupCallRingsAndLoop());
|
||||
drop(this.cleanupStaleRingingCalls());
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
drop(this.enumerateMediaDevices());
|
||||
|
@ -593,6 +594,27 @@ export class CallingClass {
|
|||
);
|
||||
}
|
||||
|
||||
public async cleanupStaleRingingCalls(): Promise<void> {
|
||||
const calls = await dataInterface.getRecentStaleRingsAndMarkOlderMissed();
|
||||
|
||||
const results = await Promise.all(
|
||||
calls.map(async call => {
|
||||
const peekInfo = await this.peekGroupCall(call.peerId);
|
||||
return { callId: call.callId, peekInfo };
|
||||
})
|
||||
);
|
||||
|
||||
const staleCallIds = results
|
||||
.filter(result => {
|
||||
return result.peekInfo == null;
|
||||
})
|
||||
.map(result => {
|
||||
return result.callId;
|
||||
});
|
||||
|
||||
await dataInterface.markCallHistoryMissed(staleCallIds);
|
||||
}
|
||||
|
||||
public async peekGroupCall(conversationId: string): Promise<PeekInfo> {
|
||||
// This can be undefined in two cases:
|
||||
//
|
||||
|
|
|
@ -19,7 +19,10 @@ import type { BadgeType } from '../badges/types';
|
|||
import type { LoggerType } from '../types/Logging';
|
||||
import type { ReadStatus } from '../messages/MessageReadStatus';
|
||||
import type { RawBodyRange } from '../types/BodyRange';
|
||||
import type { GetMessagesBetweenOptions } from './Server';
|
||||
import type {
|
||||
GetMessagesBetweenOptions,
|
||||
MaybeStaleCallHistory,
|
||||
} from './Server';
|
||||
import type { MessageTimestamps } from '../state/ducks/conversations';
|
||||
import type {
|
||||
CallHistoryDetails,
|
||||
|
@ -665,6 +668,10 @@ export type DataInterface = {
|
|||
conversationId: string,
|
||||
eraId: string
|
||||
) => Promise<boolean>;
|
||||
markCallHistoryMissed(callIds: ReadonlyArray<string>): Promise<void>;
|
||||
getRecentStaleRingsAndMarkOlderMissed(): Promise<
|
||||
ReadonlyArray<MaybeStaleCallHistory>
|
||||
>;
|
||||
migrateConversationMessages: (
|
||||
obsoleteId: string,
|
||||
currentId: string
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
map,
|
||||
mapValues,
|
||||
omit,
|
||||
partition,
|
||||
pick,
|
||||
} from 'lodash';
|
||||
|
||||
|
@ -157,6 +158,8 @@ import {
|
|||
CallHistoryFilterStatus,
|
||||
callHistoryDetailsSchema,
|
||||
CallDirection,
|
||||
GroupCallStatus,
|
||||
CallType,
|
||||
} from '../types/CallDisposition';
|
||||
|
||||
type ConversationRow = Readonly<{
|
||||
|
@ -316,6 +319,8 @@ const dataInterface: ServerInterface = {
|
|||
getCallHistoryGroups,
|
||||
saveCallHistory,
|
||||
hasGroupCallHistoryMessage,
|
||||
markCallHistoryMissed,
|
||||
getRecentStaleRingsAndMarkOlderMissed,
|
||||
migrateConversationMessages,
|
||||
getMessagesBetween,
|
||||
getNearbyMessageFromDeletedSet,
|
||||
|
@ -3802,6 +3807,65 @@ async function hasGroupCallHistoryMessage(
|
|||
return exists !== 0;
|
||||
}
|
||||
|
||||
function _markCallHistoryMissed(db: Database, callIds: ReadonlyArray<string>) {
|
||||
batchMultiVarQuery(db, callIds, batch => {
|
||||
const [updateQuery, updateParams] = sql`
|
||||
UPDATE callsHistory
|
||||
SET status = ${sqlConstant(GroupCallStatus.Missed)}
|
||||
WHERE callId IN (${sqlJoin(batch)})
|
||||
`;
|
||||
return db.prepare(updateQuery).run(updateParams);
|
||||
});
|
||||
}
|
||||
|
||||
async function markCallHistoryMissed(
|
||||
callIds: ReadonlyArray<string>
|
||||
): Promise<void> {
|
||||
const db = await getWritableInstance();
|
||||
return db.transaction(() => _markCallHistoryMissed(db, callIds))();
|
||||
}
|
||||
|
||||
export type MaybeStaleCallHistory = Readonly<
|
||||
Pick<CallHistoryDetails, 'callId' | 'peerId'>
|
||||
>;
|
||||
|
||||
async function getRecentStaleRingsAndMarkOlderMissed(): Promise<
|
||||
ReadonlyArray<MaybeStaleCallHistory>
|
||||
> {
|
||||
const db = await getWritableInstance();
|
||||
return db.transaction(() => {
|
||||
const [selectQuery, selectParams] = sql`
|
||||
SELECT callId, peerId FROM callsHistory
|
||||
WHERE
|
||||
type = ${sqlConstant(CallType.Group)} AND
|
||||
status = ${sqlConstant(GroupCallStatus.Ringing)}
|
||||
ORDER BY timestamp DESC
|
||||
`;
|
||||
|
||||
const ringingCalls = db.prepare(selectQuery).all(selectParams);
|
||||
|
||||
const seen = new Set<string>();
|
||||
const [latestCalls, pastCalls] = partition(ringingCalls, result => {
|
||||
if (seen.size >= 10) {
|
||||
return false;
|
||||
}
|
||||
if (seen.has(result.peerId)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(result.peerId);
|
||||
return true;
|
||||
});
|
||||
|
||||
_markCallHistoryMissed(
|
||||
db,
|
||||
pastCalls.map(result => result.callId)
|
||||
);
|
||||
|
||||
// These are returned so we can peek them.
|
||||
return latestCalls;
|
||||
})();
|
||||
}
|
||||
|
||||
async function migrateConversationMessages(
|
||||
obsoleteId: string,
|
||||
currentId: string
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { v4 as generateUuid } from 'uuid';
|
||||
|
||||
import { times } from 'lodash';
|
||||
import dataInterface from '../../sql/Client';
|
||||
|
||||
import { CallMode } from '../../types/Calling';
|
||||
import { generateAci } from '../../types/ServiceId';
|
||||
import type { CallHistoryDetails } from '../../types/CallDisposition';
|
||||
import {
|
||||
CallDirection,
|
||||
CallType,
|
||||
GroupCallStatus,
|
||||
} from '../../types/CallDisposition';
|
||||
import type { MaybeStaleCallHistory } from '../../sql/Server';
|
||||
|
||||
const {
|
||||
removeAll,
|
||||
getRecentStaleRingsAndMarkOlderMissed,
|
||||
saveCallHistory,
|
||||
getAllCallHistory,
|
||||
} = dataInterface;
|
||||
|
||||
describe('sql/getRecentStaleRingsAndMarkOlderMissed', () => {
|
||||
beforeEach(async () => {
|
||||
await removeAll();
|
||||
});
|
||||
|
||||
const now = Date.now();
|
||||
let offset = 0;
|
||||
|
||||
async function makeCall(
|
||||
peerId: string,
|
||||
callId: string,
|
||||
status: GroupCallStatus
|
||||
) {
|
||||
const timestamp = now + offset;
|
||||
offset += 1;
|
||||
const call: CallHistoryDetails = {
|
||||
callId,
|
||||
peerId,
|
||||
ringerId: generateAci(),
|
||||
mode: CallMode.Group,
|
||||
type: CallType.Group,
|
||||
direction: CallDirection.Incoming,
|
||||
timestamp,
|
||||
status,
|
||||
};
|
||||
await saveCallHistory(call);
|
||||
return call;
|
||||
}
|
||||
|
||||
function toMissed(call: CallHistoryDetails) {
|
||||
return { ...call, status: GroupCallStatus.Missed };
|
||||
}
|
||||
|
||||
function toMaybeStale(call: CallHistoryDetails): MaybeStaleCallHistory {
|
||||
return { callId: call.callId, peerId: call.peerId };
|
||||
}
|
||||
|
||||
it('should mark every call but the latest with the same peer as missed', async () => {
|
||||
const peer1 = generateUuid();
|
||||
const peer2 = generateUuid();
|
||||
const call1 = await makeCall(peer1, '1', GroupCallStatus.Ringing);
|
||||
const call2 = await makeCall(peer1, '2', GroupCallStatus.Ringing);
|
||||
const call3 = await makeCall(peer2, '3', GroupCallStatus.Ringing);
|
||||
const call4 = await makeCall(peer2, '4', GroupCallStatus.Ringing);
|
||||
const callsToCheck = await getRecentStaleRingsAndMarkOlderMissed();
|
||||
const callHistory = await getAllCallHistory();
|
||||
assert.deepEqual(callHistory, [
|
||||
toMissed(call1),
|
||||
call2, // latest peer1
|
||||
toMissed(call3),
|
||||
call4, // latest peer2
|
||||
]);
|
||||
assert.deepEqual(callsToCheck, [
|
||||
// in order of timestamp
|
||||
toMaybeStale(call4),
|
||||
toMaybeStale(call2),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should mark every ringing call after the first 10 as missed', async () => {
|
||||
const calls = await Promise.all(
|
||||
times(15, async i => {
|
||||
return makeCall(generateUuid(), String(i), GroupCallStatus.Ringing);
|
||||
})
|
||||
);
|
||||
|
||||
const callsToCheck = await getRecentStaleRingsAndMarkOlderMissed();
|
||||
const callHistory = await getAllCallHistory();
|
||||
assert.deepEqual(callHistory, [
|
||||
// first 10 are not missed
|
||||
...calls.slice(0, -10).map(toMissed),
|
||||
...calls.slice(-10),
|
||||
]);
|
||||
assert.deepEqual(
|
||||
callsToCheck,
|
||||
calls.slice(-10).map(toMaybeStale).reverse()
|
||||
);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue