Add more timestamp fallbacks for call migration

This commit is contained in:
Jamie Kyle 2023-08-29 16:31:45 -07:00 committed by GitHub
parent bc74a696f4
commit eae9e570fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 10 deletions

View file

@ -37,7 +37,7 @@ type GroupCallHistoryDetailsType = {
callMode: CallMode.Group; callMode: CallMode.Group;
creatorUuid: string; creatorUuid: string;
eraId: string; eraId: string;
startedTime: number; startedTime?: number; // Treat this as optional, some calls may be missing it
}; };
export type CallHistoryDetailsType = export type CallHistoryDetailsType =
| DirectCallHistoryDetailsType | DirectCallHistoryDetailsType
@ -113,7 +113,8 @@ function convertLegacyCallDetails(
ourUuid: string | undefined, ourUuid: string | undefined,
peerId: string, peerId: string,
message: MessageType, message: MessageType,
partialDetails: CallHistoryDetailsFromDiskType partialDetails: CallHistoryDetailsFromDiskType,
logger: LoggerType
): CallHistoryDetails { ): CallHistoryDetails {
const details = upcastCallHistoryDetailsFromDiskType(partialDetails); const details = upcastCallHistoryDetailsFromDiskType(partialDetails);
const { callMode: mode } = details; const { callMode: mode } = details;
@ -127,6 +128,10 @@ function convertLegacyCallDetails(
strictAssert(mode != null, 'mode must exist'); strictAssert(mode != null, 'mode must exist');
// If we cannot find any timestamp on the message, we'll use 0
const fallbackTimestamp =
message.timestamp ?? message.sent_at ?? message.received_at_ms ?? 0;
if (mode === CallMode.Direct) { if (mode === CallMode.Direct) {
// We don't have a callId for older calls, generating a uuid instead // We don't have a callId for older calls, generating a uuid instead
callId = details.callId ?? generateUuid(); callId = details.callId ?? generateUuid();
@ -141,7 +146,7 @@ function convertLegacyCallDetails(
? DirectCallStatus.Declined ? DirectCallStatus.Declined
: DirectCallStatus.Missed; : DirectCallStatus.Missed;
} }
timestamp = details.acceptedTime ?? details.endedTime ?? message.timestamp; timestamp = details.acceptedTime ?? details.endedTime ?? fallbackTimestamp;
} else if (mode === CallMode.Group) { } else if (mode === CallMode.Group) {
callId = Long.fromValue(callIdFromEra(details.eraId)).toString(); callId = Long.fromValue(callIdFromEra(details.eraId)).toString();
type = CallType.Group; type = CallType.Group;
@ -150,7 +155,7 @@ function convertLegacyCallDetails(
? CallDirection.Outgoing ? CallDirection.Outgoing
: CallDirection.Incoming; : CallDirection.Incoming;
status = GroupCallStatus.GenericGroupCall; status = GroupCallStatus.GenericGroupCall;
timestamp = details.startedTime; timestamp = details.startedTime ?? fallbackTimestamp;
ringerId = details.creatorUuid; ringerId = details.creatorUuid;
} else { } else {
throw missingCaseError(mode); throw missingCaseError(mode);
@ -167,7 +172,16 @@ function convertLegacyCallDetails(
timestamp, timestamp,
}; };
return callHistoryDetailsSchema.parse(callHistory); const result = callHistoryDetailsSchema.safeParse(callHistory);
if (result.success) {
return result.data;
}
logger.error(
`convertLegacyCallDetails: Could not convert ${mode} call`,
result.error.toString()
);
throw new Error(`Failed to convert legacy ${mode} call details`);
} }
export default function updateToSchemaVersion89( export default function updateToSchemaVersion89(
@ -265,7 +279,8 @@ export default function updateToSchemaVersion89(
ourUuid, ourUuid,
peerId, peerId,
message, message,
details details,
logger
); );
const [insertQuery, insertParams] = sql` const [insertQuery, insertParams] = sql`

View file

@ -41,6 +41,7 @@ describe('SQL/updateToSchemaVersion89', () => {
callId: string | null; callId: string | null;
noCallMode?: boolean; noCallMode?: boolean;
wasDeclined?: boolean; wasDeclined?: boolean;
noTimestamps?: boolean;
}): CallHistoryDetailsFromDiskType { }): CallHistoryDetailsFromDiskType {
return { return {
callId: options.callId ?? undefined, callId: options.callId ?? undefined,
@ -56,27 +57,42 @@ describe('SQL/updateToSchemaVersion89', () => {
function getGroupCallHistoryDetails(options: { function getGroupCallHistoryDetails(options: {
eraId: string; eraId: string;
noCallMode?: boolean; noCallMode?: boolean;
noTimestamps?: boolean;
}): CallHistoryDetailsFromDiskType { }): CallHistoryDetailsFromDiskType {
return { return {
eraId: options.eraId, eraId: options.eraId,
callMode: options.noCallMode ? undefined : CallMode.Group, callMode: options.noCallMode ? undefined : CallMode.Group,
creatorUuid: generateGuid(), creatorUuid: generateGuid(),
startedTime: Date.now(), startedTime: options.noTimestamps ? undefined : Date.now(),
}; };
} }
type Timestamps = Pick<
MessageWithCallHistoryDetails,
'sent_at' | 'received_at_ms' | 'timestamp'
>;
function createCallHistoryMessage(options: { function createCallHistoryMessage(options: {
messageId: string; messageId: string;
conversationId: string; conversationId: string;
callHistoryDetails: CallHistoryDetailsFromDiskType; callHistoryDetails: CallHistoryDetailsFromDiskType;
timestamps?: Partial<Timestamps>;
}): MessageWithCallHistoryDetails { }): MessageWithCallHistoryDetails {
// @ts-expect-error Purposefully violating the type to test the migration
const timestamps: Timestamps = options.timestamps
? options.timestamps
: {
sent_at: Date.now(),
received_at_ms: Date.now(),
timestamp: Date.now(),
};
const message: MessageWithCallHistoryDetails = { const message: MessageWithCallHistoryDetails = {
id: options.messageId, id: options.messageId,
type: 'call-history', type: 'call-history',
conversationId: options.conversationId, conversationId: options.conversationId,
sent_at: Date.now() - 10, received_at: Date.now(),
received_at: Date.now() - 10, ...timestamps,
timestamp: Date.now() - 10,
callHistoryDetails: options.callHistoryDetails, callHistoryDetails: options.callHistoryDetails,
}; };
@ -300,6 +316,69 @@ describe('SQL/updateToSchemaVersion89', () => {
assert.strictEqual(callHistory[0].peerId, conversation.id); assert.strictEqual(callHistory[0].peerId, conversation.id);
}); });
it('migrates call-history messages with no timestamp', () => {
updateToVersion(db, 88);
const conversation = createConversation('private', Date.now());
const timestampCases = {
noTimestamps: {
sent_at: undefined,
received_at_ms: undefined,
timestamp: undefined,
},
onlyTimestamp: {
sent_at: undefined,
received_at_ms: undefined,
timestamp: 1,
},
onlySentAt: {
sent_at: 2,
received_at_ms: undefined,
timestamp: undefined,
},
onlyReceivedAt: {
sent_at: undefined,
received_at_ms: 3,
timestamp: undefined,
},
} satisfies Record<string, Partial<Timestamps>>;
for (const [id, timestamps] of Object.entries(timestampCases)) {
createCallHistoryMessage({
messageId: generateGuid(),
conversationId: conversation.id,
callHistoryDetails: getDirectCallHistoryDetails({
callId: id,
noTimestamps: true,
}),
timestamps,
});
createCallHistoryMessage({
messageId: generateGuid(),
conversationId: conversation.id,
callHistoryDetails: getGroupCallHistoryDetails({
eraId: id,
noTimestamps: true,
}),
timestamps,
});
}
updateToVersion(db, 89);
const callHistory = getAllCallHistory();
assert.strictEqual(callHistory.length, 8);
assert.strictEqual(callHistory[0].timestamp, 0);
assert.strictEqual(callHistory[1].timestamp, 0);
assert.strictEqual(callHistory[2].timestamp, 1);
assert.strictEqual(callHistory[3].timestamp, 1);
assert.strictEqual(callHistory[4].timestamp, 2);
assert.strictEqual(callHistory[5].timestamp, 2);
assert.strictEqual(callHistory[6].timestamp, 3);
assert.strictEqual(callHistory[7].timestamp, 3);
});
describe('clients with schema version 87', () => { describe('clients with schema version 87', () => {
function createCallHistoryTable() { function createCallHistoryTable() {
const [query] = sql` const [query] = sql`