Change Phone Number notifications

This commit is contained in:
Fedor Indutny 2021-08-05 16:34:49 -07:00 committed by GitHub
parent 4b82ac387b
commit a001882d58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 277 additions and 39 deletions

View file

@ -1168,6 +1168,16 @@
} }
} }
}, },
"ChangeNumber--notification": {
"message": "$sender$ changed their number to a new number",
"description": "Shown in timeline when a member of a conversation changes their phone number",
"placeholders": {
"sender": {
"content": "$1",
"example": "Sam"
}
}
},
"quoteThumbnailAlt": { "quoteThumbnailAlt": {
"message": "Thumbnail of image from quoted message", "message": "Thumbnail of image from quoted message",
"description": "Used in alt tag of thumbnail images inside of an embedded message quote" "description": "Used in alt tag of thumbnail images inside of an embedded message quote"

View file

@ -135,4 +135,5 @@ message AccountRecord {
repeated PinnedConversation pinnedConversations = 14; repeated PinnedConversation pinnedConversations = 14;
optional uint32 universalExpireTimer = 17; optional uint32 universalExpireTimer = 17;
optional bool primarySendsSms = 18; optional bool primarySendsSms = 18;
optional string e164 = 19;
} }

View file

@ -2425,6 +2425,45 @@ $timer-icons: '55', '50', '45', '40', '35', '30', '25', '20', '15', '10', '05',
} }
} }
// Module: Change Number Notification
.module-change-number-notification {
@include font-body-2;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
@include light-theme {
color: $color-gray-60;
}
@include dark-theme {
color: $color-gray-05;
}
&__icon {
height: 16px;
width: 16px;
display: inline-block;
margin-right: 8px;
@include light-theme {
@include color-svg(
'../images/icons/v2/phone-right-outline-24.svg',
$color-gray-75
);
}
@include dark-theme {
@include color-svg(
'../images/icons/v2/phone-right-solid-24.svg',
$color-gray-15
);
}
}
}
// Module: Error Boundary
.module-error-boundary-notification { .module-error-boundary-notification {
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;

View file

@ -60,6 +60,7 @@ import {
ReadSyncEvent, ReadSyncEvent,
ContactEvent, ContactEvent,
GroupEvent, GroupEvent,
EnvelopeEvent,
} from './textsecure/messageReceiverEvents'; } from './textsecure/messageReceiverEvents';
import type { WebAPIType } from './textsecure/WebAPI'; import type { WebAPIType } from './textsecure/WebAPI';
import * as universalExpireTimer from './util/universalExpireTimer'; import * as universalExpireTimer from './util/universalExpireTimer';
@ -174,6 +175,10 @@ export async function startApp(): Promise<void> {
}; };
} }
messageReceiver.addEventListener(
'envelope',
queuedEventListener(onEnvelopeReceived, false)
);
messageReceiver.addEventListener( messageReceiver.addEventListener(
'message', 'message',
queuedEventListener(onMessageReceived, false) queuedEventListener(onMessageReceived, false)
@ -503,17 +508,7 @@ export async function startApp(): Promise<void> {
accountManager = new window.textsecure.AccountManager(server); accountManager = new window.textsecure.AccountManager(server);
accountManager.addEventListener('registration', () => { accountManager.addEventListener('registration', () => {
const ourDeviceId = window.textsecure.storage.user.getDeviceId(); window.Whisper.events.trigger('userChanged');
const ourNumber = window.textsecure.storage.user.getNumber();
const ourUuid = window.textsecure.storage.user.getUuid();
const user = {
ourConversationId: window.ConversationController.getOurConversationId(),
ourDeviceId,
ourNumber,
ourUuid,
regionCode: window.storage.get('regionCode'),
};
window.Whisper.events.trigger('userChanged', user);
window.Signal.Util.Registration.markDone(); window.Signal.Util.Registration.markDone();
window.log.info('dispatching registration event'); window.log.info('dispatching registration event');
@ -1210,7 +1205,6 @@ export async function startApp(): Promise<void> {
conversationRemoved, conversationRemoved,
removeAllConversations, removeAllConversations,
} = window.reduxActions.conversations; } = window.reduxActions.conversations;
const { userChanged } = window.reduxActions.user;
convoCollection.on('remove', conversation => { convoCollection.on('remove', conversation => {
const { id } = conversation || {}; const { id } = conversation || {};
@ -1254,7 +1248,19 @@ export async function startApp(): Promise<void> {
}); });
convoCollection.on('reset', removeAllConversations); convoCollection.on('reset', removeAllConversations);
window.Whisper.events.on('userChanged', userChanged); window.Whisper.events.on('userChanged', () => {
const newDeviceId = window.textsecure.storage.user.getDeviceId();
const newNumber = window.textsecure.storage.user.getNumber();
const newUuid = window.textsecure.storage.user.getUuid();
window.reduxActions.user.userChanged({
ourConversationId: window.ConversationController.getOurConversationId(),
ourDeviceId: newDeviceId,
ourNumber: newNumber,
ourUuid: newUuid,
regionCode: window.storage.get('regionCode'),
});
});
let shortcutGuideView: WhatIsThis | null = null; let shortcutGuideView: WhatIsThis | null = null;
@ -3000,6 +3006,17 @@ export async function startApp(): Promise<void> {
maxSize: Infinity, maxSize: Infinity,
}); });
function onEnvelopeReceived({ envelope }: EnvelopeEvent) {
const ourUuid = window.textsecure.storage.user.getUuid();
if (envelope.sourceUuid && envelope.sourceUuid !== ourUuid) {
window.ConversationController.ensureContactIds({
e164: envelope.source,
uuid: envelope.sourceUuid,
highTrust: true,
});
}
}
// Note: We do very little in this function, since everything in handleDataMessage is // Note: We do very little in this function, since everything in handleDataMessage is
// inside a conversation-specific queue(). Any code here might run before an earlier // inside a conversation-specific queue(). Any code here might run before an earlier
// message is processed in handleDataMessage(). // message is processed in handleDataMessage().

View file

@ -0,0 +1,43 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React from 'react';
import { ConversationType } from '../../state/ducks/conversations';
import { LocalizerType } from '../../types/Util';
import { Intl } from '../Intl';
import { Timestamp } from './Timestamp';
import { Emojify } from './Emojify';
export type PropsData = {
sender: ConversationType;
timestamp: number;
};
export type PropsHousekeeping = {
i18n: LocalizerType;
};
export type Props = PropsData & PropsHousekeeping;
const CSS_MODULE = 'module-change-number-notification';
export const ChangeNumberNotification: React.FC<Props> = props => {
const { i18n, sender, timestamp } = props;
return (
<div className={CSS_MODULE}>
<span className={`${CSS_MODULE}__icon`} />
<Intl
id="ChangeNumber--notification"
components={{
sender: <Emojify text={sender.firstName || sender.title} />,
}}
i18n={i18n}
/>
&nbsp;·&nbsp;
<Timestamp i18n={i18n} timestamp={timestamp} />
</div>
);
};

View file

@ -129,6 +129,13 @@ storiesOf('Components/Conversation/TimelineItem', module)
type: 'universalTimerNotification', type: 'universalTimerNotification',
data: null, data: null,
}, },
{
type: 'changeNumberNotification',
data: {
sender: getDefaultConversation(),
timestamp: Date.now(),
},
},
{ {
type: 'callHistory', type: 'callHistory',
data: { data: {

View file

@ -24,6 +24,10 @@ import {
PropsActionsType as DeliveryIssueActionProps, PropsActionsType as DeliveryIssueActionProps,
PropsDataType as DeliveryIssueProps, PropsDataType as DeliveryIssueProps,
} from './DeliveryIssueNotification'; } from './DeliveryIssueNotification';
import {
ChangeNumberNotification,
PropsData as ChangeNumberNotificationProps,
} from './ChangeNumberNotification';
import { CallingNotificationType } from '../../util/callingNotification'; import { CallingNotificationType } from '../../util/callingNotification';
import { InlineNotificationWrapper } from './InlineNotificationWrapper'; import { InlineNotificationWrapper } from './InlineNotificationWrapper';
import { import {
@ -95,6 +99,10 @@ type UniversalTimerNotificationType = {
type: 'universalTimerNotification'; type: 'universalTimerNotification';
data: null; data: null;
}; };
type ChangeNumberNotificationType = {
type: 'changeNumberNotification';
data: ChangeNumberNotificationProps;
};
type SafetyNumberNotificationType = { type SafetyNumberNotificationType = {
type: 'safetyNumberNotification'; type: 'safetyNumberNotification';
data: SafetyNumberNotificationProps; data: SafetyNumberNotificationProps;
@ -138,6 +146,7 @@ export type TimelineItemType =
| SafetyNumberNotificationType | SafetyNumberNotificationType
| TimerNotificationType | TimerNotificationType
| UniversalTimerNotificationType | UniversalTimerNotificationType
| ChangeNumberNotificationType
| UnsupportedMessageType | UnsupportedMessageType
| VerificationNotificationType; | VerificationNotificationType;
@ -244,6 +253,10 @@ export class TimelineItem extends React.PureComponent<PropsType> {
); );
} else if (item.type === 'universalTimerNotification') { } else if (item.type === 'universalTimerNotification') {
notification = renderUniversalTimerNotification(); notification = renderUniversalTimerNotification();
} else if (item.type === 'changeNumberNotification') {
notification = (
<ChangeNumberNotification {...this.props} {...item.data} i18n={i18n} />
);
} else if (item.type === 'safetyNumberNotification') { } else if (item.type === 'safetyNumberNotification') {
notification = ( notification = (
<SafetyNumberNotification {...this.props} {...item.data} i18n={i18n} /> <SafetyNumberNotification {...this.props} {...item.data} i18n={i18n} />

1
ts/model-types.d.ts vendored
View file

@ -141,6 +141,7 @@ export type MessageAttributesType = {
| 'profile-change' | 'profile-change'
| 'timer-notification' | 'timer-notification'
| 'universal-timer-notification' | 'universal-timer-notification'
| 'change-number-notification'
| 'verified-change'; | 'verified-change';
body?: string; body?: string;
attachments?: Array<AttachmentType>; attachments?: Array<AttachmentType>;

View file

@ -152,8 +152,6 @@ export class ConversationModel extends window.Backbone
jobQueue?: typeof window.PQueueType; jobQueue?: typeof window.PQueueType;
ourNumber?: string;
ourUuid?: string; ourUuid?: string;
storeName?: string | null; storeName?: string | null;
@ -234,7 +232,6 @@ export class ConversationModel extends window.Backbone
this.storeName = 'conversations'; this.storeName = 'conversations';
this.ourNumber = window.textsecure.storage.user.getNumber();
this.ourUuid = window.textsecure.storage.user.getUuid(); this.ourUuid = window.textsecure.storage.user.getUuid();
this.verifiedEnum = window.textsecure.storage.protocol.VerifiedStatus; this.verifiedEnum = window.textsecure.storage.protocol.VerifiedStatus;
@ -1497,6 +1494,11 @@ export class ConversationModel extends window.Backbone
const oldValue = this.get('e164'); const oldValue = this.get('e164');
if (e164 && e164 !== oldValue) { if (e164 && e164 !== oldValue) {
this.set('e164', e164); this.set('e164', e164);
if (oldValue) {
this.addChangeNumberNotification();
}
window.Signal.Data.updateConversation(this.attributes); window.Signal.Data.updateConversation(this.attributes);
this.trigger('idUpdated', this, 'e164', oldValue); this.trigger('idUpdated', this, 'e164', oldValue);
} }
@ -2688,7 +2690,7 @@ export class ConversationModel extends window.Backbone
sent_at: now, sent_at: now,
received_at: window.Signal.Util.incrementMessageCounter(), received_at: window.Signal.Util.incrementMessageCounter(),
received_at_ms: now, received_at_ms: now,
unread: 0, unread: false,
changedId: conversationId || this.id, changedId: conversationId || this.id,
profileChange, profileChange,
// TODO: DESKTOP-722 // TODO: DESKTOP-722
@ -2716,23 +2718,30 @@ export class ConversationModel extends window.Backbone
} }
} }
async addUniversalTimerNotification(): Promise<string> { async addNotification(
type: MessageAttributesType['type'],
extra: Partial<MessageAttributesType> = {}
): Promise<string> {
const now = Date.now(); const now = Date.now();
const message = ({ const message: Partial<MessageAttributesType> = {
...extra,
conversationId: this.id, conversationId: this.id,
type: 'universal-timer-notification', type,
sent_at: now, sent_at: now,
received_at: window.Signal.Util.incrementMessageCounter(), received_at: window.Signal.Util.incrementMessageCounter(),
received_at_ms: now, received_at_ms: now,
unread: 0, unread: false,
// TODO: DESKTOP-722 };
} as unknown) as typeof window.Whisper.MessageAttributesType;
const id = await window.Signal.Data.saveMessage(message); const id = await window.Signal.Data.saveMessage(
// TODO: DESKTOP-722
message as MessageAttributesType
);
const model = window.MessageController.register( const model = window.MessageController.register(
id, id,
new window.Whisper.Message({ new window.Whisper.Message({
...message, ...(message as MessageAttributesType),
id, id,
}) })
); );
@ -2764,7 +2773,9 @@ export class ConversationModel extends window.Backbone
return; return;
} }
const notificationId = await this.addUniversalTimerNotification(); const notificationId = await this.addNotification(
'universal-timer-notification'
);
this.set('pendingUniversalTimer', notificationId); this.set('pendingUniversalTimer', notificationId);
} }
@ -2797,6 +2808,27 @@ export class ConversationModel extends window.Backbone
this.set('pendingUniversalTimer', undefined); this.set('pendingUniversalTimer', undefined);
} }
async addChangeNumberNotification(): Promise<void> {
window.log.info(
`Conversation ${this.idForLogging()}: adding change number notification`
);
const convos = [
this,
...(await window.ConversationController.getAllGroupsInvolvingId(this.id)),
];
const sourceUuid = this.get('uuid');
await Promise.all(
convos.map(convo => {
return convo.addNotification('change-number-notification', {
sourceUuid,
});
})
);
}
async onReadMessage( async onReadMessage(
message: MessageModel, message: MessageModel,
readAt?: number readAt?: number

View file

@ -178,8 +178,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
INITIAL_PROTOCOL_VERSION?: number; INITIAL_PROTOCOL_VERSION?: number;
OUR_NUMBER?: string;
OUR_UUID?: string; OUR_UUID?: string;
isSelected?: boolean; isSelected?: boolean;
@ -209,7 +207,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.CURRENT_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.CURRENT; this.CURRENT_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.CURRENT;
this.INITIAL_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.INITIAL; this.INITIAL_PROTOCOL_VERSION = Proto.DataMessage.ProtocolVersion.INITIAL;
this.OUR_NUMBER = window.textsecure.storage.user.getNumber();
this.OUR_UUID = window.textsecure.storage.user.getUuid(); this.OUR_UUID = window.textsecure.storage.user.getUuid();
this.on('change', this.notifyRedux); this.on('change', this.notifyRedux);
@ -364,7 +361,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.attributes, this.attributes,
findAndFormatContact, findAndFormatContact,
ourConversationId, ourConversationId,
this.OUR_NUMBER, window.textsecure.storage.user.getNumber(),
this.OUR_UUID, this.OUR_UUID,
undefined, undefined,
undefined, undefined,
@ -1066,7 +1063,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
); );
} }
return this.OUR_NUMBER; return window.textsecure.storage.user.getNumber();
} }
getSourceDevice(): string | number | undefined { getSourceDevice(): string | number | undefined {
@ -1280,11 +1277,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const quoteWithData = await loadQuoteData(this.get('quote')); const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview')); const previewWithData = await loadPreviewData(this.get('preview'));
const stickerWithData = await loadStickerData(this.get('sticker')); const stickerWithData = await loadStickerData(this.get('sticker'));
const ourNumber = window.textsecure.storage.user.getNumber();
// Special-case the self-send case - we send only a sync message // Special-case the self-send case - we send only a sync message
if ( if (
recipients.length === 1 && recipients.length === 1 &&
(recipients[0] === this.OUR_NUMBER || recipients[0] === this.OUR_UUID) (recipients[0] === ourNumber || recipients[0] === this.OUR_UUID)
) { ) {
const dataMessage = await window.textsecure.messaging.getDataMessage({ const dataMessage = await window.textsecure.messaging.getDataMessage({
attachments, attachments,
@ -1434,9 +1432,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const quoteWithData = await loadQuoteData(this.get('quote')); const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview')); const previewWithData = await loadPreviewData(this.get('preview'));
const stickerWithData = await loadStickerData(this.get('sticker')); const stickerWithData = await loadStickerData(this.get('sticker'));
const ourNumber = window.textsecure.storage.user.getNumber();
// Special-case the self-send case - we send only a sync message // Special-case the self-send case - we send only a sync message
if (identifier === this.OUR_NUMBER || identifier === this.OUR_UUID) { if (identifier === ourNumber || identifier === this.OUR_UUID) {
const dataMessage = await window.textsecure.messaging.getDataMessage({ const dataMessage = await window.textsecure.messaging.getDataMessage({
attachments, attachments,
body, body,

View file

@ -186,6 +186,11 @@ export async function toAccountRecord(
accountRecord.primarySendsSms = Boolean(primarySendsSms); accountRecord.primarySendsSms = Boolean(primarySendsSms);
} }
const accountE164 = window.storage.get('accountE164');
if (accountE164 !== undefined) {
accountRecord.e164 = accountE164;
}
const universalExpireTimer = getUniversalExpireTimer(); const universalExpireTimer = getUniversalExpireTimer();
if (universalExpireTimer) { if (universalExpireTimer) {
accountRecord.universalExpireTimer = Number(universalExpireTimer); accountRecord.universalExpireTimer = Number(universalExpireTimer);
@ -828,6 +833,7 @@ export async function mergeAccountRecord(
typingIndicators, typingIndicators,
primarySendsSms, primarySendsSms,
universalExpireTimer, universalExpireTimer,
e164: accountE164,
} = accountRecord; } = accountRecord;
window.storage.put('read-receipt-setting', Boolean(readReceipts)); window.storage.put('read-receipt-setting', Boolean(readReceipts));
@ -848,6 +854,11 @@ export async function mergeAccountRecord(
window.storage.put('primarySendsSms', primarySendsSms); window.storage.put('primarySendsSms', primarySendsSms);
} }
if (typeof accountE164 === 'string') {
window.storage.put('accountE164', accountE164);
window.storage.user.setNumber(accountE164);
}
setUniversalExpireTimer(universalExpireTimer || 0); setUniversalExpireTimer(universalExpireTimer || 0);
const PHONE_NUMBER_SHARING_MODE_ENUM = const PHONE_NUMBER_SHARING_MODE_ENUM =

View file

@ -3522,7 +3522,8 @@ async function hasUserInitiatedMessages(
'message-history-unsynced', 'message-history-unsynced',
'keychange', 'keychange',
'group-v1-migration', 'group-v1-migration',
'universal-timer-notification' 'universal-timer-notification',
'change-number-notification'
) )
) AND ) AND
json_extract(json, '$.expirationTimerUpdate') IS NULL json_extract(json, '$.expirationTimerUpdate') IS NULL
@ -4226,7 +4227,8 @@ async function getLastConversationActivity({
'message-history-unsynced', 'message-history-unsynced',
'keychange', 'keychange',
'group-v1-migration', 'group-v1-migration',
'universal-timer-notification' 'universal-timer-notification',
'change-number-notification'
) )
) AND ) AND
( (
@ -4277,7 +4279,8 @@ async function getLastConversationPreview({
'verified-change', 'verified-change',
'message-history-unsynced', 'message-history-unsynced',
'group-v1-migration', 'group-v1-migration',
'universal-timer-notification' 'universal-timer-notification',
'change-number-notification'
) )
) AND NOT ) AND NOT
( (

View file

@ -13,6 +13,7 @@ import {
import { TimelineItemType } from '../../components/conversation/TimelineItem'; import { TimelineItemType } from '../../components/conversation/TimelineItem';
import { PropsData } from '../../components/conversation/Message'; import { PropsData } from '../../components/conversation/Message';
import { PropsData as TimerNotificationProps } from '../../components/conversation/TimerNotification'; import { PropsData as TimerNotificationProps } from '../../components/conversation/TimerNotification';
import { PropsData as ChangeNumberNotificationProps } from '../../components/conversation/ChangeNumberNotification';
import { PropsData as SafetyNumberNotificationProps } from '../../components/conversation/SafetyNumberNotification'; import { PropsData as SafetyNumberNotificationProps } from '../../components/conversation/SafetyNumberNotification';
import { PropsData as VerificationNotificationProps } from '../../components/conversation/VerificationNotification'; import { PropsData as VerificationNotificationProps } from '../../components/conversation/VerificationNotification';
import { PropsDataType as GroupsV2Props } from '../../components/conversation/GroupV2Change'; import { PropsDataType as GroupsV2Props } from '../../components/conversation/GroupV2Change';
@ -188,6 +189,12 @@ export function getPropsForBubble(
data: null, data: null,
}; };
} }
if (isChangeNumberNotification(message)) {
return {
type: 'changeNumberNotification',
data: getPropsForChangeNumberNotification(message, conversationSelector),
};
}
if (isChatSessionRefreshed(message)) { if (isChatSessionRefreshed(message)) {
return { return {
type: 'chatSessionRefreshed', type: 'chatSessionRefreshed',
@ -852,6 +859,24 @@ export function isUniversalTimerNotification(
return message.type === 'universal-timer-notification'; return message.type === 'universal-timer-notification';
} }
// Change Number Notification
export function isChangeNumberNotification(
message: MessageAttributesType
): boolean {
return message.type === 'change-number-notification';
}
function getPropsForChangeNumberNotification(
message: MessageAttributesType,
conversationSelector: GetConversationByIdType
): ChangeNumberNotificationProps {
return {
sender: conversationSelector(message.sourceUuid),
timestamp: message.sent_at,
};
}
// Chat Session Refreshed // Chat Session Refreshed
export function isChatSessionRefreshed( export function isChatSessionRefreshed(

View file

@ -95,6 +95,7 @@ import {
ContactSyncEvent, ContactSyncEvent,
GroupEvent, GroupEvent,
GroupSyncEvent, GroupSyncEvent,
EnvelopeEvent,
} from './messageReceiverEvents'; } from './messageReceiverEvents';
// TODO: remove once we move away from ArrayBuffers // TODO: remove once we move away from ArrayBuffers
@ -459,6 +460,11 @@ export default class MessageReceiver
handler: (ev: GroupSyncEvent) => void handler: (ev: GroupSyncEvent) => void
): void; ): void;
public addEventListener(
name: 'envelope',
handler: (ev: EnvelopeEvent) => void
): void;
public addEventListener(name: string, handler: EventHandler): void { public addEventListener(name: string, handler: EventHandler): void {
return super.addEventListener(name, handler); return super.addEventListener(name, handler);
} }
@ -981,8 +987,6 @@ export default class MessageReceiver
if (this.stoppingProcessing) { if (this.stoppingProcessing) {
return; return;
} }
// No decryption is required for delivery receipts, so the decrypted field of
// the Unprocessed model will never be set
if (envelope.content) { if (envelope.content) {
await this.innerHandleContentMessage(envelope, plaintext); await this.innerHandleContentMessage(envelope, plaintext);

View file

@ -5,7 +5,11 @@
import { PublicKey } from '@signalapp/signal-client'; import { PublicKey } from '@signalapp/signal-client';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
import { ProcessedDataMessage, ProcessedSent } from './Types.d'; import {
ProcessedEnvelope,
ProcessedDataMessage,
ProcessedSent,
} from './Types.d';
import type { import type {
ModifiedContactDetails, ModifiedContactDetails,
ModifiedGroupDetails, ModifiedGroupDetails,
@ -136,6 +140,12 @@ export class GroupSyncEvent extends Event {
} }
} }
export class EnvelopeEvent extends Event {
constructor(public readonly envelope: ProcessedEnvelope) {
super('envelope');
}
}
// //
// Confirmable events below // Confirmable events below
// //

View file

@ -3,6 +3,7 @@
import { WebAPICredentials } from '../Types.d'; import { WebAPICredentials } from '../Types.d';
import { strictAssert } from '../../util/assert';
import { StorageInterface } from '../../types/Storage.d'; import { StorageInterface } from '../../types/Storage.d';
import Helpers from '../Helpers'; import Helpers from '../Helpers';
@ -27,6 +28,25 @@ export class User {
window.log.info('storage.user: uuid and device id changed'); window.log.info('storage.user: uuid and device id changed');
} }
public async setNumber(number: string): Promise<void> {
if (this.getNumber() === number) {
return;
}
const deviceId = this.getDeviceId();
strictAssert(
deviceId !== undefined,
'Cannot update device number without knowing device id'
);
window.log.info('storage.user: number changed');
await this.storage.put('number_id', `${number}.${deviceId}`);
// Notify redux about phone number change
window.Whisper.events.trigger('userChanged');
}
public getNumber(): string | undefined { public getNumber(): string | undefined {
const numberId = this.storage.get('number_id'); const numberId = this.storage.get('number_id');
if (numberId === undefined) return undefined; if (numberId === undefined) return undefined;

View file

@ -77,6 +77,9 @@ export type StorageAccessType = {
phoneNumberDiscoverability: PhoneNumberDiscoverability; phoneNumberDiscoverability: PhoneNumberDiscoverability;
pinnedConversationIds: Array<string>; pinnedConversationIds: Array<string>;
primarySendsSms: boolean; primarySendsSms: boolean;
// Unlike `number_id` (which also includes device id) this field is only
// updated whenever we receive a new storage manifest
accountE164: string;
typingIndicators: boolean; typingIndicators: boolean;
sealedSenderIndicators: boolean; sealedSenderIndicators: boolean;
storageFetchComplete: boolean; storageFetchComplete: boolean;