Fix backup import for unused admin call links
This commit is contained in:
parent
1e7d259909
commit
79d3a0f1ee
4 changed files with 116 additions and 44 deletions
|
@ -134,7 +134,10 @@ import { getBackupCdnInfo } from './util/mediaId';
|
||||||
import { calculateExpirationTimestamp } from '../../util/expirationTimer';
|
import { calculateExpirationTimestamp } from '../../util/expirationTimer';
|
||||||
import { ReadStatus } from '../../messages/MessageReadStatus';
|
import { ReadStatus } from '../../messages/MessageReadStatus';
|
||||||
import { CallLinkRestrictions } from '../../types/CallLink';
|
import { CallLinkRestrictions } from '../../types/CallLink';
|
||||||
import { toAdminKeyBytes } from '../../util/callLinks';
|
import {
|
||||||
|
isCallHistoryForUnusedCallLink,
|
||||||
|
toAdminKeyBytes,
|
||||||
|
} from '../../util/callLinks';
|
||||||
import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc';
|
import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc';
|
||||||
import { SeenStatus } from '../../MessageSeenStatus';
|
import { SeenStatus } from '../../MessageSeenStatus';
|
||||||
import { migrateAllMessages } from '../../messages/migrateMessageData';
|
import { migrateAllMessages } from '../../messages/migrateMessageData';
|
||||||
|
@ -512,7 +515,7 @@ export class BackupExportStream extends Readable {
|
||||||
for (const item of allCallHistoryItems) {
|
for (const item of allCallHistoryItems) {
|
||||||
const { callId, type, peerId: roomId, status, timestamp } = item;
|
const { callId, type, peerId: roomId, status, timestamp } = item;
|
||||||
|
|
||||||
if (type !== CallType.Adhoc) {
|
if (type !== CallType.Adhoc || isCallHistoryForUnusedCallLink(item)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ import { assertDev, strictAssert } from '../../util/assert';
|
||||||
import {
|
import {
|
||||||
getCheckedTimestampFromLong,
|
getCheckedTimestampFromLong,
|
||||||
getCheckedTimestampOrUndefinedFromLong,
|
getCheckedTimestampOrUndefinedFromLong,
|
||||||
|
getTimestampOrUndefinedFromLong,
|
||||||
} from '../../util/timestampLongUtils';
|
} from '../../util/timestampLongUtils';
|
||||||
import { MAX_SAFE_DATE } from '../../util/timestamp';
|
import { MAX_SAFE_DATE } from '../../util/timestamp';
|
||||||
import { DurationInSeconds, SECOND } from '../../util/durations';
|
import { DurationInSeconds, SECOND } from '../../util/durations';
|
||||||
|
@ -108,10 +109,13 @@ import {
|
||||||
GroupCallStatus,
|
GroupCallStatus,
|
||||||
} from '../../types/CallDisposition';
|
} from '../../types/CallDisposition';
|
||||||
import type { CallHistoryDetails } from '../../types/CallDisposition';
|
import type { CallHistoryDetails } from '../../types/CallDisposition';
|
||||||
import { CallLinkRestrictions } from '../../types/CallLink';
|
import { CallLinkRestrictions, isCallLinkAdmin } from '../../types/CallLink';
|
||||||
import type { CallLinkType } from '../../types/CallLink';
|
import type { CallLinkType } from '../../types/CallLink';
|
||||||
import type { RawBodyRange } from '../../types/BodyRange';
|
import type { RawBodyRange } from '../../types/BodyRange';
|
||||||
import { fromAdminKeyBytes } from '../../util/callLinks';
|
import {
|
||||||
|
fromAdminKeyBytes,
|
||||||
|
toCallHistoryFromUnusedCallLink,
|
||||||
|
} from '../../util/callLinks';
|
||||||
import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc';
|
import { getRoomIdFromRootKey } from '../../util/callLinksRingrtc';
|
||||||
import { loadAllAndReinitializeRedux } from '../allLoaders';
|
import { loadAllAndReinitializeRedux } from '../allLoaders';
|
||||||
import {
|
import {
|
||||||
|
@ -209,6 +213,7 @@ export class BackupImportStream extends Writable {
|
||||||
ConversationAttributesType
|
ConversationAttributesType
|
||||||
>();
|
>();
|
||||||
private readonly recipientIdToCallLink = new Map<number, CallLinkType>();
|
private readonly recipientIdToCallLink = new Map<number, CallLinkType>();
|
||||||
|
private readonly adminCallLinksToHasCall = new Map<CallLinkType, boolean>();
|
||||||
private readonly chatIdToConvo = new Map<
|
private readonly chatIdToConvo = new Map<
|
||||||
number,
|
number,
|
||||||
ConversationAttributesType
|
ConversationAttributesType
|
||||||
|
@ -326,6 +331,15 @@ export class BackupImportStream extends Writable {
|
||||||
// Store sticker packs and schedule downloads
|
// Store sticker packs and schedule downloads
|
||||||
await createPacksFromBackup(this.stickerPacks);
|
await createPacksFromBackup(this.stickerPacks);
|
||||||
|
|
||||||
|
// Add placeholder call history for unused admin call links to show in calls tab
|
||||||
|
for (const [callLink, hasCall] of this.adminCallLinksToHasCall) {
|
||||||
|
if (!hasCall) {
|
||||||
|
const callHistory = toCallHistoryFromUnusedCallLink(callLink);
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.saveCallHistory(callHistory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset and reload conversations and storage again
|
// Reset and reload conversations and storage again
|
||||||
window.ConversationController.reset();
|
window.ConversationController.reset();
|
||||||
|
|
||||||
|
@ -1223,12 +1237,19 @@ export class BackupImportStream extends Writable {
|
||||||
name,
|
name,
|
||||||
restrictions: fromCallLinkRestrictionsProto(restrictions),
|
restrictions: fromCallLinkRestrictionsProto(restrictions),
|
||||||
revoked: false,
|
revoked: false,
|
||||||
expiration: getCheckedTimestampOrUndefinedFromLong(expirationMs) ?? null,
|
expiration: getTimestampOrUndefinedFromLong(expirationMs) ?? null,
|
||||||
storageNeedsSync: false,
|
storageNeedsSync: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.recipientIdToCallLink.set(recipientId, callLink);
|
this.recipientIdToCallLink.set(recipientId, callLink);
|
||||||
|
|
||||||
|
if (
|
||||||
|
isCallLinkAdmin(callLink) &&
|
||||||
|
!this.adminCallLinksToHasCall.has(callLink)
|
||||||
|
) {
|
||||||
|
this.adminCallLinksToHasCall.set(callLink, false);
|
||||||
|
}
|
||||||
|
|
||||||
await DataWriter.insertCallLink(callLink);
|
await DataWriter.insertCallLink(callLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3172,6 +3193,10 @@ export class BackupImportStream extends Writable {
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.saveCallHistory(callHistory);
|
await this.saveCallHistory(callHistory);
|
||||||
|
|
||||||
|
if (isCallLinkAdmin(callLink)) {
|
||||||
|
this.adminCallLinksToHasCall.set(callLink, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fromCustomChatColors(
|
private async fromCustomChatColors(
|
||||||
|
|
|
@ -39,7 +39,6 @@ const GROUP_ID_STRING = Bytes.toBase64(deriveGroupID(GROUP_SECRET_PARAMS));
|
||||||
describe('backup/calling', () => {
|
describe('backup/calling', () => {
|
||||||
let contactA: ConversationModel;
|
let contactA: ConversationModel;
|
||||||
let groupA: ConversationModel;
|
let groupA: ConversationModel;
|
||||||
let callLink: CallLinkType;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await DataWriter.removeAll();
|
await DataWriter.removeAll();
|
||||||
|
@ -64,24 +63,6 @@ describe('backup/calling', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const rootKey = CallLinkRootKey.generate();
|
|
||||||
const adminKey = CallLinkRootKey.generateAdminPassKey();
|
|
||||||
callLink = {
|
|
||||||
rootKey: rootKey.toString(),
|
|
||||||
roomId: getRoomIdFromRootKey(rootKey),
|
|
||||||
adminKey: fromAdminKeyBytes(adminKey),
|
|
||||||
name: "Let's Talk Rocks",
|
|
||||||
restrictions: CallLinkRestrictions.AdminApproval,
|
|
||||||
revoked: false,
|
|
||||||
expiration: null,
|
|
||||||
storageID: undefined,
|
|
||||||
storageVersion: undefined,
|
|
||||||
storageUnknownFields: undefined,
|
|
||||||
storageNeedsSync: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
await DataWriter.insertCallLink(callLink);
|
|
||||||
|
|
||||||
await loadAllAndReinitializeRedux();
|
await loadAllAndReinitializeRedux();
|
||||||
});
|
});
|
||||||
after(async () => {
|
after(async () => {
|
||||||
|
@ -137,6 +118,7 @@ describe('backup/calling', () => {
|
||||||
assert.deepEqual(callHistory, allCallHistory[0]);
|
assert.deepEqual(callHistory, allCallHistory[0]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Group calls', () => {
|
describe('Group calls', () => {
|
||||||
it('roundtrips with a missed call', async () => {
|
it('roundtrips with a missed call', async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -187,22 +169,28 @@ describe('backup/calling', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Call Links', () => {
|
describe('Call Links', () => {
|
||||||
it('roundtrips with a link with admin details', async () => {
|
let callLink: CallLinkType;
|
||||||
const allCallLinksBefore = await DataReader.getAllCallLinks();
|
let adminCallLink: CallLinkType;
|
||||||
assert.strictEqual(allCallLinksBefore.length, 1);
|
|
||||||
|
|
||||||
await symmetricRoundtripHarness([]);
|
beforeEach(async () => {
|
||||||
|
const adminRootKey = CallLinkRootKey.generate();
|
||||||
const allCallLinks = await DataReader.getAllCallLinks();
|
const adminKey = CallLinkRootKey.generateAdminPassKey();
|
||||||
assert.strictEqual(allCallLinks.length, 1);
|
adminCallLink = {
|
||||||
|
rootKey: adminRootKey.toString(),
|
||||||
assert.deepEqual(callLink, allCallLinks[0]);
|
roomId: getRoomIdFromRootKey(adminRootKey),
|
||||||
});
|
adminKey: fromAdminKeyBytes(adminKey),
|
||||||
it('roundtrips with a link without admin details', async () => {
|
name: "Let's Talk Rocks",
|
||||||
await DataWriter._removeAllCallLinks();
|
restrictions: CallLinkRestrictions.AdminApproval,
|
||||||
|
revoked: false,
|
||||||
|
expiration: null,
|
||||||
|
storageID: undefined,
|
||||||
|
storageVersion: undefined,
|
||||||
|
storageUnknownFields: undefined,
|
||||||
|
storageNeedsSync: false,
|
||||||
|
};
|
||||||
|
|
||||||
const rootKey = CallLinkRootKey.generate();
|
const rootKey = CallLinkRootKey.generate();
|
||||||
const callLinkNoAdmin = {
|
callLink = {
|
||||||
rootKey: rootKey.toString(),
|
rootKey: rootKey.toString(),
|
||||||
roomId: getRoomIdFromRootKey(rootKey),
|
roomId: getRoomIdFromRootKey(rootKey),
|
||||||
adminKey: null,
|
adminKey: null,
|
||||||
|
@ -215,7 +203,15 @@ describe('backup/calling', () => {
|
||||||
storageUnknownFields: undefined,
|
storageUnknownFields: undefined,
|
||||||
storageNeedsSync: false,
|
storageNeedsSync: false,
|
||||||
};
|
};
|
||||||
await DataWriter.insertCallLink(callLinkNoAdmin);
|
await DataWriter.insertCallLink(callLink);
|
||||||
|
|
||||||
|
await loadAllAndReinitializeRedux();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('roundtrips with a link with admin details', async () => {
|
||||||
|
await DataWriter._removeAllCallLinks();
|
||||||
|
|
||||||
|
await DataWriter.insertCallLink(adminCallLink);
|
||||||
|
|
||||||
const allCallLinksBefore = await DataReader.getAllCallLinks();
|
const allCallLinksBefore = await DataReader.getAllCallLinks();
|
||||||
assert.strictEqual(allCallLinksBefore.length, 1);
|
assert.strictEqual(allCallLinksBefore.length, 1);
|
||||||
|
@ -225,11 +221,37 @@ describe('backup/calling', () => {
|
||||||
const allCallLinks = await DataReader.getAllCallLinks();
|
const allCallLinks = await DataReader.getAllCallLinks();
|
||||||
assert.strictEqual(allCallLinks.length, 1);
|
assert.strictEqual(allCallLinks.length, 1);
|
||||||
|
|
||||||
assert.deepEqual(callLinkNoAdmin, allCallLinks[0]);
|
assert.deepEqual(adminCallLink, allCallLinks[0]);
|
||||||
});
|
});
|
||||||
|
it('creates placeholder call history for a link with admin details', async () => {
|
||||||
|
await DataWriter._removeAllCallLinks();
|
||||||
|
|
||||||
|
await DataWriter.insertCallLink(adminCallLink);
|
||||||
|
|
||||||
|
const allCallHistoryBefore = await DataReader.getAllCallHistory();
|
||||||
|
assert.strictEqual(allCallHistoryBefore.length, 0);
|
||||||
|
|
||||||
|
await symmetricRoundtripHarness([]);
|
||||||
|
|
||||||
|
const allCallHistory = await DataReader.getAllCallHistory();
|
||||||
|
assert.strictEqual(allCallHistory.length, 1);
|
||||||
});
|
});
|
||||||
describe('Adhoc calls', () => {
|
it('roundtrips with a link without admin details', async () => {
|
||||||
it('roundtrips with a joined call', async () => {
|
await DataWriter._removeAllCallLinks();
|
||||||
|
|
||||||
|
await DataWriter.insertCallLink(callLink);
|
||||||
|
|
||||||
|
const allCallLinksBefore = await DataReader.getAllCallLinks();
|
||||||
|
assert.strictEqual(allCallLinksBefore.length, 1);
|
||||||
|
|
||||||
|
await symmetricRoundtripHarness([]);
|
||||||
|
|
||||||
|
const allCallLinks = await DataReader.getAllCallLinks();
|
||||||
|
assert.strictEqual(allCallLinks.length, 1);
|
||||||
|
|
||||||
|
assert.deepEqual(callLink, allCallLinks[0]);
|
||||||
|
});
|
||||||
|
it('roundtrips with a joined adhoc call', async () => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const callId = '333333';
|
const callId = '333333';
|
||||||
const callHistory: CallHistoryDetails = {
|
const callHistory: CallHistoryDetails = {
|
||||||
|
@ -254,8 +276,7 @@ describe('backup/calling', () => {
|
||||||
|
|
||||||
assert.deepEqual(callHistory, allCallHistory[0]);
|
assert.deepEqual(callHistory, allCallHistory[0]);
|
||||||
});
|
});
|
||||||
|
it('does not roundtrip adhoc call with missing call link', async () => {
|
||||||
it('does not roundtrip call with missing call link', async () => {
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const callId = '44444';
|
const callId = '44444';
|
||||||
const callHistory: CallHistoryDetails = {
|
const callHistory: CallHistoryDetails = {
|
||||||
|
|
|
@ -116,3 +116,26 @@ export function toCallHistoryFromUnusedCallLink(
|
||||||
status: AdhocCallStatus.Pending,
|
status: AdhocCallStatus.Pending,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCallHistoryForUnusedCallLink(
|
||||||
|
callHistory: CallHistoryDetails
|
||||||
|
): boolean {
|
||||||
|
const {
|
||||||
|
ringerId,
|
||||||
|
startedById,
|
||||||
|
endedTimestamp,
|
||||||
|
mode,
|
||||||
|
type,
|
||||||
|
direction,
|
||||||
|
status,
|
||||||
|
} = callHistory;
|
||||||
|
return (
|
||||||
|
ringerId == null &&
|
||||||
|
startedById == null &&
|
||||||
|
endedTimestamp == null &&
|
||||||
|
mode === CallMode.Adhoc &&
|
||||||
|
type === CallType.Adhoc &&
|
||||||
|
direction === CallDirection.Incoming &&
|
||||||
|
status === AdhocCallStatus.Pending
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue