Fix handling CallLogEvent sync for call link targets
Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com>
This commit is contained in:
parent
3d1e6cc3c3
commit
b94bf9d88f
4 changed files with 136 additions and 71 deletions
|
@ -3568,7 +3568,7 @@ function clearCallHistory(
|
||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
const callHistory = getCallHistoryForCallLogEventTarget(db, target);
|
const callHistory = getCallHistoryForCallLogEventTarget(db, target);
|
||||||
if (callHistory == null) {
|
if (callHistory == null) {
|
||||||
logger.error('clearCallHistory: Target call not found');
|
logger.warn('clearCallHistory: Target call not found');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const { timestamp } = callHistory;
|
const { timestamp } = callHistory;
|
||||||
|
@ -3750,7 +3750,10 @@ function getCallHistoryForCallLogEventTarget(
|
||||||
db: ReadableDB,
|
db: ReadableDB,
|
||||||
target: CallLogEventTarget
|
target: CallLogEventTarget
|
||||||
): CallHistoryDetails | null {
|
): CallHistoryDetails | null {
|
||||||
const { callId, peerId, timestamp } = target;
|
const { callId, timestamp } = target;
|
||||||
|
|
||||||
|
if ('peerId' in target) {
|
||||||
|
const { peerId } = target;
|
||||||
|
|
||||||
let row: unknown;
|
let row: unknown;
|
||||||
|
|
||||||
|
@ -3790,6 +3793,35 @@ function getCallHistoryForCallLogEventTarget(
|
||||||
return parseUnknown(callHistoryDetailsSchema, row as unknown);
|
return parseUnknown(callHistoryDetailsSchema, row as unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For incoming CallLogEvent sync messages, peerId is ambiguous whether it
|
||||||
|
// refers to conversation or call link.
|
||||||
|
if ('peerIdAsConversationId' in target && 'peerIdAsRoomId' in target) {
|
||||||
|
const resultForConversation = getCallHistoryForCallLogEventTarget(db, {
|
||||||
|
callId,
|
||||||
|
timestamp,
|
||||||
|
peerId: target.peerIdAsConversationId,
|
||||||
|
});
|
||||||
|
if (resultForConversation) {
|
||||||
|
return resultForConversation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resultForCallLink = getCallHistoryForCallLogEventTarget(db, {
|
||||||
|
callId,
|
||||||
|
timestamp,
|
||||||
|
peerId: target.peerIdAsRoomId,
|
||||||
|
});
|
||||||
|
if (resultForCallLink) {
|
||||||
|
return resultForCallLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
'Either peerId, or peerIdAsConversationId and peerIdAsRoomId must be present'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function getConversationIdForCallHistory(
|
function getConversationIdForCallHistory(
|
||||||
db: ReadableDB,
|
db: ReadableDB,
|
||||||
callHistory: CallHistoryDetails
|
callHistory: CallHistoryDetails
|
||||||
|
@ -3852,10 +3884,6 @@ export function markAllCallHistoryRead(
|
||||||
target: CallLogEventTarget,
|
target: CallLogEventTarget,
|
||||||
inConversation = false
|
inConversation = false
|
||||||
): number {
|
): number {
|
||||||
if (inConversation) {
|
|
||||||
strictAssert(target.peerId, 'peerId is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
const callHistory = getCallHistoryForCallLogEventTarget(db, target);
|
const callHistory = getCallHistoryForCallLogEventTarget(db, target);
|
||||||
if (callHistory == null) {
|
if (callHistory == null) {
|
||||||
|
@ -3870,28 +3898,45 @@ export function markAllCallHistoryRead(
|
||||||
'Call ID must be the same as target if supplied'
|
'Call ID must be the same as target if supplied'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let predicate: QueryFragment;
|
||||||
|
let receivedAt: number | null;
|
||||||
|
if (callHistory.mode === CallMode.Adhoc) {
|
||||||
|
// If the target is a call link, there's no associated conversation and messages,
|
||||||
|
// and we can only mark call history read based on timestamp.
|
||||||
|
strictAssert(
|
||||||
|
!inConversation,
|
||||||
|
'markAllCallHistoryRead: Not possible to mark read in conversation for Adhoc calls'
|
||||||
|
);
|
||||||
|
|
||||||
|
receivedAt = callHistory.timestamp;
|
||||||
|
predicate = sqlFragment`TRUE`;
|
||||||
|
} else {
|
||||||
const conversationId = getConversationIdForCallHistory(db, callHistory);
|
const conversationId = getConversationIdForCallHistory(db, callHistory);
|
||||||
if (conversationId == null) {
|
if (conversationId == null) {
|
||||||
logger.warn('markAllCallHistoryRead: Conversation not found for call');
|
logger.warn('markAllCallHistoryRead: Conversation not found for call');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
logger.info(`markAllCallHistoryRead: Found conversation ${conversationId}`);
|
|
||||||
const receivedAt = getMessageReceivedAtForCall(db, callId, conversationId);
|
logger.info(
|
||||||
|
`markAllCallHistoryRead: Found conversation ${conversationId}`
|
||||||
|
);
|
||||||
|
receivedAt = getMessageReceivedAtForCall(db, callId, conversationId);
|
||||||
|
|
||||||
|
predicate = inConversation
|
||||||
|
? sqlFragment`messages.conversationId IS ${conversationId}`
|
||||||
|
: sqlFragment`TRUE`;
|
||||||
|
}
|
||||||
|
|
||||||
if (receivedAt == null) {
|
if (receivedAt == null) {
|
||||||
logger.warn('markAllCallHistoryRead: Message not found for call');
|
logger.warn('markAllCallHistoryRead: Message not found for call');
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const predicate = inConversation
|
|
||||||
? sqlFragment`messages.conversationId IS ${conversationId}`
|
|
||||||
: sqlFragment`TRUE`;
|
|
||||||
|
|
||||||
const jsonPatch = JSON.stringify({
|
const jsonPatch = JSON.stringify({
|
||||||
seenStatus: SeenStatus.Seen,
|
seenStatus: SeenStatus.Seen,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.warn(
|
logger.info(
|
||||||
`markAllCallHistoryRead: Marking calls before ${receivedAt} read`
|
`markAllCallHistoryRead: Marking calls before ${receivedAt} read`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3915,7 +3960,6 @@ function markAllCallHistoryReadInConversation(
|
||||||
db: WritableDB,
|
db: WritableDB,
|
||||||
target: CallLogEventTarget
|
target: CallLogEventTarget
|
||||||
): number {
|
): number {
|
||||||
strictAssert(target.peerId, 'peerId is required');
|
|
||||||
return markAllCallHistoryRead(db, target, true);
|
return markAllCallHistoryRead(db, target, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,16 +125,26 @@ export type CallDetails = Readonly<{
|
||||||
endedTimestamp: number | null;
|
endedTimestamp: number | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type CallLogEventTarget = Readonly<{
|
export type CallLogEventTarget = Readonly<
|
||||||
|
{
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
callId: string | null;
|
callId: string | null;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
peerId: AciString | string | null;
|
peerId: AciString | string | null;
|
||||||
}>;
|
}
|
||||||
|
| {
|
||||||
|
peerIdAsConversationId: AciString | string | null;
|
||||||
|
peerIdAsRoomId: string | null;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
>;
|
||||||
|
|
||||||
export type CallLogEventDetails = Readonly<{
|
export type CallLogEventDetails = Readonly<{
|
||||||
type: CallLogEvent;
|
type: CallLogEvent;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
peerId: AciString | string | null;
|
peerIdAsConversationId: AciString | string | null;
|
||||||
|
peerIdAsRoomId: string | null;
|
||||||
callId: string | null;
|
callId: string | null;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -243,7 +253,9 @@ export const callHistoryGroupSchema = z.object({
|
||||||
),
|
),
|
||||||
}) satisfies z.ZodType<CallHistoryGroup>;
|
}) satisfies z.ZodType<CallHistoryGroup>;
|
||||||
|
|
||||||
const peerIdInBytesSchema = z.instanceof(Uint8Array).transform(value => {
|
const conversationPeerIdInBytesSchema = z
|
||||||
|
.instanceof(Uint8Array)
|
||||||
|
.transform(value => {
|
||||||
// direct conversationId
|
// direct conversationId
|
||||||
if (value.byteLength === UUID_BYTE_SIZE) {
|
if (value.byteLength === UUID_BYTE_SIZE) {
|
||||||
const uuid = bytesToUuid(value);
|
const uuid = bytesToUuid(value);
|
||||||
|
@ -287,7 +299,7 @@ export const callEventNormalizeSchema = z
|
||||||
type: z
|
type: z
|
||||||
.nativeEnum(Proto.SyncMessage.CallEvent.Type)
|
.nativeEnum(Proto.SyncMessage.CallEvent.Type)
|
||||||
.refine(val => val !== Proto.SyncMessage.CallEvent.Type.AD_HOC_CALL),
|
.refine(val => val !== Proto.SyncMessage.CallEvent.Type.AD_HOC_CALL),
|
||||||
peerId: peerIdInBytesSchema,
|
peerId: conversationPeerIdInBytesSchema,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
@ -295,7 +307,8 @@ export const callEventNormalizeSchema = z
|
||||||
export const callLogEventNormalizeSchema = z.object({
|
export const callLogEventNormalizeSchema = z.object({
|
||||||
type: z.nativeEnum(Proto.SyncMessage.CallLogEvent.Type),
|
type: z.nativeEnum(Proto.SyncMessage.CallLogEvent.Type),
|
||||||
timestamp: longToNumberSchema,
|
timestamp: longToNumberSchema,
|
||||||
peerId: peerIdInBytesSchema.optional(),
|
peerIdAsConversationId: conversationPeerIdInBytesSchema.optional(),
|
||||||
|
peerIdAsRoomId: roomIdInBytesSchema.optional(),
|
||||||
callId: longToStringSchema.optional(),
|
callId: longToStringSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -282,10 +282,14 @@ const callLogEventFromProto: Partial<
|
||||||
export function getCallLogEventForProto(
|
export function getCallLogEventForProto(
|
||||||
callLogEventProto: Proto.SyncMessage.ICallLogEvent
|
callLogEventProto: Proto.SyncMessage.ICallLogEvent
|
||||||
): CallLogEventDetails {
|
): CallLogEventDetails {
|
||||||
const callLogEvent = parsePartial(
|
// CallLogEvent peerId is ambiguous whether it's a conversationId (direct, or groupId)
|
||||||
callLogEventNormalizeSchema,
|
// or roomId so handle both cases
|
||||||
callLogEventProto
|
const { peerId: peerIdBytes } = callLogEventProto;
|
||||||
);
|
const callLogEvent = parsePartial(callLogEventNormalizeSchema, {
|
||||||
|
...callLogEventProto,
|
||||||
|
peerIdAsConversationId: peerIdBytes,
|
||||||
|
peerIdAsRoomId: peerIdBytes,
|
||||||
|
});
|
||||||
|
|
||||||
const type = callLogEventFromProto[callLogEvent.type];
|
const type = callLogEventFromProto[callLogEvent.type];
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
|
@ -295,7 +299,8 @@ export function getCallLogEventForProto(
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
timestamp: callLogEvent.timestamp,
|
timestamp: callLogEvent.timestamp,
|
||||||
peerId: callLogEvent.peerId ?? null,
|
peerIdAsConversationId: callLogEvent.peerIdAsConversationId ?? null,
|
||||||
|
peerIdAsRoomId: callLogEvent.peerIdAsRoomId ?? null,
|
||||||
callId: callLogEvent.callId ?? null,
|
callId: callLogEvent.callId ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,10 +14,12 @@ export async function onCallLogEventSync(
|
||||||
syncEvent: CallLogEventSyncEvent
|
syncEvent: CallLogEventSyncEvent
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { data, confirm } = syncEvent;
|
const { data, confirm } = syncEvent;
|
||||||
const { type, peerId, callId, timestamp } = data.callLogEventDetails;
|
const { type, peerIdAsConversationId, peerIdAsRoomId, callId, timestamp } =
|
||||||
|
data.callLogEventDetails;
|
||||||
|
|
||||||
const target: CallLogEventTarget = {
|
const target: CallLogEventTarget = {
|
||||||
peerId,
|
peerIdAsConversationId,
|
||||||
|
peerIdAsRoomId,
|
||||||
callId,
|
callId,
|
||||||
timestamp,
|
timestamp,
|
||||||
};
|
};
|
||||||
|
@ -50,7 +52,8 @@ export async function onCallLogEventSync(
|
||||||
} else if (type === CallLogEvent.MarkedAsReadInConversation) {
|
} else if (type === CallLogEvent.MarkedAsReadInConversation) {
|
||||||
log.info('onCallLogEventSync: Marking call history read in conversation');
|
log.info('onCallLogEventSync: Marking call history read in conversation');
|
||||||
try {
|
try {
|
||||||
strictAssert(peerId, 'Missing peerId');
|
strictAssert(peerIdAsConversationId, 'Missing peerIdAsConversationId');
|
||||||
|
strictAssert(peerIdAsRoomId, 'Missing peerIdAsRoomId');
|
||||||
const count =
|
const count =
|
||||||
await DataWriter.markAllCallHistoryReadInConversation(target);
|
await DataWriter.markAllCallHistoryReadInConversation(target);
|
||||||
log.info(
|
log.info(
|
||||||
|
|
Loading…
Reference in a new issue