Migrate schema to service ids

This commit is contained in:
Fedor Indutny 2023-08-16 22:54:39 +02:00 committed by Jamie Kyle
parent 71958f8a01
commit 8b0da36caa
258 changed files with 4795 additions and 2613 deletions

View file

@ -40,7 +40,7 @@ export namespace BodyRange {
export const { Style } = Proto.DataMessage.BodyRange;
export type Mention = {
mentionUuid: AciString;
mentionAci: AciString;
};
export type Link = {
url: string;
@ -68,7 +68,7 @@ export namespace BodyRange {
bodyRange: BodyRange<T>
): bodyRange is X {
// satisfies keyof Mention
return ('mentionUuid' as const) in bodyRange;
return ('mentionAci' as const) in bodyRange;
}
export function isFormatting(
bodyRange: BodyRange<object>
@ -181,14 +181,12 @@ export function filterAndClean(
return undefined;
}
let mentionUuid: AciString | undefined;
let mentionAci: AciString | undefined;
if ('mentionAci' in range && range.mentionAci) {
mentionUuid = normalizeAci(range.mentionAci, 'BodyRange.mentionAci');
} else if ('mentionUuid' in range && range.mentionUuid) {
mentionUuid = normalizeAci(range.mentionUuid, 'BodyRange.mentionUuid');
mentionAci = normalizeAci(range.mentionAci, 'BodyRange.mentionAci');
}
if (mentionUuid) {
if (mentionAci) {
countByTypeRecord[MENTION_NAME] += 1;
if (countByTypeRecord[MENTION_NAME] > MAX_PER_TYPE) {
return undefined;
@ -198,7 +196,7 @@ export function filterAndClean(
...restOfRange,
start,
length,
mentionUuid,
mentionAci,
};
}
if ('style' in range && range.style) {
@ -230,7 +228,7 @@ export function hydrateRanges(
return filterAndClean(ranges)?.map(range => {
if (BodyRange.isMention(range)) {
const conversation = conversationSelector(range.mentionUuid);
const conversation = conversationSelector(range.mentionAci);
return {
...range,

View file

@ -4,7 +4,8 @@
import { z } from 'zod';
import Long from 'long';
import { CallMode } from './Calling';
import type { ServiceIdString } from './ServiceId';
import type { AciString } from './ServiceId';
import { aciSchema } from './ServiceId';
import { bytesToUuid } from '../util/uuidToBytes';
import { SignalService as Proto } from '../protobuf';
import * as Bytes from '../Bytes';
@ -67,8 +68,8 @@ export type CallStatus = DirectCallStatus | GroupCallStatus;
export type CallDetails = Readonly<{
callId: string;
peerId: ServiceIdString | string;
ringerId: string | null;
peerId: AciString | string;
ringerId: AciString | string | null;
mode: CallMode;
type: CallType;
direction: CallDirection;
@ -95,7 +96,7 @@ export type CallHistoryGroup = Omit<CallHistoryDetails, 'callId' | 'ringerId'> &
export type GroupCallMeta = Readonly<{
callId: string;
ringerId: string;
ringerId: string | AciString;
}>;
export enum CallHistoryFilterStatus {
@ -118,7 +119,7 @@ export type CallHistoryPagination = Readonly<{
limit: number;
}>;
const ringerIdSchema = z.union([z.string(), z.null()]);
const ringerIdSchema = z.union([aciSchema, z.string(), z.null()]);
const callModeSchema = z.nativeEnum(CallMode);
const callTypeSchema = z.nativeEnum(CallType);

View file

@ -3,6 +3,7 @@
import type { AudioDevice } from '@signalapp/ringrtc';
import type { ConversationType } from '../state/ducks/conversations';
import type { ServiceIdString } from './ServiceId';
// These are strings (1) for the database (2) for Storybook.
export enum CallMode {
@ -59,7 +60,10 @@ export type ActiveDirectCallType = ActiveCallBaseType & {
hasRemoteVideo: boolean;
presenting: boolean;
title: string;
uuid?: string;
// Note that the field name/type has to match the
// GroupCallRemoteParticipantType below (which is based on
// ConversationType).
serviceId?: ServiceIdString;
}
];
};

View file

@ -31,7 +31,7 @@ type GenericEmbeddedContactType<AvatarType> = {
// Populated by selector
firstNumber?: string;
uuid?: ServiceIdString;
serviceId?: ServiceIdString;
};
export type EmbeddedContactType = GenericEmbeddedContactType<Avatar>;
@ -149,11 +149,12 @@ export function embeddedContactSelector(
options: {
regionCode?: string;
firstNumber?: string;
uuid?: ServiceIdString;
serviceId?: ServiceIdString;
getAbsoluteAttachmentPath: (path: string) => string;
}
): EmbeddedContactType {
const { getAbsoluteAttachmentPath, firstNumber, uuid, regionCode } = options;
const { getAbsoluteAttachmentPath, firstNumber, serviceId, regionCode } =
options;
let { avatar } = contact;
if (avatar && avatar.avatar) {
@ -175,7 +176,7 @@ export function embeddedContactSelector(
return {
...contact,
firstNumber,
uuid,
serviceId,
avatar,
number:
contact.number &&

View file

@ -28,7 +28,7 @@ export type PanelRequestType =
contact: EmbeddedContactType;
signalAccount?: {
phoneNumber: string;
uuid: ServiceIdString;
serviceId: ServiceIdString;
};
};
}
@ -50,7 +50,7 @@ export type PanelRenderType =
contact: EmbeddedContactType;
signalAccount?: {
phoneNumber: string;
uuid: ServiceIdString;
serviceId: ServiceIdString;
};
};
}

View file

@ -1,12 +1,14 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { AciString } from './ServiceId';
export type ReactionType = Readonly<{
conversationId: string;
emoji: string;
fromId: string;
messageId: string;
messageReceivedAt: number;
targetAuthorUuid: string;
targetAuthorAci: AciString;
targetTimestamp: number;
}>;

View file

@ -3,11 +3,13 @@
import { z } from 'zod';
import { aciSchema } from './ServiceId';
export const receiptSchema = z.object({
messageId: z.string(),
conversationId: z.string(),
senderE164: z.string().optional(),
senderUuid: z.string().optional(),
senderAci: aciSchema.optional(),
timestamp: z.number(),
isDirectConversation: z.boolean().optional(),
});

View file

@ -2,6 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { v4 as generateUuid } from 'uuid';
import { z } from 'zod';
import { Aci, Pni, ServiceId } from '@signalapp/libsignal-client';
import { isValidUuid } from '../util/isValidUuid';
import * as log from '../logging/log';
@ -14,20 +16,22 @@ export enum ServiceIdKind {
}
export type PniString = string & { __pni: never };
export type UntaggedPniString = string & { __pni: never };
export type UntaggedPniString = string & { __untagged_pni: never };
export type AciString = string & { __aci: never };
export type ServiceIdString = PniString | AciString;
export function isServiceIdString(value?: string): value is ServiceIdString {
export function isServiceIdString(
value?: string | null
): value is ServiceIdString {
return isAciString(value) || isPniString(value);
}
export function isAciString(value?: string): value is AciString {
export function isAciString(value?: string | null): value is AciString {
return isValidUuid(value);
}
export function isPniString(value?: string): value is PniString {
if (value === undefined) {
export function isPniString(value?: string | null): value is PniString {
if (value == null) {
return false;
}
@ -35,12 +39,11 @@ export function isPniString(value?: string): value is PniString {
return true;
}
// Legacy IDs
return isValidUuid(value);
return false;
}
export function isUntaggedPniString(
value?: string
value?: string | null
): value is UntaggedPniString {
return isValidUuid(value);
}
@ -52,8 +55,24 @@ export function toTaggedPni(untagged: UntaggedPniString): PniString {
export function normalizeServiceId(
rawServiceId: string,
context: string,
logger?: Pick<LoggerType, 'warn'>
): ServiceIdString;
export function normalizeServiceId(
rawServiceId: string | undefined | null,
context: string,
logger?: Pick<LoggerType, 'warn'>
): ServiceIdString | undefined;
export function normalizeServiceId(
rawServiceId: string | undefined | null,
context: string,
logger: Pick<LoggerType, 'warn'> = log
): ServiceIdString {
): ServiceIdString | undefined {
if (rawServiceId == null) {
return undefined;
}
const result = rawServiceId.toLowerCase().replace(/^pni:/, 'PNI:');
if (!isAciString(result) && !isPniString(result)) {
@ -71,8 +90,24 @@ export function normalizeServiceId(
export function normalizeAci(
rawAci: string,
context: string,
logger?: Pick<LoggerType, 'warn'>
): AciString;
export function normalizeAci(
rawAci: string | undefined | null,
context: string,
logger?: Pick<LoggerType, 'warn'>
): AciString | undefined;
export function normalizeAci(
rawAci: string | undefined | null,
context: string,
logger: Pick<LoggerType, 'warn'> = log
): AciString {
): AciString | undefined {
if (rawAci == null) {
return undefined;
}
const result = rawAci.toLowerCase();
if (!isAciString(result)) {
@ -90,8 +125,24 @@ export function normalizeAci(
export function normalizePni(
rawPni: string,
context: string,
logger?: Pick<LoggerType, 'warn'>
): PniString;
export function normalizePni(
rawPni: string | undefined | null,
context: string,
logger?: Pick<LoggerType, 'warn'>
): PniString | undefined;
export function normalizePni(
rawPni: string | undefined | null,
context: string,
logger: Pick<LoggerType, 'warn'> = log
): PniString {
): PniString | undefined {
if (rawPni == null) {
return undefined;
}
const result = rawPni.toLowerCase().replace(/^pni:/, 'PNI:');
if (!isPniString(result)) {
@ -122,3 +173,49 @@ export function getAciFromPrefix(prefix: string): AciString {
}
return `${padded}-0000-4000-8000-${'0'.repeat(12)}` as AciString;
}
export const aciSchema = z
.string()
.refine(isAciString)
.transform(x => {
if (!isAciString(x)) {
throw new Error('Refine did not throw!');
}
return x;
});
export const serviceIdSchema = z
.string()
.refine(isServiceIdString)
.transform(x => {
if (!isServiceIdString(x)) {
throw new Error('Refine did not throw!');
}
return x;
});
export function toServiceIdObject(serviceId: ServiceIdString): ServiceId {
return ServiceId.parseFromServiceIdString(serviceId);
}
export function toAciObject(aci: AciString): Aci {
return Aci.parseFromServiceIdString(aci);
}
export function toPniObject(pni: PniString): Pni {
return Pni.parseFromServiceIdString(pni);
}
// Note: getServiceIdString() returns normalized string so we can cast it
// without normalizing.
export function fromServiceIdObject(obj: ServiceId): ServiceIdString {
return obj.getServiceIdString() as ServiceIdString;
}
export function fromAciObject(obj: Aci): AciString {
return obj.getServiceIdString() as AciString;
}
export function fromPniObject(obj: Pni): PniString {
return obj.getServiceIdString() as PniString;
}

View file

@ -94,7 +94,7 @@ export type StoryViewType = {
| 'profileName'
| 'sharedGroupNames'
| 'title'
| 'uuid'
| 'serviceId'
>;
sendState?: Array<StorySendStateType>;
timestamp: number;

View file

@ -67,3 +67,19 @@ export type UnwrapPromise<Value> = Value extends Promise<infer T> ? T : Value;
export type BytesToStrings<Value> = Value extends Uint8Array
? string
: { [Key in keyof Value]: BytesToStrings<Value[Key]> };
export type JSONWithUnknownFields<Value> = Value extends Record<
string | symbol | number,
unknown
>
? Readonly<
{
[Key in keyof Value]: JSONWithUnknownFields<Value[Key]>;
} & {
// Make sure that rest property is required to handle.
__rest: never;
}
>
: Value extends Array<infer E>
? ReadonlyArray<JSONWithUnknownFields<E>>
: Value;