Save storage for defunct and pending call links
This commit is contained in:
parent
a7be33b201
commit
c6902ec26a
10 changed files with 474 additions and 31 deletions
|
@ -4,21 +4,23 @@
|
||||||
import * as z from 'zod';
|
import * as z from 'zod';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import { CallLinkRootKey } from '@signalapp/ringrtc';
|
import { CallLinkRootKey } from '@signalapp/ringrtc';
|
||||||
|
import * as globalLogger from '../logging/log';
|
||||||
import type { LoggerType } from '../types/Logging';
|
import type { LoggerType } from '../types/Logging';
|
||||||
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
|
import { exponentialBackoffMaxAttempts } from '../util/exponentialBackoff';
|
||||||
import type { ParsedJob } from './types';
|
import type { ParsedJob, StoredJob } from './types';
|
||||||
import type { JOB_STATUS } from './JobQueue';
|
import type { JOB_STATUS } from './JobQueue';
|
||||||
import { JobQueue } from './JobQueue';
|
import { JobQueue } from './JobQueue';
|
||||||
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
||||||
import { DAY, SECOND } from '../util/durations';
|
import { DAY, SECOND } from '../util/durations';
|
||||||
import { commonShouldJobContinue } from './helpers/commonShouldJobContinue';
|
import { commonShouldJobContinue } from './helpers/commonShouldJobContinue';
|
||||||
import { DataReader, DataWriter } from '../sql/Client';
|
import { DataReader, DataWriter } from '../sql/Client';
|
||||||
import type { CallLinkType } from '../types/CallLink';
|
import type { CallLinkType, PendingCallLinkType } from '../types/CallLink';
|
||||||
import { calling } from '../services/calling';
|
import { calling } from '../services/calling';
|
||||||
import { sleeper } from '../util/sleeper';
|
import { sleeper } from '../util/sleeper';
|
||||||
import { parseUnknown } from '../util/schemas';
|
import { parseUnknown } from '../util/schemas';
|
||||||
import { getRoomIdFromRootKey } from '../util/callLinksRingrtc';
|
import { getRoomIdFromRootKey } from '../util/callLinksRingrtc';
|
||||||
import { toCallHistoryFromUnusedCallLink } from '../util/callLinks';
|
import { toCallHistoryFromUnusedCallLink } from '../util/callLinks';
|
||||||
|
import type { StorageServiceFieldsType } from '../sql/Interface';
|
||||||
|
|
||||||
const MAX_RETRY_TIME = DAY;
|
const MAX_RETRY_TIME = DAY;
|
||||||
const MAX_PARALLEL_JOBS = 10;
|
const MAX_PARALLEL_JOBS = 10;
|
||||||
|
@ -45,6 +47,8 @@ export type CallLinkRefreshJobData = z.infer<
|
||||||
export class CallLinkRefreshJobQueue extends JobQueue<CallLinkRefreshJobData> {
|
export class CallLinkRefreshJobQueue extends JobQueue<CallLinkRefreshJobData> {
|
||||||
private parallelQueue = new PQueue({ concurrency: MAX_PARALLEL_JOBS });
|
private parallelQueue = new PQueue({ concurrency: MAX_PARALLEL_JOBS });
|
||||||
|
|
||||||
|
private readonly pendingCallLinks = new Map<string, PendingCallLinkType>();
|
||||||
|
|
||||||
protected override getQueues(): ReadonlySet<PQueue> {
|
protected override getQueues(): ReadonlySet<PQueue> {
|
||||||
return new Set([this.parallelQueue]);
|
return new Set([this.parallelQueue]);
|
||||||
}
|
}
|
||||||
|
@ -59,6 +63,97 @@ export class CallLinkRefreshJobQueue extends JobQueue<CallLinkRefreshJobData> {
|
||||||
return parseUnknown(callLinkRefreshJobDataSchema, data);
|
return parseUnknown(callLinkRefreshJobDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called for every job; wrap it to save pending storage data
|
||||||
|
protected override async enqueueStoredJob(
|
||||||
|
storedJob: Readonly<StoredJob>
|
||||||
|
): Promise<void> {
|
||||||
|
let parsedData: CallLinkRefreshJobData | undefined;
|
||||||
|
try {
|
||||||
|
parsedData = this.parseData(storedJob.data);
|
||||||
|
} catch {
|
||||||
|
// No need to err, it will fail below during super
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
storageID,
|
||||||
|
storageVersion,
|
||||||
|
storageUnknownFields,
|
||||||
|
rootKey,
|
||||||
|
adminKey,
|
||||||
|
} = parsedData ?? {};
|
||||||
|
if (storageID && storageVersion && rootKey) {
|
||||||
|
this.pendingCallLinks.set(rootKey, {
|
||||||
|
rootKey,
|
||||||
|
adminKey: adminKey ?? null,
|
||||||
|
storageID: storageID ?? undefined,
|
||||||
|
storageVersion: storageVersion ?? undefined,
|
||||||
|
storageUnknownFields,
|
||||||
|
storageNeedsSync: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await super.enqueueStoredJob(storedJob);
|
||||||
|
|
||||||
|
if (rootKey) {
|
||||||
|
this.pendingCallLinks.delete(rootKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return pending call links with storageIDs and versions. They're pending because
|
||||||
|
// depending on the refresh result, we will create either CallLinks or DefunctCallLinks,
|
||||||
|
// and we'll save storageID and version onto those records.
|
||||||
|
public getPendingAdminCallLinks(): ReadonlyArray<PendingCallLinkType> {
|
||||||
|
return Array.from(this.pendingCallLinks.values()).filter(
|
||||||
|
callLink => callLink.adminKey != null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasPendingCallLink(rootKey: string): boolean {
|
||||||
|
return this.pendingCallLinks.has(rootKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a new version of storage is uploaded before we get a chance to refresh the
|
||||||
|
// call link, then we need to refresh pending storage fields so when the job
|
||||||
|
// completes it will save with the latest storage fields.
|
||||||
|
public updatePendingCallLinkStorageFields(
|
||||||
|
rootKey: string,
|
||||||
|
storageFields: StorageServiceFieldsType
|
||||||
|
): void {
|
||||||
|
const existingStorageFields = this.pendingCallLinks.get(rootKey);
|
||||||
|
if (!existingStorageFields) {
|
||||||
|
globalLogger.warn(
|
||||||
|
'callLinkRefreshJobQueue.updatePendingCallLinkStorageFields: unknown rootKey'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pendingCallLinks.set(rootKey, {
|
||||||
|
...existingStorageFields,
|
||||||
|
...storageFields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getPendingCallLinkStorageFields(
|
||||||
|
storageID: string,
|
||||||
|
jobData: CallLinkRefreshJobData
|
||||||
|
): StorageServiceFieldsType | undefined {
|
||||||
|
const storageFields = this.pendingCallLinks.get(storageID);
|
||||||
|
if (storageFields) {
|
||||||
|
return {
|
||||||
|
storageID: storageFields.storageID,
|
||||||
|
storageVersion: storageFields.storageVersion,
|
||||||
|
storageUnknownFields: storageFields.storageUnknownFields,
|
||||||
|
storageNeedsSync: storageFields.storageNeedsSync,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
storageID: jobData.storageID ?? undefined,
|
||||||
|
storageVersion: jobData.storageVersion ?? undefined,
|
||||||
|
storageUnknownFields: jobData.storageUnknownFields ?? undefined,
|
||||||
|
storageNeedsSync: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected async run(
|
protected async run(
|
||||||
{
|
{
|
||||||
data,
|
data,
|
||||||
|
@ -102,16 +197,18 @@ export class CallLinkRefreshJobQueue extends JobQueue<CallLinkRefreshJobData> {
|
||||||
window.reduxActions.calling.handleCallLinkUpdateLocal(callLink);
|
window.reduxActions.calling.handleCallLinkUpdateLocal(callLink);
|
||||||
} else {
|
} else {
|
||||||
log.info(`${logId}: Creating new call link`);
|
log.info(`${logId}: Creating new call link`);
|
||||||
const { adminKey, storageID, storageVersion, storageUnknownFields } =
|
const { adminKey } = data;
|
||||||
data;
|
// Refresh the latest storage fields, since they may have changed.
|
||||||
|
const storageFields = this.getPendingCallLinkStorageFields(
|
||||||
|
rootKey,
|
||||||
|
data
|
||||||
|
);
|
||||||
const callLink: CallLinkType = {
|
const callLink: CallLinkType = {
|
||||||
...freshCallLinkState,
|
...freshCallLinkState,
|
||||||
roomId,
|
roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey: adminKey ?? null,
|
adminKey: adminKey ?? null,
|
||||||
storageID: storageID ?? undefined,
|
...storageFields,
|
||||||
storageVersion: storageVersion ?? undefined,
|
|
||||||
storageUnknownFields,
|
|
||||||
storageNeedsSync: false,
|
storageNeedsSync: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -130,10 +227,17 @@ export class CallLinkRefreshJobQueue extends JobQueue<CallLinkRefreshJobData> {
|
||||||
log.info(
|
log.info(
|
||||||
`${logId}: Call link not found on server but absent locally, saving DefunctCallLink`
|
`${logId}: Call link not found on server but absent locally, saving DefunctCallLink`
|
||||||
);
|
);
|
||||||
|
// Refresh the latest storage fields, since they may have changed.
|
||||||
|
const storageFields = this.getPendingCallLinkStorageFields(
|
||||||
|
rootKey,
|
||||||
|
data
|
||||||
|
);
|
||||||
await DataWriter.insertDefunctCallLink({
|
await DataWriter.insertDefunctCallLink({
|
||||||
roomId,
|
roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey: data.adminKey ?? null,
|
adminKey: data.adminKey ?? null,
|
||||||
|
...storageFields,
|
||||||
|
storageNeedsSync: false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.info(
|
log.info(
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
toStickerPackRecord,
|
toStickerPackRecord,
|
||||||
toCallLinkRecord,
|
toCallLinkRecord,
|
||||||
mergeCallLinkRecord,
|
mergeCallLinkRecord,
|
||||||
|
toDefunctOrPendingCallLinkRecord,
|
||||||
} from './storageRecordOps';
|
} from './storageRecordOps';
|
||||||
import type { MergeResultType } from './storageRecordOps';
|
import type { MergeResultType } from './storageRecordOps';
|
||||||
import { MAX_READ_KEYS } from './storageConstants';
|
import { MAX_READ_KEYS } from './storageConstants';
|
||||||
|
@ -71,8 +72,16 @@ import { MY_STORY_ID } from '../types/Stories';
|
||||||
import { isNotNil } from '../util/isNotNil';
|
import { isNotNil } from '../util/isNotNil';
|
||||||
import { isSignalConversation } from '../util/isSignalConversation';
|
import { isSignalConversation } from '../util/isSignalConversation';
|
||||||
import { redactExtendedStorageID, redactStorageID } from '../util/privacy';
|
import { redactExtendedStorageID, redactStorageID } from '../util/privacy';
|
||||||
import type { CallLinkRecord } from '../types/CallLink';
|
import type {
|
||||||
import { callLinkFromRecord } from '../util/callLinksRingrtc';
|
CallLinkRecord,
|
||||||
|
DefunctCallLinkType,
|
||||||
|
PendingCallLinkType,
|
||||||
|
} from '../types/CallLink';
|
||||||
|
import {
|
||||||
|
callLinkFromRecord,
|
||||||
|
getRoomIdFromRootKeyString,
|
||||||
|
} from '../util/callLinksRingrtc';
|
||||||
|
import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue';
|
||||||
|
|
||||||
type IManifestRecordIdentifier = Proto.ManifestRecord.IIdentifier;
|
type IManifestRecordIdentifier = Proto.ManifestRecord.IIdentifier;
|
||||||
|
|
||||||
|
@ -333,6 +342,8 @@ async function generateManifest(
|
||||||
|
|
||||||
const {
|
const {
|
||||||
callLinkDbRecords,
|
callLinkDbRecords,
|
||||||
|
defunctCallLinks,
|
||||||
|
pendingCallLinks,
|
||||||
storyDistributionLists,
|
storyDistributionLists,
|
||||||
installedStickerPacks,
|
installedStickerPacks,
|
||||||
uninstalledStickerPacks,
|
uninstalledStickerPacks,
|
||||||
|
@ -475,6 +486,8 @@ async function generateManifest(
|
||||||
`adding callLinks=${callLinkDbRecords.length}`
|
`adding callLinks=${callLinkDbRecords.length}`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const callLinkRoomIds = new Set<string>();
|
||||||
|
|
||||||
for (const callLinkDbRecord of callLinkDbRecords) {
|
for (const callLinkDbRecord of callLinkDbRecords) {
|
||||||
const { roomId } = callLinkDbRecord;
|
const { roomId } = callLinkDbRecord;
|
||||||
if (callLinkDbRecord.adminKey == null || callLinkDbRecord.rootKey == null) {
|
if (callLinkDbRecord.adminKey == null || callLinkDbRecord.rootKey == null) {
|
||||||
|
@ -487,8 +500,10 @@ async function generateManifest(
|
||||||
|
|
||||||
const storageRecord = new Proto.StorageRecord();
|
const storageRecord = new Proto.StorageRecord();
|
||||||
storageRecord.callLink = toCallLinkRecord(callLinkDbRecord);
|
storageRecord.callLink = toCallLinkRecord(callLinkDbRecord);
|
||||||
|
|
||||||
const callLink = callLinkFromRecord(callLinkDbRecord);
|
const callLink = callLinkFromRecord(callLinkDbRecord);
|
||||||
|
|
||||||
|
callLinkRoomIds.add(callLink.roomId);
|
||||||
|
|
||||||
const { isNewItem, storageID } = processStorageRecord({
|
const { isNewItem, storageID } = processStorageRecord({
|
||||||
currentStorageID: callLink.storageID,
|
currentStorageID: callLink.storageID,
|
||||||
currentStorageVersion: callLink.storageVersion,
|
currentStorageVersion: callLink.storageVersion,
|
||||||
|
@ -521,6 +536,76 @@ async function generateManifest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
`storageService.upload(${version}): ` +
|
||||||
|
`adding defunctCallLinks=${defunctCallLinks.length}`
|
||||||
|
);
|
||||||
|
|
||||||
|
defunctCallLinks.forEach(defunctCallLink => {
|
||||||
|
const storageRecord = new Proto.StorageRecord();
|
||||||
|
storageRecord.callLink = toDefunctOrPendingCallLinkRecord(defunctCallLink);
|
||||||
|
|
||||||
|
callLinkRoomIds.add(defunctCallLink.roomId);
|
||||||
|
|
||||||
|
const { isNewItem, storageID } = processStorageRecord({
|
||||||
|
currentStorageID: defunctCallLink.storageID,
|
||||||
|
currentStorageVersion: defunctCallLink.storageVersion,
|
||||||
|
identifierType: ITEM_TYPE.CALL_LINK,
|
||||||
|
storageNeedsSync: defunctCallLink.storageNeedsSync,
|
||||||
|
storageRecord,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isNewItem) {
|
||||||
|
postUploadUpdateFunctions.push(() => {
|
||||||
|
drop(
|
||||||
|
DataWriter.updateDefunctCallLink({
|
||||||
|
...defunctCallLink,
|
||||||
|
storageID,
|
||||||
|
storageVersion: version,
|
||||||
|
storageNeedsSync: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
`storageService.upload(${version}): ` +
|
||||||
|
`adding pendingCallLinks=${pendingCallLinks.length}`
|
||||||
|
);
|
||||||
|
|
||||||
|
pendingCallLinks.forEach(pendingCallLink => {
|
||||||
|
const storageRecord = new Proto.StorageRecord();
|
||||||
|
storageRecord.callLink = toDefunctOrPendingCallLinkRecord(pendingCallLink);
|
||||||
|
|
||||||
|
const roomId = getRoomIdFromRootKeyString(pendingCallLink.rootKey);
|
||||||
|
if (callLinkRoomIds.has(roomId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isNewItem, storageID } = processStorageRecord({
|
||||||
|
currentStorageID: pendingCallLink.storageID,
|
||||||
|
currentStorageVersion: pendingCallLink.storageVersion,
|
||||||
|
identifierType: ITEM_TYPE.CALL_LINK,
|
||||||
|
storageNeedsSync: pendingCallLink.storageNeedsSync,
|
||||||
|
storageRecord,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isNewItem) {
|
||||||
|
postUploadUpdateFunctions.push(() => {
|
||||||
|
callLinkRefreshJobQueue.updatePendingCallLinkStorageFields(
|
||||||
|
pendingCallLink.rootKey,
|
||||||
|
{
|
||||||
|
...pendingCallLink,
|
||||||
|
storageID,
|
||||||
|
storageVersion: version,
|
||||||
|
storageNeedsSync: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const unknownRecordsArray: ReadonlyArray<UnknownRecord> = (
|
const unknownRecordsArray: ReadonlyArray<UnknownRecord> = (
|
||||||
window.storage.get('storage-service-unknown-records') || []
|
window.storage.get('storage-service-unknown-records') || []
|
||||||
).filter((record: UnknownRecord) => !validRecordTypes.has(record.itemType));
|
).filter((record: UnknownRecord) => !validRecordTypes.has(record.itemType));
|
||||||
|
@ -1145,6 +1230,8 @@ async function mergeRecord(
|
||||||
|
|
||||||
type NonConversationRecordsResultType = Readonly<{
|
type NonConversationRecordsResultType = Readonly<{
|
||||||
callLinkDbRecords: ReadonlyArray<CallLinkRecord>;
|
callLinkDbRecords: ReadonlyArray<CallLinkRecord>;
|
||||||
|
defunctCallLinks: ReadonlyArray<DefunctCallLinkType>;
|
||||||
|
pendingCallLinks: ReadonlyArray<PendingCallLinkType>;
|
||||||
installedStickerPacks: ReadonlyArray<StickerPackType>;
|
installedStickerPacks: ReadonlyArray<StickerPackType>;
|
||||||
uninstalledStickerPacks: ReadonlyArray<UninstalledStickerPackType>;
|
uninstalledStickerPacks: ReadonlyArray<UninstalledStickerPackType>;
|
||||||
storyDistributionLists: ReadonlyArray<StoryDistributionWithMembersType>;
|
storyDistributionLists: ReadonlyArray<StoryDistributionWithMembersType>;
|
||||||
|
@ -1154,11 +1241,15 @@ type NonConversationRecordsResultType = Readonly<{
|
||||||
async function getNonConversationRecords(): Promise<NonConversationRecordsResultType> {
|
async function getNonConversationRecords(): Promise<NonConversationRecordsResultType> {
|
||||||
const [
|
const [
|
||||||
callLinkDbRecords,
|
callLinkDbRecords,
|
||||||
|
defunctCallLinks,
|
||||||
|
pendingCallLinks,
|
||||||
storyDistributionLists,
|
storyDistributionLists,
|
||||||
uninstalledStickerPacks,
|
uninstalledStickerPacks,
|
||||||
installedStickerPacks,
|
installedStickerPacks,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
DataReader.getAllCallLinkRecordsWithAdminKey(),
|
DataReader.getAllCallLinkRecordsWithAdminKey(),
|
||||||
|
DataReader.getAllDefunctCallLinksWithAdminKey(),
|
||||||
|
callLinkRefreshJobQueue.getPendingAdminCallLinks(),
|
||||||
DataReader.getAllStoryDistributionsWithMembers(),
|
DataReader.getAllStoryDistributionsWithMembers(),
|
||||||
DataReader.getUninstalledStickerPacks(),
|
DataReader.getUninstalledStickerPacks(),
|
||||||
DataReader.getInstalledStickerPacks(),
|
DataReader.getInstalledStickerPacks(),
|
||||||
|
@ -1166,6 +1257,8 @@ async function getNonConversationRecords(): Promise<NonConversationRecordsResult
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callLinkDbRecords,
|
callLinkDbRecords,
|
||||||
|
defunctCallLinks,
|
||||||
|
pendingCallLinks,
|
||||||
storyDistributionLists,
|
storyDistributionLists,
|
||||||
uninstalledStickerPacks,
|
uninstalledStickerPacks,
|
||||||
installedStickerPacks,
|
installedStickerPacks,
|
||||||
|
@ -1202,6 +1295,8 @@ async function processManifest(
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
callLinkDbRecords,
|
callLinkDbRecords,
|
||||||
|
defunctCallLinks,
|
||||||
|
pendingCallLinks,
|
||||||
storyDistributionLists,
|
storyDistributionLists,
|
||||||
installedStickerPacks,
|
installedStickerPacks,
|
||||||
uninstalledStickerPacks,
|
uninstalledStickerPacks,
|
||||||
|
@ -1221,6 +1316,12 @@ async function processManifest(
|
||||||
);
|
);
|
||||||
localRecordCount += callLinkDbRecords.length;
|
localRecordCount += callLinkDbRecords.length;
|
||||||
|
|
||||||
|
defunctCallLinks.forEach(collectLocalKeysFromFields);
|
||||||
|
localRecordCount += defunctCallLinks.length;
|
||||||
|
|
||||||
|
pendingCallLinks.forEach(collectLocalKeysFromFields);
|
||||||
|
localRecordCount += pendingCallLinks.length;
|
||||||
|
|
||||||
storyDistributionLists.forEach(collectLocalKeysFromFields);
|
storyDistributionLists.forEach(collectLocalKeysFromFields);
|
||||||
localRecordCount += storyDistributionLists.length;
|
localRecordCount += storyDistributionLists.length;
|
||||||
|
|
||||||
|
@ -1342,6 +1443,8 @@ async function processManifest(
|
||||||
{
|
{
|
||||||
const {
|
const {
|
||||||
callLinkDbRecords,
|
callLinkDbRecords,
|
||||||
|
defunctCallLinks,
|
||||||
|
pendingCallLinks,
|
||||||
storyDistributionLists,
|
storyDistributionLists,
|
||||||
installedStickerPacks,
|
installedStickerPacks,
|
||||||
uninstalledStickerPacks,
|
uninstalledStickerPacks,
|
||||||
|
@ -1454,6 +1557,47 @@ async function processManifest(
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defunctCallLinks.forEach(defunctCallLink => {
|
||||||
|
const { storageID, storageVersion } = defunctCallLink;
|
||||||
|
if (!storageID || remoteKeys.has(storageID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingKey = redactStorageID(storageID, storageVersion);
|
||||||
|
log.info(
|
||||||
|
`storageService.process(${version}): localKey=${missingKey} was not ` +
|
||||||
|
'in remote manifest'
|
||||||
|
);
|
||||||
|
drop(
|
||||||
|
DataWriter.updateDefunctCallLink({
|
||||||
|
...defunctCallLink,
|
||||||
|
storageID: undefined,
|
||||||
|
storageVersion: undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
pendingCallLinks.forEach(pendingCallLink => {
|
||||||
|
const { storageID, storageVersion } = pendingCallLink;
|
||||||
|
if (!storageID || remoteKeys.has(storageID)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const missingKey = redactStorageID(storageID, storageVersion);
|
||||||
|
log.info(
|
||||||
|
`storageService.process(${version}): localKey=${missingKey} was not ` +
|
||||||
|
'in remote manifest'
|
||||||
|
);
|
||||||
|
callLinkRefreshJobQueue.updatePendingCallLinkStorageFields(
|
||||||
|
pendingCallLink.rootKey,
|
||||||
|
{
|
||||||
|
...pendingCallLink,
|
||||||
|
storageID: undefined,
|
||||||
|
storageVersion: undefined,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
|
|
||||||
import { CallLinkRootKey } from '@signalapp/ringrtc';
|
|
||||||
import { uuidToBytes, bytesToUuid } from '../util/uuidToBytes';
|
import { uuidToBytes, bytesToUuid } from '../util/uuidToBytes';
|
||||||
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
|
@ -66,13 +65,18 @@ import { findAndDeleteOnboardingStoryIfExists } from '../util/findAndDeleteOnboa
|
||||||
import { downloadOnboardingStory } from '../util/downloadOnboardingStory';
|
import { downloadOnboardingStory } from '../util/downloadOnboardingStory';
|
||||||
import { drop } from '../util/drop';
|
import { drop } from '../util/drop';
|
||||||
import { redactExtendedStorageID } from '../util/privacy';
|
import { redactExtendedStorageID } from '../util/privacy';
|
||||||
import type { CallLinkRecord } from '../types/CallLink';
|
import type {
|
||||||
|
CallLinkRecord,
|
||||||
|
DefunctCallLinkType,
|
||||||
|
PendingCallLinkType,
|
||||||
|
} from '../types/CallLink';
|
||||||
import {
|
import {
|
||||||
callLinkFromRecord,
|
callLinkFromRecord,
|
||||||
fromRootKeyBytes,
|
fromRootKeyBytes,
|
||||||
getRoomIdFromRootKey,
|
getRoomIdFromRootKeyString,
|
||||||
|
toRootKeyBytes,
|
||||||
} from '../util/callLinksRingrtc';
|
} from '../util/callLinksRingrtc';
|
||||||
import { fromAdminKeyBytes } from '../util/callLinks';
|
import { fromAdminKeyBytes, toAdminKeyBytes } from '../util/callLinks';
|
||||||
import { isOlderThan } from '../util/timestamp';
|
import { isOlderThan } from '../util/timestamp';
|
||||||
import { getMessageQueueTime } from '../util/getMessageQueueTime';
|
import { getMessageQueueTime } from '../util/getMessageQueueTime';
|
||||||
import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue';
|
import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue';
|
||||||
|
@ -643,6 +647,29 @@ export function toCallLinkRecord(
|
||||||
return callLinkRecord;
|
return callLinkRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toDefunctOrPendingCallLinkRecord(
|
||||||
|
callLink: DefunctCallLinkType | PendingCallLinkType
|
||||||
|
): Proto.CallLinkRecord {
|
||||||
|
const rootKey = toRootKeyBytes(callLink.rootKey);
|
||||||
|
const adminKey = callLink.adminKey
|
||||||
|
? toAdminKeyBytes(callLink.adminKey)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
strictAssert(rootKey, 'toDefunctOrPendingCallLinkRecord: no rootKey');
|
||||||
|
strictAssert(adminKey, 'toDefunctOrPendingCallLinkRecord: no adminPasskey');
|
||||||
|
|
||||||
|
const callLinkRecord = new Proto.CallLinkRecord();
|
||||||
|
|
||||||
|
callLinkRecord.rootKey = rootKey;
|
||||||
|
callLinkRecord.adminPasskey = adminKey;
|
||||||
|
|
||||||
|
if (callLink.storageUnknownFields) {
|
||||||
|
callLinkRecord.$unknownFields = [callLink.storageUnknownFields];
|
||||||
|
}
|
||||||
|
|
||||||
|
return callLinkRecord;
|
||||||
|
}
|
||||||
|
|
||||||
type MessageRequestCapableRecord = Proto.IContactRecord | Proto.IGroupV1Record;
|
type MessageRequestCapableRecord = Proto.IContactRecord | Proto.IGroupV1Record;
|
||||||
|
|
||||||
function applyMessageRequestState(
|
function applyMessageRequestState(
|
||||||
|
@ -1967,8 +1994,7 @@ export async function mergeCallLinkRecord(
|
||||||
? fromAdminKeyBytes(callLinkRecord.adminPasskey)
|
? fromAdminKeyBytes(callLinkRecord.adminPasskey)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const callLinkRootKey = CallLinkRootKey.parse(rootKeyString);
|
const roomId = getRoomIdFromRootKeyString(rootKeyString);
|
||||||
const roomId = getRoomIdFromRootKey(callLinkRootKey);
|
|
||||||
const logId = `mergeCallLinkRecord(${redactedStorageID}, ${roomId})`;
|
const logId = `mergeCallLinkRecord(${redactedStorageID}, ${roomId})`;
|
||||||
|
|
||||||
const localCallLinkDbRecord =
|
const localCallLinkDbRecord =
|
||||||
|
@ -2012,6 +2038,17 @@ export async function mergeCallLinkRecord(
|
||||||
);
|
);
|
||||||
} else if (await DataReader.defunctCallLinkExists(roomId)) {
|
} else if (await DataReader.defunctCallLinkExists(roomId)) {
|
||||||
details.push('skipping known defunct call link');
|
details.push('skipping known defunct call link');
|
||||||
|
} else if (callLinkRefreshJobQueue.hasPendingCallLink(storageID)) {
|
||||||
|
details.push('pending call link refresh, updating storage fields');
|
||||||
|
callLinkRefreshJobQueue.updatePendingCallLinkStorageFields(
|
||||||
|
rootKeyString,
|
||||||
|
{
|
||||||
|
storageID,
|
||||||
|
storageVersion,
|
||||||
|
storageUnknownFields: callLinkDbRecord.storageUnknownFields,
|
||||||
|
storageNeedsSync: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
details.push('new call link, enqueueing call link refresh and create');
|
details.push('new call link, enqueueing call link refresh and create');
|
||||||
|
|
||||||
|
|
|
@ -589,6 +589,7 @@ type ReadableInterface = {
|
||||||
getCallLinkRecordByRoomId: (roomId: string) => CallLinkRecord | undefined;
|
getCallLinkRecordByRoomId: (roomId: string) => CallLinkRecord | undefined;
|
||||||
getAllAdminCallLinks(): ReadonlyArray<CallLinkType>;
|
getAllAdminCallLinks(): ReadonlyArray<CallLinkType>;
|
||||||
getAllCallLinkRecordsWithAdminKey(): ReadonlyArray<CallLinkRecord>;
|
getAllCallLinkRecordsWithAdminKey(): ReadonlyArray<CallLinkRecord>;
|
||||||
|
getAllDefunctCallLinksWithAdminKey(): ReadonlyArray<DefunctCallLinkType>;
|
||||||
getAllMarkedDeletedCallLinkRoomIds(): ReadonlyArray<string>;
|
getAllMarkedDeletedCallLinkRoomIds(): ReadonlyArray<string>;
|
||||||
getMessagesBetween: (
|
getMessagesBetween: (
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
|
@ -823,7 +824,8 @@ type WritableInterface = {
|
||||||
deleteCallLinkAndHistory(roomId: string): void;
|
deleteCallLinkAndHistory(roomId: string): void;
|
||||||
finalizeDeleteCallLink(roomId: string): void;
|
finalizeDeleteCallLink(roomId: string): void;
|
||||||
_removeAllCallLinks(): void;
|
_removeAllCallLinks(): void;
|
||||||
insertDefunctCallLink(callLink: DefunctCallLinkType): void;
|
insertDefunctCallLink(defunctCallLink: DefunctCallLinkType): void;
|
||||||
|
updateDefunctCallLink(defunctCallLink: DefunctCallLinkType): void;
|
||||||
deleteCallLinkFromSync(roomId: string): void;
|
deleteCallLinkFromSync(roomId: string): void;
|
||||||
migrateConversationMessages: (obsoleteId: string, currentId: string) => void;
|
migrateConversationMessages: (obsoleteId: string, currentId: string) => void;
|
||||||
saveEditedMessage: (
|
saveEditedMessage: (
|
||||||
|
|
|
@ -185,12 +185,14 @@ import {
|
||||||
deleteCallLinkAndHistory,
|
deleteCallLinkAndHistory,
|
||||||
getAllAdminCallLinks,
|
getAllAdminCallLinks,
|
||||||
getAllCallLinkRecordsWithAdminKey,
|
getAllCallLinkRecordsWithAdminKey,
|
||||||
|
getAllDefunctCallLinksWithAdminKey,
|
||||||
getAllMarkedDeletedCallLinkRoomIds,
|
getAllMarkedDeletedCallLinkRoomIds,
|
||||||
finalizeDeleteCallLink,
|
finalizeDeleteCallLink,
|
||||||
beginDeleteCallLink,
|
beginDeleteCallLink,
|
||||||
deleteCallLinkFromSync,
|
deleteCallLinkFromSync,
|
||||||
_removeAllCallLinks,
|
_removeAllCallLinks,
|
||||||
insertDefunctCallLink,
|
insertDefunctCallLink,
|
||||||
|
updateDefunctCallLink,
|
||||||
} from './server/callLinks';
|
} from './server/callLinks';
|
||||||
import {
|
import {
|
||||||
replaceAllEndorsementsForGroup,
|
replaceAllEndorsementsForGroup,
|
||||||
|
@ -321,6 +323,7 @@ export const DataReader: ServerReadableInterface = {
|
||||||
getCallLinkRecordByRoomId,
|
getCallLinkRecordByRoomId,
|
||||||
getAllAdminCallLinks,
|
getAllAdminCallLinks,
|
||||||
getAllCallLinkRecordsWithAdminKey,
|
getAllCallLinkRecordsWithAdminKey,
|
||||||
|
getAllDefunctCallLinksWithAdminKey,
|
||||||
getAllMarkedDeletedCallLinkRoomIds,
|
getAllMarkedDeletedCallLinkRoomIds,
|
||||||
getMessagesBetween,
|
getMessagesBetween,
|
||||||
getNearbyMessageFromDeletedSet,
|
getNearbyMessageFromDeletedSet,
|
||||||
|
@ -464,6 +467,7 @@ export const DataWriter: ServerWritableInterface = {
|
||||||
_removeAllCallLinks,
|
_removeAllCallLinks,
|
||||||
deleteCallLinkFromSync,
|
deleteCallLinkFromSync,
|
||||||
insertDefunctCallLink,
|
insertDefunctCallLink,
|
||||||
|
updateDefunctCallLink,
|
||||||
migrateConversationMessages,
|
migrateConversationMessages,
|
||||||
saveEditedMessage,
|
saveEditedMessage,
|
||||||
saveEditedMessages,
|
saveEditedMessages,
|
||||||
|
|
28
ts/sql/migrations/1250-defunct-call-links-storage.ts
Normal file
28
ts/sql/migrations/1250-defunct-call-links-storage.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// 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 = 1250;
|
||||||
|
|
||||||
|
export function updateToSchemaVersion1250(
|
||||||
|
currentVersion: number,
|
||||||
|
db: Database,
|
||||||
|
logger: LoggerType
|
||||||
|
): void {
|
||||||
|
if (currentVersion >= 1250) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
db.exec(`
|
||||||
|
ALTER TABLE defunctCallLinks ADD COLUMN storageID TEXT;
|
||||||
|
ALTER TABLE defunctCallLinks ADD COLUMN storageVersion INTEGER;
|
||||||
|
ALTER TABLE defunctCallLinks ADD COLUMN storageUnknownFields BLOB;
|
||||||
|
ALTER TABLE defunctCallLinks ADD COLUMN storageNeedsSync INTEGER NOT NULL DEFAULT 0;
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.pragma('user_version = 1250');
|
||||||
|
})();
|
||||||
|
logger.info('updateToSchemaVersion1250: success!');
|
||||||
|
}
|
|
@ -100,10 +100,11 @@ import { updateToSchemaVersion1200 } from './1200-attachment-download-source-ind
|
||||||
import { updateToSchemaVersion1210 } from './1210-call-history-started-id';
|
import { updateToSchemaVersion1210 } from './1210-call-history-started-id';
|
||||||
import { updateToSchemaVersion1220 } from './1220-blob-sessions';
|
import { updateToSchemaVersion1220 } from './1220-blob-sessions';
|
||||||
import { updateToSchemaVersion1230 } from './1230-call-links-admin-key-index';
|
import { updateToSchemaVersion1230 } from './1230-call-links-admin-key-index';
|
||||||
|
import { updateToSchemaVersion1240 } from './1240-defunct-call-links-table';
|
||||||
import {
|
import {
|
||||||
updateToSchemaVersion1240,
|
updateToSchemaVersion1250,
|
||||||
version as MAX_VERSION,
|
version as MAX_VERSION,
|
||||||
} from './1240-defunct-call-links-table';
|
} from './1250-defunct-call-links-storage';
|
||||||
|
|
||||||
function updateToSchemaVersion1(
|
function updateToSchemaVersion1(
|
||||||
currentVersion: number,
|
currentVersion: number,
|
||||||
|
@ -2073,6 +2074,7 @@ export const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion1220,
|
updateToSchemaVersion1220,
|
||||||
updateToSchemaVersion1230,
|
updateToSchemaVersion1230,
|
||||||
updateToSchemaVersion1240,
|
updateToSchemaVersion1240,
|
||||||
|
updateToSchemaVersion1250,
|
||||||
];
|
];
|
||||||
|
|
||||||
export class DBVersionFromFutureError extends Error {
|
export class DBVersionFromFutureError extends Error {
|
||||||
|
|
|
@ -11,12 +11,14 @@ import type {
|
||||||
import {
|
import {
|
||||||
callLinkRestrictionsSchema,
|
callLinkRestrictionsSchema,
|
||||||
callLinkRecordSchema,
|
callLinkRecordSchema,
|
||||||
|
defunctCallLinkRecordSchema,
|
||||||
} from '../../types/CallLink';
|
} from '../../types/CallLink';
|
||||||
import { toAdminKeyBytes } from '../../util/callLinks';
|
import { toAdminKeyBytes } from '../../util/callLinks';
|
||||||
import {
|
import {
|
||||||
callLinkToRecord,
|
callLinkToRecord,
|
||||||
callLinkFromRecord,
|
callLinkFromRecord,
|
||||||
toRootKeyBytes,
|
defunctCallLinkToRecord,
|
||||||
|
defunctCallLinkFromRecord,
|
||||||
} from '../../util/callLinksRingrtc';
|
} from '../../util/callLinksRingrtc';
|
||||||
import type { ReadableDB, WritableDB } from '../Interface';
|
import type { ReadableDB, WritableDB } from '../Interface';
|
||||||
import { prepare } from '../Server';
|
import { prepare } from '../Server';
|
||||||
|
@ -388,31 +390,73 @@ export function defunctCallLinkExists(db: ReadableDB, roomId: string): boolean {
|
||||||
return db.prepare(query).pluck(true).get(params) === 1;
|
return db.prepare(query).pluck(true).get(params) === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAllDefunctCallLinksWithAdminKey(
|
||||||
|
db: ReadableDB
|
||||||
|
): ReadonlyArray<DefunctCallLinkType> {
|
||||||
|
const [query] = sql`
|
||||||
|
SELECT *
|
||||||
|
FROM defunctCallLinks
|
||||||
|
WHERE adminKey IS NOT NULL;
|
||||||
|
`;
|
||||||
|
return db
|
||||||
|
.prepare(query)
|
||||||
|
.all()
|
||||||
|
.map((item: unknown) =>
|
||||||
|
defunctCallLinkFromRecord(parseUnknown(defunctCallLinkRecordSchema, item))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function insertDefunctCallLink(
|
export function insertDefunctCallLink(
|
||||||
db: WritableDB,
|
db: WritableDB,
|
||||||
callLink: DefunctCallLinkType
|
defunctCallLink: DefunctCallLinkType
|
||||||
): void {
|
): void {
|
||||||
const { roomId, rootKey } = callLink;
|
const { roomId, rootKey } = defunctCallLink;
|
||||||
assertRoomIdMatchesRootKey(roomId, rootKey);
|
assertRoomIdMatchesRootKey(roomId, rootKey);
|
||||||
|
|
||||||
const rootKeyData = toRootKeyBytes(callLink.rootKey);
|
const data = defunctCallLinkToRecord(defunctCallLink);
|
||||||
const adminKeyData = callLink.adminKey
|
|
||||||
? toAdminKeyBytes(callLink.adminKey)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
prepare(
|
prepare(
|
||||||
db,
|
db,
|
||||||
`
|
`
|
||||||
INSERT INTO defunctCallLinks (
|
INSERT INTO defunctCallLinks (
|
||||||
roomId,
|
roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey
|
adminKey,
|
||||||
|
storageID,
|
||||||
|
storageVersion,
|
||||||
|
storageUnknownFields,
|
||||||
|
storageNeedsSync
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$roomId,
|
$roomId,
|
||||||
$rootKeyData,
|
$rootKey,
|
||||||
$adminKeyData
|
$adminKey,
|
||||||
|
$storageID,
|
||||||
|
$storageVersion,
|
||||||
|
$storageUnknownFields,
|
||||||
|
$storageNeedsSync
|
||||||
)
|
)
|
||||||
ON CONFLICT (roomId) DO NOTHING;
|
ON CONFLICT (roomId) DO NOTHING;
|
||||||
`
|
`
|
||||||
).run({ roomId, rootKeyData, adminKeyData });
|
).run(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateDefunctCallLink(
|
||||||
|
db: WritableDB,
|
||||||
|
defunctCallLink: DefunctCallLinkType
|
||||||
|
): void {
|
||||||
|
const { roomId, rootKey } = defunctCallLink;
|
||||||
|
assertRoomIdMatchesRootKey(roomId, rootKey);
|
||||||
|
|
||||||
|
const data = defunctCallLinkToRecord(defunctCallLink);
|
||||||
|
// Do not write roomId or rootKey since they should never change
|
||||||
|
db.prepare(
|
||||||
|
`
|
||||||
|
UPDATE callLinks
|
||||||
|
SET
|
||||||
|
storageID = $storageID,
|
||||||
|
storageVersion = $storageVersion,
|
||||||
|
storageUnknownFields = $storageUnknownFields,
|
||||||
|
storageNeedsSync = $storageNeedsSync
|
||||||
|
WHERE roomId = $roomId
|
||||||
|
`
|
||||||
|
).run(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,13 +83,41 @@ export type CallLinkConversationType = ReadonlyDeep<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
// Call link discovered from sync, waiting to refresh state from the calling server
|
||||||
|
export type PendingCallLinkType = Readonly<{
|
||||||
|
rootKey: string;
|
||||||
|
adminKey: string | null;
|
||||||
|
}> &
|
||||||
|
StorageServiceFieldsType;
|
||||||
|
|
||||||
// Call links discovered missing after server refresh
|
// Call links discovered missing after server refresh
|
||||||
export type DefunctCallLinkType = Readonly<{
|
export type DefunctCallLinkType = Readonly<{
|
||||||
roomId: string;
|
roomId: string;
|
||||||
rootKey: string;
|
rootKey: string;
|
||||||
adminKey: string | null;
|
adminKey: string | null;
|
||||||
|
}> &
|
||||||
|
StorageServiceFieldsType;
|
||||||
|
|
||||||
|
export type DefunctCallLinkRecord = Readonly<{
|
||||||
|
roomId: string;
|
||||||
|
rootKey: Uint8Array;
|
||||||
|
adminKey: Uint8Array | null;
|
||||||
|
storageID: string | null;
|
||||||
|
storageVersion: number | null;
|
||||||
|
storageUnknownFields: Uint8Array | null;
|
||||||
|
storageNeedsSync: 1 | 0;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export const defunctCallLinkRecordSchema = z.object({
|
||||||
|
roomId: z.string(),
|
||||||
|
rootKey: z.instanceof(Uint8Array),
|
||||||
|
adminKey: z.instanceof(Uint8Array).nullable(),
|
||||||
|
storageID: z.string().nullable(),
|
||||||
|
storageVersion: z.number().int().nullable(),
|
||||||
|
storageUnknownFields: z.instanceof(Uint8Array).nullable(),
|
||||||
|
storageNeedsSync: z.union([z.literal(1), z.literal(0)]),
|
||||||
|
}) satisfies z.ZodType<DefunctCallLinkRecord>;
|
||||||
|
|
||||||
// DB Record
|
// DB Record
|
||||||
export type CallLinkRecord = Readonly<{
|
export type CallLinkRecord = Readonly<{
|
||||||
roomId: string;
|
roomId: string;
|
||||||
|
|
|
@ -12,11 +12,14 @@ import type {
|
||||||
CallLinkRecord,
|
CallLinkRecord,
|
||||||
CallLinkRestrictions,
|
CallLinkRestrictions,
|
||||||
CallLinkType,
|
CallLinkType,
|
||||||
|
DefunctCallLinkRecord,
|
||||||
|
DefunctCallLinkType,
|
||||||
} from '../types/CallLink';
|
} from '../types/CallLink';
|
||||||
import {
|
import {
|
||||||
type CallLinkStateType,
|
type CallLinkStateType,
|
||||||
CallLinkNameMaxByteLength,
|
CallLinkNameMaxByteLength,
|
||||||
callLinkRecordSchema,
|
callLinkRecordSchema,
|
||||||
|
defunctCallLinkRecordSchema,
|
||||||
toCallLinkRestrictions,
|
toCallLinkRestrictions,
|
||||||
} from '../types/CallLink';
|
} from '../types/CallLink';
|
||||||
import { unicodeSlice } from './unicodeSlice';
|
import { unicodeSlice } from './unicodeSlice';
|
||||||
|
@ -64,6 +67,11 @@ export function getRoomIdFromRootKey(rootKey: CallLinkRootKey): string {
|
||||||
return rootKey.deriveRoomId().toString('hex');
|
return rootKey.deriveRoomId().toString('hex');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRoomIdFromRootKeyString(rootKeyString: string): string {
|
||||||
|
const callLinkRootKey = CallLinkRootKey.parse(rootKeyString);
|
||||||
|
return getRoomIdFromRootKey(callLinkRootKey);
|
||||||
|
}
|
||||||
|
|
||||||
export function getCallLinkRootKeyFromUrlKey(key: string): Uint8Array {
|
export function getCallLinkRootKeyFromUrlKey(key: string): Uint8Array {
|
||||||
// Returns `Buffer` which inherits from `Uint8Array`
|
// Returns `Buffer` which inherits from `Uint8Array`
|
||||||
return CallLinkRootKey.parse(key).bytes;
|
return CallLinkRootKey.parse(key).bytes;
|
||||||
|
@ -167,3 +175,45 @@ export function callLinkToRecord(callLink: CallLinkType): CallLinkRecord {
|
||||||
storageNeedsSync: callLink.storageNeedsSync ? 1 : 0,
|
storageNeedsSync: callLink.storageNeedsSync ? 1 : 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function defunctCallLinkFromRecord(
|
||||||
|
record: DefunctCallLinkRecord
|
||||||
|
): DefunctCallLinkType {
|
||||||
|
if (record.rootKey == null) {
|
||||||
|
throw new Error('CallLink.defunctCallLinkFromRecord: rootKey is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootKey = fromRootKeyBytes(record.rootKey);
|
||||||
|
const adminKey = record.adminKey ? fromAdminKeyBytes(record.adminKey) : null;
|
||||||
|
return {
|
||||||
|
roomId: record.roomId,
|
||||||
|
rootKey,
|
||||||
|
adminKey,
|
||||||
|
storageID: record.storageID || undefined,
|
||||||
|
storageVersion: record.storageVersion || undefined,
|
||||||
|
storageUnknownFields: record.storageUnknownFields || undefined,
|
||||||
|
storageNeedsSync: record.storageNeedsSync === 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defunctCallLinkToRecord(
|
||||||
|
defunctCallLink: DefunctCallLinkType
|
||||||
|
): DefunctCallLinkRecord {
|
||||||
|
if (defunctCallLink.rootKey == null) {
|
||||||
|
throw new Error('CallLink.defunctCallLinkToRecord: rootKey is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
const rootKey = toRootKeyBytes(defunctCallLink.rootKey);
|
||||||
|
const adminKey = defunctCallLink.adminKey
|
||||||
|
? toAdminKeyBytes(defunctCallLink.adminKey)
|
||||||
|
: null;
|
||||||
|
return parseStrict(defunctCallLinkRecordSchema, {
|
||||||
|
roomId: defunctCallLink.roomId,
|
||||||
|
rootKey,
|
||||||
|
adminKey,
|
||||||
|
storageID: defunctCallLink.storageID || null,
|
||||||
|
storageVersion: defunctCallLink.storageVersion || null,
|
||||||
|
storageUnknownFields: defunctCallLink.storageUnknownFields || null,
|
||||||
|
storageNeedsSync: defunctCallLink.storageNeedsSync ? 1 : 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue