Save group send endorsements
This commit is contained in:
parent
dea641bae4
commit
4253bed0bd
12 changed files with 583 additions and 91 deletions
|
@ -31,6 +31,7 @@ import type {
|
|||
} from '../types/CallDisposition';
|
||||
import type { CallLinkStateType, CallLinkType } from '../types/CallLink';
|
||||
import type { AttachmentDownloadJobType } from '../types/AttachmentDownload';
|
||||
import type { GroupSendEndorsementsData } from '../types/GroupSendEndorsements';
|
||||
|
||||
export type AdjacentMessagesByConversationOptionsType = Readonly<{
|
||||
conversationId: string;
|
||||
|
@ -526,6 +527,14 @@ export type DataInterface = {
|
|||
serviceId: ServiceIdString
|
||||
) => Promise<Array<ConversationType>>;
|
||||
|
||||
replaceAllEndorsementsForGroup: (
|
||||
data: GroupSendEndorsementsData
|
||||
) => Promise<void>;
|
||||
deleteAllEndorsementsForGroup: (groupId: string) => Promise<void>;
|
||||
getGroupSendCombinedEndorsementExpiration: (
|
||||
groupId: string
|
||||
) => Promise<number | null>;
|
||||
|
||||
getMessageCount: (conversationId?: string) => Promise<number>;
|
||||
getStoryCount: (conversationId: string) => Promise<number>;
|
||||
saveMessage: (
|
||||
|
|
|
@ -174,6 +174,11 @@ import {
|
|||
updateCallLinkAdminKeyByRoomId,
|
||||
updateCallLinkState,
|
||||
} from './server/callLinks';
|
||||
import {
|
||||
replaceAllEndorsementsForGroup,
|
||||
deleteAllEndorsementsForGroup,
|
||||
getGroupSendCombinedEndorsementExpiration,
|
||||
} from './server/groupEndorsements';
|
||||
import { CallMode } from '../types/Calling';
|
||||
import {
|
||||
attachmentDownloadJobSchema,
|
||||
|
@ -286,6 +291,10 @@ const dataInterface: ServerInterface = {
|
|||
getAllConversationIds,
|
||||
getAllGroupsInvolvingServiceId,
|
||||
|
||||
replaceAllEndorsementsForGroup,
|
||||
deleteAllEndorsementsForGroup,
|
||||
getGroupSendCombinedEndorsementExpiration,
|
||||
|
||||
searchMessages,
|
||||
|
||||
getMessageCount,
|
||||
|
@ -4530,9 +4539,9 @@ function getAttachmentDownloadJob(
|
|||
SELECT * FROM attachment_downloads
|
||||
WHERE
|
||||
messageId = ${job.messageId}
|
||||
AND
|
||||
AND
|
||||
attachmentType = ${job.attachmentType}
|
||||
AND
|
||||
AND
|
||||
digest = ${job.digest};
|
||||
`;
|
||||
|
||||
|
@ -4570,7 +4579,7 @@ async function getNextAttachmentDownloadJobs({
|
|||
})
|
||||
AND
|
||||
messageId IN (${sqlJoin(prioritizeMessageIds)})
|
||||
-- for priority messages, let's load them oldest first; this helps, e.g. for stories where we
|
||||
-- for priority messages, let's load them oldest first; this helps, e.g. for stories where we
|
||||
-- want the oldest one first
|
||||
ORDER BY receivedAt ASC
|
||||
LIMIT ${limit}
|
||||
|
@ -4681,11 +4690,11 @@ function removeAttachmentDownloadJobSync(
|
|||
): void {
|
||||
const [query, params] = sql`
|
||||
DELETE FROM attachment_downloads
|
||||
WHERE
|
||||
WHERE
|
||||
messageId = ${job.messageId}
|
||||
AND
|
||||
attachmentType = ${job.attachmentType}
|
||||
AND
|
||||
attachmentType = ${job.attachmentType}
|
||||
AND
|
||||
digest = ${job.digest};
|
||||
`;
|
||||
|
||||
|
@ -6003,6 +6012,8 @@ async function removeAll(): Promise<void> {
|
|||
DELETE FROM conversations;
|
||||
DELETE FROM emojis;
|
||||
DELETE FROM groupCallRingCancellations;
|
||||
DELETE FROM groupSendCombinedEndorsement;
|
||||
DELETE FROM groupSendMemberEndorsement;
|
||||
DELETE FROM identityKeys;
|
||||
DELETE FROM items;
|
||||
DELETE FROM jobs;
|
||||
|
@ -6053,6 +6064,8 @@ async function removeAllConfiguration(): Promise<void> {
|
|||
db.transaction(() => {
|
||||
db.exec(
|
||||
`
|
||||
DELETE FROM groupSendCombinedEndorsement;
|
||||
DELETE FROM groupSendMemberEndorsement;
|
||||
DELETE FROM identityKeys;
|
||||
DELETE FROM jobs;
|
||||
DELETE FROM kyberPreKeys;
|
||||
|
|
50
ts/sql/migrations/1050-group-send-endorsements.ts
Normal file
50
ts/sql/migrations/1050-group-send-endorsements.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from '@signalapp/better-sqlite3';
|
||||
|
||||
import type { LoggerType } from '../../types/Logging';
|
||||
import { sql } from '../util';
|
||||
|
||||
export const version = 1050;
|
||||
|
||||
export function updateToSchemaVersion1050(
|
||||
currentVersion: number,
|
||||
db: Database,
|
||||
logger: LoggerType
|
||||
): void {
|
||||
if (currentVersion >= 1050) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.transaction(() => {
|
||||
const [createTables] = sql`
|
||||
DROP TABLE IF EXISTS groupSendCombinedEndorsement;
|
||||
DROP TABLE IF EXISTS groupSendMemberEndorsement;
|
||||
|
||||
-- From GroupSendEndorsementsResponse->ReceivedEndorsements in libsignal
|
||||
-- this is the combined endorsement for all group members
|
||||
CREATE TABLE groupSendCombinedEndorsement (
|
||||
groupId TEXT NOT NULL PRIMARY KEY, -- Only one endorsement per group
|
||||
expiration INTEGER NOT NULL, -- Unix timestamp in seconds
|
||||
endorsement BLOB NOT NULL
|
||||
) STRICT;
|
||||
|
||||
-- From GroupSendEndorsementsResponse->ReceivedEndorsements in libsignal
|
||||
-- these are the individual endorsements for each group member
|
||||
CREATE TABLE groupSendMemberEndorsement (
|
||||
groupId TEXT NOT NULL,
|
||||
memberAci TEXT NOT NULL,
|
||||
expiration INTEGER NOT NULL, -- Unix timestamp in seconds
|
||||
endorsement BLOB NOT NULL,
|
||||
PRIMARY KEY (groupId, memberAci) -- Only one endorsement per group member
|
||||
) STRICT;
|
||||
`;
|
||||
|
||||
db.exec(createTables);
|
||||
|
||||
db.pragma('user_version = 1050');
|
||||
})();
|
||||
|
||||
logger.info('updateToSchemaVersion1050: success!');
|
||||
}
|
|
@ -79,10 +79,11 @@ import { updateToSchemaVersion1000 } from './1000-mark-unread-call-history-messa
|
|||
import { updateToSchemaVersion1010 } from './1010-call-links-table';
|
||||
import { updateToSchemaVersion1020 } from './1020-self-merges';
|
||||
import { updateToSchemaVersion1030 } from './1030-unblock-event';
|
||||
import { updateToSchemaVersion1040 } from './1040-undownloaded-backed-up-media';
|
||||
import {
|
||||
updateToSchemaVersion1040,
|
||||
updateToSchemaVersion1050,
|
||||
version as MAX_VERSION,
|
||||
} from './1040-undownloaded-backed-up-media';
|
||||
} from './1050-group-send-endorsements';
|
||||
|
||||
function updateToSchemaVersion1(
|
||||
currentVersion: number,
|
||||
|
@ -2029,6 +2030,7 @@ export const SCHEMA_VERSIONS = [
|
|||
updateToSchemaVersion1020,
|
||||
updateToSchemaVersion1030,
|
||||
updateToSchemaVersion1040,
|
||||
updateToSchemaVersion1050,
|
||||
];
|
||||
|
||||
export class DBVersionFromFutureError extends Error {
|
||||
|
|
87
ts/sql/server/groupEndorsements.ts
Normal file
87
ts/sql/server/groupEndorsements.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Database } from '@signalapp/better-sqlite3';
|
||||
import type {
|
||||
GroupSendCombinedEndorsementRecord,
|
||||
GroupSendEndorsementsData,
|
||||
GroupSendMemberEndorsementRecord,
|
||||
} from '../../types/GroupSendEndorsements';
|
||||
import { groupSendEndorsementExpirationSchema } from '../../types/GroupSendEndorsements';
|
||||
import { getReadonlyInstance, getWritableInstance, prepare } from '../Server';
|
||||
import { sql } from '../util';
|
||||
|
||||
/**
|
||||
* We don't need to store more than one endorsement per group or per member.
|
||||
*/
|
||||
export async function replaceAllEndorsementsForGroup(
|
||||
data: GroupSendEndorsementsData
|
||||
): Promise<void> {
|
||||
const db = await getWritableInstance();
|
||||
db.transaction(() => {
|
||||
const { combinedEndorsement, memberEndorsements } = data;
|
||||
_replaceCombinedEndorsement(db, combinedEndorsement);
|
||||
_replaceMemberEndorsements(db, memberEndorsements);
|
||||
})();
|
||||
}
|
||||
|
||||
function _replaceCombinedEndorsement(
|
||||
db: Database,
|
||||
combinedEndorsement: GroupSendCombinedEndorsementRecord
|
||||
): void {
|
||||
const { groupId, expiration, endorsement } = combinedEndorsement;
|
||||
const [insertCombined, insertCombinedParams] = sql`
|
||||
INSERT OR REPLACE INTO groupSendCombinedEndorsement
|
||||
(groupId, expiration, endorsement)
|
||||
VALUES (${groupId}, ${expiration}, ${endorsement});
|
||||
`;
|
||||
prepare<Array<unknown>>(db, insertCombined).run(insertCombinedParams);
|
||||
}
|
||||
|
||||
function _replaceMemberEndorsements(
|
||||
db: Database,
|
||||
memberEndorsements: ReadonlyArray<GroupSendMemberEndorsementRecord>
|
||||
) {
|
||||
for (const memberEndorsement of memberEndorsements) {
|
||||
const { groupId, memberAci, expiration, endorsement } = memberEndorsement;
|
||||
const [replaceMember, replaceMemberParams] = sql`
|
||||
INSERT OR REPLACE INTO groupSendMemberEndorsement
|
||||
(groupId, memberAci, expiration, endorsement)
|
||||
VALUES (${groupId}, ${memberAci}, ${expiration}, ${endorsement});
|
||||
`;
|
||||
prepare<Array<unknown>>(db, replaceMember).run(replaceMemberParams);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAllEndorsementsForGroup(
|
||||
groupId: string
|
||||
): Promise<void> {
|
||||
const db = await getWritableInstance();
|
||||
db.transaction(() => {
|
||||
const [deleteCombined, deleteCombinedParams] = sql`
|
||||
DELETE FROM groupSendCombinedEndorsement
|
||||
WHERE groupId = ${groupId};
|
||||
`;
|
||||
const [deleteMembers, deleteMembersParams] = sql`
|
||||
DELETE FROM groupSendMemberEndorsement
|
||||
WHERE groupId = ${groupId};
|
||||
`;
|
||||
prepare<Array<unknown>>(db, deleteCombined).run(deleteCombinedParams);
|
||||
prepare<Array<unknown>>(db, deleteMembers).run(deleteMembersParams);
|
||||
})();
|
||||
}
|
||||
|
||||
export async function getGroupSendCombinedEndorsementExpiration(
|
||||
groupId: string
|
||||
): Promise<number | null> {
|
||||
const db = getReadonlyInstance();
|
||||
const [selectGroup, selectGroupParams] = sql`
|
||||
SELECT expiration FROM groupSendCombinedEndorsement
|
||||
WHERE groupId = ${groupId};
|
||||
`;
|
||||
const value = prepare(db, selectGroup).pluck().get(selectGroupParams);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return groupSendEndorsementExpirationSchema.parse(value);
|
||||
}
|
|
@ -36,7 +36,12 @@ export function jsonToObject<T>(json: string): T {
|
|||
return JSON.parse(json);
|
||||
}
|
||||
|
||||
export type QueryTemplateParam = string | number | null | undefined;
|
||||
export type QueryTemplateParam =
|
||||
| Uint8Array
|
||||
| string
|
||||
| number
|
||||
| null
|
||||
| undefined;
|
||||
export type QueryFragmentValue = QueryFragment | QueryTemplateParam;
|
||||
|
||||
export type QueryFragment = [
|
||||
|
@ -148,7 +153,7 @@ export type QueryTemplate = [string, ReadonlyArray<QueryTemplateParam>];
|
|||
*/
|
||||
export function sql(
|
||||
strings: TemplateStringsArray,
|
||||
...values: ReadonlyArray<QueryFragment | QueryTemplateParam>
|
||||
...values: Array<QueryFragment | QueryTemplateParam>
|
||||
): QueryTemplate {
|
||||
const [{ fragment }, params] = sqlFragment(strings, ...values);
|
||||
return [fragment, params];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue