diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index 4001685f562..905d6590cba 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -333,7 +333,7 @@ export type ConversationAttributesType = { messageRequestResponseType?: number; muteExpiresAt?: number; dontNotifyForMentionsIfMuted?: boolean; - notSharingPhoneNumber?: boolean; + sharingPhoneNumber?: boolean; profileAvatar?: ContactAvatarType | null; profileKeyCredential?: string | null; profileKeyCredentialExpiration?: number | null; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index d6f65823501..5883c7eeb4d 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -1859,7 +1859,7 @@ export class ConversationModel extends window.Backbone this.set('e164', e164 || undefined); // This user changed their phone number - if (oldValue && e164 && !this.get('notSharingPhoneNumber')) { + if (oldValue && e164 && this.get('sharingPhoneNumber')) { void this.addChangeNumberNotification(oldValue, e164); } diff --git a/ts/services/profiles.ts b/ts/services/profiles.ts index 5df1dd11420..46e20437cee 100644 --- a/ts/services/profiles.ts +++ b/ts/services/profiles.ts @@ -434,15 +434,15 @@ async function doGetProfile(c: ConversationModel): Promise { decryptionKey ); - // It should be one byte, but be conservative about it and only - // set `notSharingPhoneNumber` to `true` in all cases except [0x01]. + // It should be one byte, but be conservative about it and + // set `sharingPhoneNumber` to `false` in all cases except [0x01]. c.set( - 'notSharingPhoneNumber', - decrypted.length !== 1 || decrypted[0] !== 1 + 'sharingPhoneNumber', + decrypted.length === 1 && decrypted[0] === 1 ); } } else { - c.unset('notSharingPhoneNumber'); + c.unset('sharingPhoneNumber'); } if (profile.paymentAddress && isMe(c.attributes)) { diff --git a/ts/services/usernameIntegrity.ts b/ts/services/usernameIntegrity.ts index b6bb21d1076..35b5b73fc89 100644 --- a/ts/services/usernameIntegrity.ts +++ b/ts/services/usernameIntegrity.ts @@ -123,7 +123,7 @@ class UsernameIntegrityService { { const localValue = isSharingPhoneNumberWithEverybody(); - const remoteValue = !me.get('notSharingPhoneNumber'); + const remoteValue = me.get('sharingPhoneNumber') === true; if (localValue === remoteValue) { return; } @@ -152,7 +152,7 @@ class UsernameIntegrityService { { const localValue = isSharingPhoneNumberWithEverybody(); - const remoteValue = !me.get('notSharingPhoneNumber'); + const remoteValue = me.get('sharingPhoneNumber') === true; if (localValue === remoteValue) { log.info( 'usernameIntegrity: phone number sharing mode conflict resolved by ' + diff --git a/ts/sql/migrations/990-phone-number-sharing.ts b/ts/sql/migrations/990-phone-number-sharing.ts new file mode 100644 index 00000000000..979f4f8859a --- /dev/null +++ b/ts/sql/migrations/990-phone-number-sharing.ts @@ -0,0 +1,45 @@ +// 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'; + +export const version = 990; + +export function updateToSchemaVersion990( + currentVersion: number, + db: Database, + logger: LoggerType +): void { + if (currentVersion >= 990) { + return; + } + + db.transaction(() => { + db.exec(` + UPDATE conversations + SET json = json_remove( + json_insert( + json, + '$.sharingPhoneNumber', + iif( + json ->> '$.notSharingPhoneNumber', + -- We flip the value from false to true, and vice versa + json('false'), + json('true') + ) + ), + '$.notSharingPhoneNumber' + ) + -- Default value of '$.notSharingPhoneNumber' is true and + -- the default value of '$.sharingPhoneNumber' is false so we don't have + -- to do anything if the field wasn't present. + WHERE json ->> '$.notSharingPhoneNumber' IS NOT NULL; + `); + })(); + + db.pragma('user_version = 990'); + + logger.info('updateToSchemaVersion990: success!'); +} diff --git a/ts/sql/migrations/index.ts b/ts/sql/migrations/index.ts index e2fcc45de97..a106fe45486 100644 --- a/ts/sql/migrations/index.ts +++ b/ts/sql/migrations/index.ts @@ -73,10 +73,11 @@ import { updateToSchemaVersion940 } from './940-fts5-revert'; import { updateToSchemaVersion950 } from './950-fts5-secure-delete'; import { updateToSchemaVersion960 } from './960-untag-pni'; import { updateToSchemaVersion970 } from './970-fts5-optimize'; +import { updateToSchemaVersion980 } from './980-reaction-timestamp'; import { version as MAX_VERSION, - updateToSchemaVersion980, -} from './980-reaction-timestamp'; + updateToSchemaVersion990, +} from './990-phone-number-sharing'; function updateToSchemaVersion1( currentVersion: number, @@ -2017,6 +2018,7 @@ export const SCHEMA_VERSIONS = [ updateToSchemaVersion960, updateToSchemaVersion970, updateToSchemaVersion980, + updateToSchemaVersion990, ]; export class DBVersionFromFutureError extends Error { diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index bc4dba1f742..b2f35431e04 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -308,7 +308,7 @@ export type ConversationType = ReadonlyDeep< typingContactIdTimestamps?: Record; recentMediaItems?: ReadonlyArray; profileSharing?: boolean; - notSharingPhoneNumber?: boolean; + sharingPhoneNumber?: boolean; shouldShowDraft?: boolean; // Full information for re-hydrating composition area diff --git a/ts/test-mock/pnp/accept_gv2_invite_test.ts b/ts/test-mock/pnp/accept_gv2_invite_test.ts index fb99dec5075..9fe4b847379 100644 --- a/ts/test-mock/pnp/accept_gv2_invite_test.ts +++ b/ts/test-mock/pnp/accept_gv2_invite_test.ts @@ -53,6 +53,7 @@ describe('pnp/accept gv2 invite', function (this: Mocha.Suite) { { identityState: Proto.ContactRecord.IdentityState.DEFAULT, whitelisted: true, + profileKey: undefined, serviceE164: unknownPniContact.device.number, }, diff --git a/ts/test-node/sql/migration_990_test.ts b/ts/test-node/sql/migration_990_test.ts new file mode 100644 index 00000000000..89801d94f48 --- /dev/null +++ b/ts/test-node/sql/migration_990_test.ts @@ -0,0 +1,69 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; +import type { Database } from '@signalapp/better-sqlite3'; +import SQL from '@signalapp/better-sqlite3'; + +import { updateToVersion, insertData, getTableData } from './helpers'; + +describe('SQL/updateToSchemaVersion990', () => { + let db: Database; + + beforeEach(() => { + db = new SQL(':memory:'); + updateToVersion(db, 980); + }); + + afterEach(() => { + db.close(); + }); + + it('should migrate conversations', () => { + insertData(db, 'conversations', [ + { + id: 'no-prop', + json: { + keep: 'this', + }, + }, + { + id: 'false-prop', + json: { + keep: 'this', + notSharingPhoneNumber: false, + }, + }, + { + id: 'true-prop', + json: { + keep: 'this', + notSharingPhoneNumber: true, + }, + }, + ]); + updateToVersion(db, 990); + assert.deepStrictEqual(getTableData(db, 'conversations'), [ + { + id: 'no-prop', + json: { + keep: 'this', + }, + }, + { + id: 'false-prop', + json: { + keep: 'this', + sharingPhoneNumber: true, + }, + }, + { + id: 'true-prop', + json: { + keep: 'this', + sharingPhoneNumber: false, + }, + }, + ]); + }); +}); diff --git a/ts/util/createIPCEvents.ts b/ts/util/createIPCEvents.ts index 727380781e1..ad39315be62 100644 --- a/ts/util/createIPCEvents.ts +++ b/ts/util/createIPCEvents.ts @@ -247,12 +247,6 @@ export function createIPCEvents( setPhoneNumberSharingSetting: async (newValue: PhoneNumberSharingMode) => { const account = window.ConversationController.getOurConversationOrThrow(); - // writeProfile fetches the latest profile first so do it before updating - // local data to prevent triggering a conflict. - await writeProfile(getConversation(account), { - keepAvatar: true, - }); - const promises = new Array>(); promises.push(window.storage.put('phoneNumberSharingMode', newValue)); if (newValue === PhoneNumberSharingMode.Everybody) { @@ -264,6 +258,12 @@ export function createIPCEvents( } account.captureChange('phoneNumberSharingMode'); await Promise.all(promises); + + // Write profile after updating storage so that the write has up-to-date + // information. + await writeProfile(getConversation(account), { + keepAvatar: true, + }); }, getHasStoriesDisabled: () => diff --git a/ts/util/getConversation.ts b/ts/util/getConversation.ts index 9fc325fbce6..3da8e0c0fe6 100644 --- a/ts/util/getConversation.ts +++ b/ts/util/getConversation.ts @@ -211,7 +211,7 @@ export function getConversation(model: ConversationModel): ConversationType { profileName: getProfileName(attributes), profileSharing: attributes.profileSharing, profileLastUpdatedAt: attributes.profileLastUpdatedAt, - notSharingPhoneNumber: attributes.notSharingPhoneNumber, + sharingPhoneNumber: attributes.sharingPhoneNumber, publicParams: attributes.publicParams, secretParams: attributes.secretParams, shouldShowDraft, diff --git a/ts/util/getE164.ts b/ts/util/getE164.ts index 2651c5ed338..02f3babdf9f 100644 --- a/ts/util/getE164.ts +++ b/ts/util/getE164.ts @@ -13,12 +13,13 @@ export function getE164( | 'systemGivenName' | 'systemFamilyName' | 'e164' - | 'notSharingPhoneNumber' + | 'sharingPhoneNumber' + | 'profileKey' > ): string | undefined { - const { e164, notSharingPhoneNumber = false } = attributes; + const { e164, profileKey, sharingPhoneNumber } = attributes; - if (notSharingPhoneNumber && !isInSystemContacts(attributes)) { + if (!sharingPhoneNumber && profileKey && !isInSystemContacts(attributes)) { return undefined; } diff --git a/ts/util/getTitle.ts b/ts/util/getTitle.ts index c42cb6f03ce..aaa5a33a479 100644 --- a/ts/util/getTitle.ts +++ b/ts/util/getTitle.ts @@ -110,7 +110,7 @@ export function getSystemName( export function getNumber( attributes: Pick< ConversationAttributesType, - 'e164' | 'type' | 'notSharingPhoneNumber' + 'e164' | 'type' | 'sharingPhoneNumber' | 'profileKey' > ): string | undefined { if (!isDirectConversation(attributes)) { diff --git a/ts/util/phoneNumberSharingMode.ts b/ts/util/phoneNumberSharingMode.ts index 459c7f1f6f5..e7d250aa0b8 100644 --- a/ts/util/phoneNumberSharingMode.ts +++ b/ts/util/phoneNumberSharingMode.ts @@ -16,7 +16,7 @@ export enum PhoneNumberSharingMode { export const parsePhoneNumberSharingMode = makeEnumParser( PhoneNumberSharingMode, - PhoneNumberSharingMode.Everybody + PhoneNumberSharingMode.Nobody ); export const isSharingPhoneNumberWithEverybody = (): boolean => {