Import distribution lists from backup
This commit is contained in:
parent
3e51e4ef5d
commit
7cd07eb7b4
2 changed files with 122 additions and 1 deletions
|
@ -13,6 +13,7 @@ import * as log from '../../logging/log';
|
|||
import { StorySendMode } from '../../types/Stories';
|
||||
import type { ServiceIdString } from '../../types/ServiceId';
|
||||
import { fromAciObject, fromPniObject } from '../../types/ServiceId';
|
||||
import { isStoryDistributionId } from '../../types/StoryDistributionId';
|
||||
import * as Errors from '../../types/errors';
|
||||
import type {
|
||||
ConversationAttributesType,
|
||||
|
@ -32,6 +33,8 @@ import { isAciString } from '../../util/isAciString';
|
|||
import { createBatcher } from '../../util/batcher';
|
||||
import { PhoneNumberDiscoverability } from '../../util/phoneNumberDiscoverability';
|
||||
import { PhoneNumberSharingMode } from '../../util/phoneNumberSharingMode';
|
||||
import { bytesToUuid } from '../../util/uuidToBytes';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
import type { SendStateByConversationId } from '../../messages/MessageSendState';
|
||||
|
@ -237,6 +240,11 @@ export class BackupImportStream extends Writable {
|
|||
convo = this.ourConversation;
|
||||
} else if (recipient.group) {
|
||||
convo = await this.fromGroup(recipient.group);
|
||||
} else if (recipient.distributionList) {
|
||||
await this.fromDistributionList(recipient.distributionList);
|
||||
|
||||
// Not a conversation
|
||||
return;
|
||||
} else {
|
||||
log.warn(`${this.logId}: unsupported recipient item`);
|
||||
return;
|
||||
|
@ -504,6 +512,76 @@ export class BackupImportStream extends Writable {
|
|||
return attrs;
|
||||
}
|
||||
|
||||
private async fromDistributionList(
|
||||
list: Backups.IDistributionList
|
||||
): Promise<void> {
|
||||
strictAssert(
|
||||
Bytes.isNotEmpty(list.distributionId),
|
||||
'Missing distribution list id'
|
||||
);
|
||||
|
||||
const id = bytesToUuid(list.distributionId);
|
||||
strictAssert(isStoryDistributionId(id), 'Invalid distribution list id');
|
||||
|
||||
strictAssert(
|
||||
list.privacyMode != null,
|
||||
'Missing distribution list privacy mode'
|
||||
);
|
||||
|
||||
let isBlockList: boolean;
|
||||
const { PrivacyMode } = Backups.DistributionList;
|
||||
switch (list.privacyMode) {
|
||||
case PrivacyMode.ALL:
|
||||
strictAssert(
|
||||
!list.memberRecipientIds?.length,
|
||||
'Distribution list with ALL privacy mode has members'
|
||||
);
|
||||
isBlockList = true;
|
||||
break;
|
||||
case PrivacyMode.ALL_EXCEPT:
|
||||
strictAssert(
|
||||
list.memberRecipientIds?.length,
|
||||
'Distribution list with ALL_EXCEPT privacy mode has no members'
|
||||
);
|
||||
isBlockList = true;
|
||||
break;
|
||||
case PrivacyMode.ONLY_WITH:
|
||||
isBlockList = false;
|
||||
break;
|
||||
case PrivacyMode.UNKNOWN:
|
||||
throw new Error('Invalid privacy mode for distribution list');
|
||||
default:
|
||||
throw missingCaseError(list.privacyMode);
|
||||
}
|
||||
|
||||
const result = {
|
||||
id,
|
||||
name: list.name ?? '',
|
||||
deletedAtTimestamp:
|
||||
list.deletionTimestamp == null
|
||||
? undefined
|
||||
: getTimestampFromLong(list.deletionTimestamp),
|
||||
allowsReplies: list.allowReplies === true,
|
||||
isBlockList,
|
||||
members: (list.memberRecipientIds || []).map(recipientId => {
|
||||
const convo = this.recipientIdToConvo.get(recipientId.toNumber());
|
||||
strictAssert(convo != null, 'Missing story distribution list member');
|
||||
strictAssert(
|
||||
convo.serviceId,
|
||||
'Story distribution list member has no serviceId'
|
||||
);
|
||||
|
||||
return convo.serviceId;
|
||||
}),
|
||||
|
||||
// Default values
|
||||
senderKeyInfo: undefined,
|
||||
storageNeedsSync: false,
|
||||
};
|
||||
|
||||
await Data.createNewStoryDistribution(result);
|
||||
}
|
||||
|
||||
private async fromChat(chat: Backups.IChat): Promise<void> {
|
||||
strictAssert(chat.id != null, 'chat must have an id');
|
||||
strictAssert(chat.recipientId != null, 'chat must have a recipientId');
|
||||
|
|
|
@ -3,14 +3,21 @@
|
|||
|
||||
import createDebug from 'debug';
|
||||
import Long from 'long';
|
||||
import { StorageState } from '@signalapp/mock-server';
|
||||
import { Proto, StorageState } from '@signalapp/mock-server';
|
||||
|
||||
import { generateStoryDistributionId } from '../../types/StoryDistributionId';
|
||||
import { MY_STORY_ID } from '../../types/Stories';
|
||||
import { uuidToBytes } from '../../util/uuidToBytes';
|
||||
import * as durations from '../../util/durations';
|
||||
import type { App } from '../playwright';
|
||||
import { Bootstrap } from '../bootstrap';
|
||||
|
||||
export const debug = createDebug('mock:test:backups');
|
||||
|
||||
const IdentifierType = Proto.ManifestRecord.Identifier.Type;
|
||||
|
||||
const DISTRIBUTION1 = generateStoryDistributionId();
|
||||
|
||||
describe('backups', function (this: Mocha.Suite) {
|
||||
this.timeout(100 * durations.MINUTE);
|
||||
|
||||
|
@ -46,6 +53,33 @@ describe('backups', function (this: Mocha.Suite) {
|
|||
|
||||
state = state.pin(pinned);
|
||||
|
||||
// Create empty My Story
|
||||
state = state.addRecord({
|
||||
type: IdentifierType.STORY_DISTRIBUTION_LIST,
|
||||
record: {
|
||||
storyDistributionList: {
|
||||
allowsReplies: true,
|
||||
identifier: uuidToBytes(MY_STORY_ID),
|
||||
isBlockList: true,
|
||||
name: MY_STORY_ID,
|
||||
recipientServiceIds: [pinned.device.aci],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
state = state.addRecord({
|
||||
type: IdentifierType.STORY_DISTRIBUTION_LIST,
|
||||
record: {
|
||||
storyDistributionList: {
|
||||
allowsReplies: true,
|
||||
identifier: uuidToBytes(DISTRIBUTION1),
|
||||
isBlockList: false,
|
||||
name: 'friend',
|
||||
recipientServiceIds: [friend.device.aci],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await phone.setStorageState(state);
|
||||
|
||||
app = await bootstrap.link();
|
||||
|
@ -138,6 +172,15 @@ describe('backups', function (this: Mocha.Suite) {
|
|||
.waitFor();
|
||||
|
||||
await snapshot('conversation');
|
||||
|
||||
debug('Switching to stories nav tab');
|
||||
await window.getByTestId('NavTabsItem--Stories').click();
|
||||
|
||||
debug('Opening story privacy');
|
||||
await window.locator('.StoriesTab__MoreActionsIcon').click();
|
||||
await window.getByRole('button', { name: 'Story Privacy' }).click();
|
||||
|
||||
await snapshot('story privacy');
|
||||
},
|
||||
this.test
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue