diff --git a/ts/services/storageRecordOps.ts b/ts/services/storageRecordOps.ts index 81c1e30b3daf..fa2cb7411320 100644 --- a/ts/services/storageRecordOps.ts +++ b/ts/services/storageRecordOps.ts @@ -888,7 +888,10 @@ export async function mergeGroupV1Record( }); conversation.setMuteExpiration( - getTimestampFromLong(groupV1Record.mutedUntilTimestamp), + getTimestampFromLong( + groupV1Record.mutedUntilTimestamp, + Number.MAX_SAFE_INTEGER + ), { viaStorageServiceSync: true, } @@ -1025,7 +1028,10 @@ export async function mergeGroupV2Record( }); conversation.setMuteExpiration( - getTimestampFromLong(groupV2Record.mutedUntilTimestamp), + getTimestampFromLong( + groupV2Record.mutedUntilTimestamp, + Number.MAX_SAFE_INTEGER + ), { viaStorageServiceSync: true, } @@ -1265,7 +1271,10 @@ export async function mergeContactRecord( } conversation.setMuteExpiration( - getTimestampFromLong(contactRecord.mutedUntilTimestamp), + getTimestampFromLong( + contactRecord.mutedUntilTimestamp, + Number.MAX_SAFE_INTEGER + ), { viaStorageServiceSync: true, } diff --git a/ts/sql/migrations/1310-muted-fixup.ts b/ts/sql/migrations/1310-muted-fixup.ts new file mode 100644 index 000000000000..63879cbd1ab3 --- /dev/null +++ b/ts/sql/migrations/1310-muted-fixup.ts @@ -0,0 +1,40 @@ +// Copyright 2025 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +import type { LoggerType } from '../../types/Logging'; +import { sql } from '../util'; +import type { WritableDB } from '../Interface'; + +export const version = 1310; + +// Value from ts/util/timestamp.ts at the time of creation of this migration +const MAX_SAFE_DATE = 8640000000000000; + +export function updateToSchemaVersion1310( + currentVersion: number, + db: WritableDB, + logger: LoggerType +): void { + if (currentVersion >= 1310) { + return; + } + + db.transaction(() => { + const [query, params] = sql` + UPDATE conversations + SET json = json_replace( + json, + '$.muteExpiresAt', + 9007199254740991 -- max safe integer + ) + WHERE json ->> '$.muteExpiresAt' IS ${MAX_SAFE_DATE}; + `; + const { changes } = db.prepare(query).run(params); + if (changes !== 0) { + logger.warn(`updateToSchemaVersion1310: fixed ${changes} conversations`); + } + + db.pragma('user_version = 1310'); + })(); + + logger.info('updateToSchemaVersion1310: success!'); +} diff --git a/ts/sql/migrations/index.ts b/ts/sql/migrations/index.ts index 7e3acce1f340..15d879db3689 100644 --- a/ts/sql/migrations/index.ts +++ b/ts/sql/migrations/index.ts @@ -106,10 +106,11 @@ import { updateToSchemaVersion1260 } from './1260-sync-tasks-rowid'; import { updateToSchemaVersion1270 } from './1270-normalize-messages'; import { updateToSchemaVersion1280 } from './1280-blob-unprocessed'; import { updateToSchemaVersion1290 } from './1290-int-unprocessed-source-device'; +import { updateToSchemaVersion1300 } from './1300-sticker-pack-refs'; import { - updateToSchemaVersion1300, + updateToSchemaVersion1310, version as MAX_VERSION, -} from './1300-sticker-pack-refs'; +} from './1310-muted-fixup'; import { DataWriter } from '../Server'; function updateToSchemaVersion1( @@ -2087,6 +2088,7 @@ export const SCHEMA_VERSIONS = [ updateToSchemaVersion1290, updateToSchemaVersion1300, + updateToSchemaVersion1310, ]; export class DBVersionFromFutureError extends Error { diff --git a/ts/test-node/sql/migration_1310_test.ts b/ts/test-node/sql/migration_1310_test.ts new file mode 100644 index 000000000000..ed6529ae5ed3 --- /dev/null +++ b/ts/test-node/sql/migration_1310_test.ts @@ -0,0 +1,80 @@ +// Copyright 2025 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; + +import { type WritableDB } from '../../sql/Interface'; +import { createDB, updateToVersion, insertData, getTableData } from './helpers'; + +describe('SQL/updateToSchemaVersion1310', () => { + let db: WritableDB; + + afterEach(() => { + db.close(); + }); + + beforeEach(() => { + db = createDB(); + updateToVersion(db, 1300); + }); + + it('leaves absent muteExpiresAt untouched', () => { + const convos = [ + { + id: 'convo', + expireTimerVersion: 1, + json: {}, + }, + ]; + insertData(db, 'conversations', convos); + updateToVersion(db, 1310); + + assert.deepStrictEqual(getTableData(db, 'conversations'), convos); + }); + + it('leaves regular muteExpiresAt untouched', () => { + const convos = [ + { + id: 'convo', + expireTimerVersion: 1, + json: { + muteExpiresAt: 123, + }, + }, + { + id: 'convo-2', + expireTimerVersion: 1, + json: { + muteExpiresAt: 8640000000000000 - 1, + }, + }, + ]; + insertData(db, 'conversations', convos); + updateToVersion(db, 1310); + + assert.deepStrictEqual(getTableData(db, 'conversations'), convos); + }); + + it('promotes MAX_SAFE_DATE to MAX_SAFE_INTEGER', () => { + insertData(db, 'conversations', [ + { + id: 'convo', + expireTimerVersion: 1, + json: { + muteExpiresAt: 8640000000000000, + }, + }, + ]); + updateToVersion(db, 1310); + + assert.deepStrictEqual(getTableData(db, 'conversations'), [ + { + id: 'convo', + expireTimerVersion: 1, + json: { + muteExpiresAt: Number.MAX_SAFE_INTEGER, + }, + }, + ]); + }); +}); diff --git a/ts/util/timestampLongUtils.ts b/ts/util/timestampLongUtils.ts index 5196b200fefe..7bc386b0fbd5 100644 --- a/ts/util/timestampLongUtils.ts +++ b/ts/util/timestampLongUtils.ts @@ -19,7 +19,10 @@ export function getSafeLongFromTimestamp( return Long.fromNumber(timestamp); } -export function getTimestampFromLong(value?: Long | null): number { +export function getTimestampFromLong( + value?: Long | null, + maxValue = MAX_SAFE_DATE +): number { if (!value || value.isNegative()) { return 0; } @@ -27,7 +30,7 @@ export function getTimestampFromLong(value?: Long | null): number { const num = value.toNumber(); if (num > MAX_SAFE_DATE) { - return MAX_SAFE_DATE; + return maxValue; } return num;