Add schema utils
This commit is contained in:
parent
c8a729f8be
commit
b26466e59d
45 changed files with 674 additions and 151 deletions
|
@ -9,6 +9,7 @@ import LRU from 'lru-cache';
|
||||||
|
|
||||||
import type { OptionalResourceService } from './OptionalResourceService';
|
import type { OptionalResourceService } from './OptionalResourceService';
|
||||||
import { SignalService as Proto } from '../ts/protobuf';
|
import { SignalService as Proto } from '../ts/protobuf';
|
||||||
|
import { parseUnknown } from '../ts/util/schemas';
|
||||||
|
|
||||||
const MANIFEST_PATH = join(__dirname, '..', 'build', 'jumbomoji.json');
|
const MANIFEST_PATH = join(__dirname, '..', 'build', 'jumbomoji.json');
|
||||||
|
|
||||||
|
@ -64,8 +65,9 @@ export class EmojiService {
|
||||||
public static async create(
|
public static async create(
|
||||||
resourceService: OptionalResourceService
|
resourceService: OptionalResourceService
|
||||||
): Promise<EmojiService> {
|
): Promise<EmojiService> {
|
||||||
const json = await readFile(MANIFEST_PATH, 'utf8');
|
const contents = await readFile(MANIFEST_PATH, 'utf8');
|
||||||
const manifest = manifestSchema.parse(JSON.parse(json));
|
const json: unknown = JSON.parse(contents);
|
||||||
|
const manifest = parseUnknown(manifestSchema, json);
|
||||||
return new EmojiService(resourceService, manifest);
|
return new EmojiService(resourceService, manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { OptionalResourcesDictSchema } from '../ts/types/OptionalResource';
|
||||||
import * as log from '../ts/logging/log';
|
import * as log from '../ts/logging/log';
|
||||||
import { getGotOptions } from '../ts/updater/got';
|
import { getGotOptions } from '../ts/updater/got';
|
||||||
import { drop } from '../ts/util/drop';
|
import { drop } from '../ts/util/drop';
|
||||||
|
import { parseUnknown } from '../ts/util/schemas';
|
||||||
|
|
||||||
const RESOURCES_DICT_PATH = join(
|
const RESOURCES_DICT_PATH = join(
|
||||||
__dirname,
|
__dirname,
|
||||||
|
@ -106,8 +107,10 @@ export class OptionalResourceService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = JSON.parse(await readFile(RESOURCES_DICT_PATH, 'utf8'));
|
const json: unknown = JSON.parse(
|
||||||
this.maybeDeclaration = OptionalResourcesDictSchema.parse(json);
|
await readFile(RESOURCES_DICT_PATH, 'utf8')
|
||||||
|
);
|
||||||
|
this.maybeDeclaration = parseUnknown(OptionalResourcesDictSchema, json);
|
||||||
|
|
||||||
// Clean unknown resources
|
// Clean unknown resources
|
||||||
let subPaths: Array<string>;
|
let subPaths: Array<string>;
|
||||||
|
|
|
@ -56,6 +56,7 @@ import {
|
||||||
isVideoTypeSupported,
|
isVideoTypeSupported,
|
||||||
} from '../ts/util/GoogleChrome';
|
} from '../ts/util/GoogleChrome';
|
||||||
import { decryptAttachmentV2ToSink } from '../ts/AttachmentCrypto';
|
import { decryptAttachmentV2ToSink } from '../ts/AttachmentCrypto';
|
||||||
|
import { parseLoose } from '../ts/util/schemas';
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
|
@ -471,7 +472,7 @@ export async function handleAttachmentRequest(req: Request): Promise<Response> {
|
||||||
let disposition: z.infer<typeof dispositionSchema> = 'attachment';
|
let disposition: z.infer<typeof dispositionSchema> = 'attachment';
|
||||||
const dispositionParam = url.searchParams.get('disposition');
|
const dispositionParam = url.searchParams.get('disposition');
|
||||||
if (dispositionParam != null) {
|
if (dispositionParam != null) {
|
||||||
disposition = dispositionSchema.parse(dispositionParam);
|
disposition = parseLoose(dispositionSchema, dispositionParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
strictAssert(attachmentsDir != null, 'not initialized');
|
strictAssert(attachmentsDir != null, 'not initialized');
|
||||||
|
|
|
@ -12,6 +12,7 @@ import * as Errors from '../ts/types/errors';
|
||||||
import { isProduction } from '../ts/util/version';
|
import { isProduction } from '../ts/util/version';
|
||||||
import { isNotNil } from '../ts/util/isNotNil';
|
import { isNotNil } from '../ts/util/isNotNil';
|
||||||
import OS from '../ts/util/os/osMain';
|
import OS from '../ts/util/os/osMain';
|
||||||
|
import { parseUnknown } from '../ts/util/schemas';
|
||||||
|
|
||||||
// See https://github.com/rust-minidump/rust-minidump/blob/main/minidump-processor/json-schema.md
|
// See https://github.com/rust-minidump/rust-minidump/blob/main/minidump-processor/json-schema.md
|
||||||
const dumpString = z.string().or(z.null()).optional();
|
const dumpString = z.string().or(z.null()).optional();
|
||||||
|
@ -120,9 +121,8 @@ export function setup(
|
||||||
pendingDumps.map(async fullPath => {
|
pendingDumps.map(async fullPath => {
|
||||||
const content = await readFile(fullPath);
|
const content = await readFile(fullPath);
|
||||||
try {
|
try {
|
||||||
const dump = dumpSchema.parse(
|
const json: unknown = JSON.parse(dumpToJSONString(content));
|
||||||
JSON.parse(dumpToJSONString(content))
|
const dump = parseUnknown(dumpSchema, json);
|
||||||
);
|
|
||||||
if (dump.crash_info?.type !== 'Simulated Exception') {
|
if (dump.crash_info?.type !== 'Simulated Exception') {
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,8 @@ export function setup(
|
||||||
const content = await readFile(fullPath);
|
const content = await readFile(fullPath);
|
||||||
const { mtime } = await stat(fullPath);
|
const { mtime } = await stat(fullPath);
|
||||||
|
|
||||||
const dump = dumpSchema.parse(JSON.parse(dumpToJSONString(content)));
|
const json: unknown = JSON.parse(dumpToJSONString(content));
|
||||||
|
const dump = parseUnknown(dumpSchema, json);
|
||||||
|
|
||||||
if (dump.crash_info?.type === 'Simulated Exception') {
|
if (dump.crash_info?.type === 'Simulated Exception') {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { join } from 'path';
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import { DNSFallbackSchema } from '../ts/types/DNSFallback';
|
import { DNSFallbackSchema } from '../ts/types/DNSFallback';
|
||||||
import type { DNSFallbackType } from '../ts/types/DNSFallback';
|
import type { DNSFallbackType } from '../ts/types/DNSFallback';
|
||||||
|
import { parseUnknown } from '../ts/util/schemas';
|
||||||
|
|
||||||
let cached: DNSFallbackType | undefined;
|
let cached: DNSFallbackType | undefined;
|
||||||
|
|
||||||
|
@ -25,9 +26,9 @@ export async function getDNSFallback(): Promise<DNSFallbackType> {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = JSON.parse(str);
|
const json: unknown = JSON.parse(str);
|
||||||
|
|
||||||
const result = DNSFallbackSchema.parse(json);
|
const result = parseUnknown(DNSFallbackSchema, json);
|
||||||
cached = result;
|
cached = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import type { LoggerType } from '../ts/types/Logging';
|
||||||
import type { HourCyclePreference, LocaleMessagesType } from '../ts/types/I18N';
|
import type { HourCyclePreference, LocaleMessagesType } from '../ts/types/I18N';
|
||||||
import type { LocalizerType } from '../ts/types/Util';
|
import type { LocalizerType } from '../ts/types/Util';
|
||||||
import * as Errors from '../ts/types/errors';
|
import * as Errors from '../ts/types/errors';
|
||||||
|
import { parseUnknown } from '../ts/util/schemas';
|
||||||
|
|
||||||
const TextInfoSchema = z.object({
|
const TextInfoSchema = z.object({
|
||||||
direction: z.enum(['ltr', 'rtl']),
|
direction: z.enum(['ltr', 'rtl']),
|
||||||
|
@ -70,16 +71,18 @@ function getLocaleDirection(
|
||||||
try {
|
try {
|
||||||
// @ts-expect-error -- TS doesn't know about this method
|
// @ts-expect-error -- TS doesn't know about this method
|
||||||
if (typeof locale.getTextInfo === 'function') {
|
if (typeof locale.getTextInfo === 'function') {
|
||||||
return TextInfoSchema.parse(
|
return parseUnknown(
|
||||||
|
TextInfoSchema,
|
||||||
// @ts-expect-error -- TS doesn't know about this method
|
// @ts-expect-error -- TS doesn't know about this method
|
||||||
locale.getTextInfo()
|
locale.getTextInfo() as unknown
|
||||||
).direction;
|
).direction;
|
||||||
}
|
}
|
||||||
// @ts-expect-error -- TS doesn't know about this property
|
// @ts-expect-error -- TS doesn't know about this property
|
||||||
if (typeof locale.textInfo === 'object') {
|
if (typeof locale.textInfo === 'object') {
|
||||||
return TextInfoSchema.parse(
|
return parseUnknown(
|
||||||
|
TextInfoSchema,
|
||||||
// @ts-expect-error -- TS doesn't know about this property
|
// @ts-expect-error -- TS doesn't know about this property
|
||||||
locale.textInfo
|
locale.textInfo as unknown
|
||||||
).direction;
|
).direction;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -123,6 +123,7 @@ import { ZoomFactorService } from '../ts/services/ZoomFactorService';
|
||||||
import { SafeStorageBackendChangeError } from '../ts/types/SafeStorageBackendChangeError';
|
import { SafeStorageBackendChangeError } from '../ts/types/SafeStorageBackendChangeError';
|
||||||
import { LINUX_PASSWORD_STORE_FLAGS } from '../ts/util/linuxPasswordStoreFlags';
|
import { LINUX_PASSWORD_STORE_FLAGS } from '../ts/util/linuxPasswordStoreFlags';
|
||||||
import { getOwn } from '../ts/util/getOwn';
|
import { getOwn } from '../ts/util/getOwn';
|
||||||
|
import { safeParseLoose, safeParseUnknown } from '../ts/util/schemas';
|
||||||
|
|
||||||
const animationSettings = systemPreferences.getAnimationSettings();
|
const animationSettings = systemPreferences.getAnimationSettings();
|
||||||
|
|
||||||
|
@ -436,7 +437,8 @@ export const windowConfigSchema = z.object({
|
||||||
type WindowConfigType = z.infer<typeof windowConfigSchema>;
|
type WindowConfigType = z.infer<typeof windowConfigSchema>;
|
||||||
|
|
||||||
let windowConfig: WindowConfigType | undefined;
|
let windowConfig: WindowConfigType | undefined;
|
||||||
const windowConfigParsed = windowConfigSchema.safeParse(
|
const windowConfigParsed = safeParseUnknown(
|
||||||
|
windowConfigSchema,
|
||||||
windowFromEphemeral || windowFromUserConfig
|
windowFromEphemeral || windowFromUserConfig
|
||||||
);
|
);
|
||||||
if (windowConfigParsed.success) {
|
if (windowConfigParsed.success) {
|
||||||
|
@ -2692,7 +2694,7 @@ ipc.on('delete-all-data', () => {
|
||||||
ipc.on('get-config', async event => {
|
ipc.on('get-config', async event => {
|
||||||
const theme = await getResolvedThemeSetting();
|
const theme = await getResolvedThemeSetting();
|
||||||
|
|
||||||
const directoryConfig = directoryConfigSchema.safeParse({
|
const directoryConfig = safeParseLoose(directoryConfigSchema, {
|
||||||
directoryUrl: config.get<string | null>('directoryUrl') || undefined,
|
directoryUrl: config.get<string | null>('directoryUrl') || undefined,
|
||||||
directoryMRENCLAVE:
|
directoryMRENCLAVE:
|
||||||
config.get<string | null>('directoryMRENCLAVE') || undefined,
|
config.get<string | null>('directoryMRENCLAVE') || undefined,
|
||||||
|
@ -2705,7 +2707,7 @@ ipc.on('get-config', async event => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = rendererConfigSchema.safeParse({
|
const parsed = safeParseLoose(rendererConfigSchema, {
|
||||||
name: packageJson.productName,
|
name: packageJson.productName,
|
||||||
availableLocales: getResolvedMessagesLocale().availableLocales,
|
availableLocales: getResolvedMessagesLocale().availableLocales,
|
||||||
resolvedTranslationsLocale: getResolvedMessagesLocale().name,
|
resolvedTranslationsLocale: getResolvedMessagesLocale().name,
|
||||||
|
|
14
patches/zod+3.22.3.patch
Normal file
14
patches/zod+3.22.3.patch
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
diff --git a/node_modules/zod/lib/types.d.ts b/node_modules/zod/lib/types.d.ts
|
||||||
|
index 0ece6e8..57bbe86 100644
|
||||||
|
--- a/node_modules/zod/lib/types.d.ts
|
||||||
|
+++ b/node_modules/zod/lib/types.d.ts
|
||||||
|
@@ -56,7 +56,9 @@ export declare abstract class ZodType<Output = any, Def extends ZodTypeDef = Zod
|
||||||
|
};
|
||||||
|
_parseSync(input: ParseInput): SyncParseReturnType<Output>;
|
||||||
|
_parseAsync(input: ParseInput): AsyncParseReturnType<Output>;
|
||||||
|
+ /** @deprecated (Signal Desktop: Use ts/util/schema.ts instead) */
|
||||||
|
parse(data: unknown, params?: Partial<ParseParams>): Output;
|
||||||
|
+ /** @deprecated (Signal Desktop: Use ts/util/schema.ts instead) */
|
||||||
|
safeParse(data: unknown, params?: Partial<ParseParams>): SafeParseReturnType<Input, Output>;
|
||||||
|
parseAsync(data: unknown, params?: Partial<ParseParams>): Promise<Output>;
|
||||||
|
safeParseAsync(data: unknown, params?: Partial<ParseParams>): Promise<SafeParseReturnType<Input, Output>>;
|
|
@ -67,6 +67,7 @@ import {
|
||||||
SIGNED_PRE_KEY_ID_KEY,
|
SIGNED_PRE_KEY_ID_KEY,
|
||||||
} from './textsecure/AccountManager';
|
} from './textsecure/AccountManager';
|
||||||
import { formatGroups, groupWhile } from './util/groupWhile';
|
import { formatGroups, groupWhile } from './util/groupWhile';
|
||||||
|
import { parseUnknown } from './util/schemas';
|
||||||
|
|
||||||
const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds
|
const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds
|
||||||
const LOW_KEYS_THRESHOLD = 25;
|
const LOW_KEYS_THRESHOLD = 25;
|
||||||
|
@ -99,7 +100,7 @@ const identityKeySchema = z.object({
|
||||||
|
|
||||||
function validateIdentityKey(attrs: unknown): attrs is IdentityKeyType {
|
function validateIdentityKey(attrs: unknown): attrs is IdentityKeyType {
|
||||||
// We'll throw if this doesn't match
|
// We'll throw if this doesn't match
|
||||||
identityKeySchema.parse(attrs);
|
parseUnknown(identityKeySchema, attrs);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -9,6 +9,7 @@ import * as log from '../logging/log';
|
||||||
import type { BadgeType, BadgeImageType } from './types';
|
import type { BadgeType, BadgeImageType } from './types';
|
||||||
import { parseBadgeCategory } from './BadgeCategory';
|
import { parseBadgeCategory } from './BadgeCategory';
|
||||||
import { BadgeImageTheme, parseBadgeImageTheme } from './BadgeImageTheme';
|
import { BadgeImageTheme, parseBadgeImageTheme } from './BadgeImageTheme';
|
||||||
|
import { safeParseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const MAX_BADGES = 1000;
|
const MAX_BADGES = 1000;
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ export function parseBoostBadgeListFromServer(
|
||||||
): Record<string, BadgeType> {
|
): Record<string, BadgeType> {
|
||||||
const result: Record<string, BadgeType> = {};
|
const result: Record<string, BadgeType> = {};
|
||||||
|
|
||||||
const parseResult = boostBadgesFromServerSchema.safeParse(value);
|
const parseResult = safeParseUnknown(boostBadgesFromServerSchema, value);
|
||||||
if (!parseResult.success) {
|
if (!parseResult.success) {
|
||||||
log.warn(
|
log.warn(
|
||||||
'parseBoostBadgeListFromServer: server response was invalid:',
|
'parseBoostBadgeListFromServer: server response was invalid:',
|
||||||
|
@ -73,7 +74,7 @@ export function parseBadgeFromServer(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
updatesUrl: string
|
updatesUrl: string
|
||||||
): BadgeType | undefined {
|
): BadgeType | undefined {
|
||||||
const parseResult = badgeFromServerSchema.safeParse(value);
|
const parseResult = safeParseUnknown(badgeFromServerSchema, value);
|
||||||
if (!parseResult.success) {
|
if (!parseResult.success) {
|
||||||
log.warn(
|
log.warn(
|
||||||
'parseBadgeFromServer: badge was invalid:',
|
'parseBadgeFromServer: badge was invalid:',
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { Input } from './Input';
|
||||||
import { AutoSizeTextArea } from './AutoSizeTextArea';
|
import { AutoSizeTextArea } from './AutoSizeTextArea';
|
||||||
import { Button, ButtonVariant } from './Button';
|
import { Button, ButtonVariant } from './Button';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { safeParsePartial } from '../util/schemas';
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
nickname: z
|
nickname: z
|
||||||
|
@ -67,7 +68,7 @@ export function EditNicknameAndNoteModal({
|
||||||
const familyNameValue = toOptionalStringValue(familyName);
|
const familyNameValue = toOptionalStringValue(familyName);
|
||||||
const noteValue = toOptionalStringValue(note);
|
const noteValue = toOptionalStringValue(note);
|
||||||
const hasEitherName = givenNameValue != null || familyNameValue != null;
|
const hasEitherName = givenNameValue != null || familyNameValue != null;
|
||||||
return formSchema.safeParse({
|
return safeParsePartial(formSchema, {
|
||||||
nickname: hasEitherName
|
nickname: hasEitherName
|
||||||
? { givenName: givenNameValue, familyName: familyNameValue }
|
? { givenName: givenNameValue, familyName: familyNameValue }
|
||||||
: null,
|
: null,
|
||||||
|
|
|
@ -45,6 +45,7 @@ import type { MIMEType } from '../types/MIME';
|
||||||
import { AttachmentDownloadSource } from '../sql/Interface';
|
import { AttachmentDownloadSource } from '../sql/Interface';
|
||||||
import { drop } from '../util/drop';
|
import { drop } from '../util/drop';
|
||||||
import { getAttachmentCiphertextLength } from '../AttachmentCrypto';
|
import { getAttachmentCiphertextLength } from '../AttachmentCrypto';
|
||||||
|
import { safeParsePartial } from '../util/schemas';
|
||||||
|
|
||||||
export enum AttachmentDownloadUrgency {
|
export enum AttachmentDownloadUrgency {
|
||||||
IMMEDIATE = 'immediate',
|
IMMEDIATE = 'immediate',
|
||||||
|
@ -175,7 +176,7 @@ export class AttachmentDownloadManager extends JobManager<CoreAttachmentDownload
|
||||||
source,
|
source,
|
||||||
urgency = AttachmentDownloadUrgency.STANDARD,
|
urgency = AttachmentDownloadUrgency.STANDARD,
|
||||||
} = newJobData;
|
} = newJobData;
|
||||||
const parseResult = coreAttachmentDownloadJobSchema.safeParse({
|
const parseResult = safeParsePartial(coreAttachmentDownloadJobSchema, {
|
||||||
messageId,
|
messageId,
|
||||||
receivedAt,
|
receivedAt,
|
||||||
sentAt,
|
sentAt,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { DataReader, DataWriter } from '../sql/Client';
|
||||||
import type { CallLinkType } from '../types/CallLink';
|
import type { CallLinkType } 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';
|
||||||
|
|
||||||
const MAX_RETRY_TIME = DAY;
|
const MAX_RETRY_TIME = DAY;
|
||||||
const MAX_PARALLEL_JOBS = 5;
|
const MAX_PARALLEL_JOBS = 5;
|
||||||
|
@ -46,7 +47,7 @@ export class CallLinkRefreshJobQueue extends JobQueue<CallLinkRefreshJobData> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseData(data: unknown): CallLinkRefreshJobData {
|
protected parseData(data: unknown): CallLinkRefreshJobData {
|
||||||
return callLinkRefreshJobDataSchema.parse(data);
|
return parseUnknown(callLinkRefreshJobDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async run(
|
protected async run(
|
||||||
|
|
|
@ -50,6 +50,7 @@ import { drop } from '../util/drop';
|
||||||
import { isInPast } from '../util/timestamp';
|
import { isInPast } from '../util/timestamp';
|
||||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||||
import { FIBONACCI } from '../util/BackOff';
|
import { FIBONACCI } from '../util/BackOff';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
// Note: generally, we only want to add to this list. If you do need to change one of
|
// Note: generally, we only want to add to this list. If you do need to change one of
|
||||||
// these values, you'll likely need to write a database migration.
|
// these values, you'll likely need to write a database migration.
|
||||||
|
@ -415,7 +416,7 @@ export class ConversationJobQueue extends JobQueue<ConversationQueueJobData> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseData(data: unknown): ConversationQueueJobData {
|
protected parseData(data: unknown): ConversationQueueJobData {
|
||||||
return conversationQueueJobDataSchema.parse(data);
|
return parseUnknown(conversationQueueJobDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override getInMemoryQueue({
|
protected override getInMemoryQueue({
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { DataWriter } from '../sql/Client';
|
||||||
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 { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const groupAvatarJobDataSchema = z.object({
|
const groupAvatarJobDataSchema = z.object({
|
||||||
conversationId: z.string(),
|
conversationId: z.string(),
|
||||||
|
@ -20,7 +21,7 @@ export type GroupAvatarJobData = z.infer<typeof groupAvatarJobDataSchema>;
|
||||||
|
|
||||||
export class GroupAvatarJobQueue extends JobQueue<GroupAvatarJobData> {
|
export class GroupAvatarJobQueue extends JobQueue<GroupAvatarJobData> {
|
||||||
protected parseData(data: unknown): GroupAvatarJobData {
|
protected parseData(data: unknown): GroupAvatarJobData {
|
||||||
return groupAvatarJobDataSchema.parse(data);
|
return parseUnknown(groupAvatarJobDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async run(
|
protected async run(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import type { JOB_STATUS } from './JobQueue';
|
||||||
import { JobQueue } from './JobQueue';
|
import { JobQueue } from './JobQueue';
|
||||||
|
|
||||||
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
import { jobQueueDatabaseStore } from './JobQueueDatabaseStore';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const removeStorageKeyJobDataSchema = z.object({
|
const removeStorageKeyJobDataSchema = z.object({
|
||||||
key: z.enum([
|
key: z.enum([
|
||||||
|
@ -22,7 +23,7 @@ type RemoveStorageKeyJobData = z.infer<typeof removeStorageKeyJobDataSchema>;
|
||||||
|
|
||||||
export class RemoveStorageKeyJobQueue extends JobQueue<RemoveStorageKeyJobData> {
|
export class RemoveStorageKeyJobQueue extends JobQueue<RemoveStorageKeyJobData> {
|
||||||
protected parseData(data: unknown): RemoveStorageKeyJobData {
|
protected parseData(data: unknown): RemoveStorageKeyJobData {
|
||||||
return removeStorageKeyJobDataSchema.parse(data);
|
return parseUnknown(removeStorageKeyJobDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async run({
|
protected async run({
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { parseIntWithFallback } from '../util/parseIntWithFallback';
|
||||||
import type { WebAPIType } from '../textsecure/WebAPI';
|
import type { WebAPIType } from '../textsecure/WebAPI';
|
||||||
import { HTTPError } from '../textsecure/Errors';
|
import { HTTPError } from '../textsecure/Errors';
|
||||||
import { sleeper } from '../util/sleeper';
|
import { sleeper } from '../util/sleeper';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const RETRY_WAIT_TIME = durations.MINUTE;
|
const RETRY_WAIT_TIME = durations.MINUTE;
|
||||||
const RETRYABLE_4XX_FAILURE_STATUSES = new Set([
|
const RETRYABLE_4XX_FAILURE_STATUSES = new Set([
|
||||||
|
@ -44,7 +45,7 @@ export class ReportSpamJobQueue extends JobQueue<ReportSpamJobData> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseData(data: unknown): ReportSpamJobData {
|
protected parseData(data: unknown): ReportSpamJobData {
|
||||||
return reportSpamJobDataSchema.parse(data);
|
return parseUnknown(reportSpamJobDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async run(
|
protected async run(
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
} from './helpers/handleMultipleSendErrors';
|
} from './helpers/handleMultipleSendErrors';
|
||||||
import { isConversationUnregistered } from '../util/isConversationUnregistered';
|
import { isConversationUnregistered } from '../util/isConversationUnregistered';
|
||||||
import { isConversationAccepted } from '../util/isConversationAccepted';
|
import { isConversationAccepted } from '../util/isConversationAccepted';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const MAX_RETRY_TIME = DAY;
|
const MAX_RETRY_TIME = DAY;
|
||||||
const MAX_PARALLEL_JOBS = 5;
|
const MAX_PARALLEL_JOBS = 5;
|
||||||
|
@ -43,7 +44,7 @@ export class SingleProtoJobQueue extends JobQueue<SingleProtoJobData> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected parseData(data: unknown): SingleProtoJobData {
|
protected parseData(data: unknown): SingleProtoJobData {
|
||||||
return singleProtoJobDataSchema.parse(data);
|
return parseUnknown(singleProtoJobDataSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async run(
|
protected async run(
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { getUserAgent } from '../util/getUserAgent';
|
||||||
import { maybeParseUrl } from '../util/url';
|
import { maybeParseUrl } from '../util/url';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import type { LoggerType } from '../types/Logging';
|
import type { LoggerType } from '../types/Logging';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const BASE_URL = 'https://debuglogs.org';
|
const BASE_URL = 'https://debuglogs.org';
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ const tokenBodySchema = z
|
||||||
const parseTokenBody = (
|
const parseTokenBody = (
|
||||||
rawBody: unknown
|
rawBody: unknown
|
||||||
): { fields: Record<string, unknown>; url: string } => {
|
): { fields: Record<string, unknown>; url: string } => {
|
||||||
const body = tokenBodySchema.parse(rawBody);
|
const body = parseUnknown(tokenBodySchema, rawBody);
|
||||||
|
|
||||||
const parsedUrl = maybeParseUrl(body.url);
|
const parsedUrl = maybeParseUrl(body.url);
|
||||||
if (!parsedUrl) {
|
if (!parsedUrl) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { parse } from 'csv-parse';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { _getAvailableLocales } from '../../app/locale';
|
import { _getAvailableLocales } from '../../app/locale';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const type = process.argv[2];
|
const type = process.argv[2];
|
||||||
if (type !== 'countries' && type !== 'locales') {
|
if (type !== 'countries' && type !== 'locales') {
|
||||||
|
@ -119,7 +120,7 @@ function assertValuesForAllCountries(result: LocaleDisplayNamesResult) {
|
||||||
async function main() {
|
async function main() {
|
||||||
const contents = await fs.readFile(localeDisplayNamesDataPath, 'utf-8');
|
const contents = await fs.readFile(localeDisplayNamesDataPath, 'utf-8');
|
||||||
const records = await parseCsv(contents);
|
const records = await parseCsv(contents);
|
||||||
const data = LocaleDisplayNames.parse(records);
|
const data = parseUnknown(LocaleDisplayNames, records as unknown);
|
||||||
const result = convertData(data);
|
const result = convertData(data);
|
||||||
if (type === 'locales') {
|
if (type === 'locales') {
|
||||||
assertValuesForAllLocales(result);
|
assertValuesForAllLocales(result);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import prettier from 'prettier';
|
||||||
|
|
||||||
import type { OptionalResourceType } from '../types/OptionalResource';
|
import type { OptionalResourceType } from '../types/OptionalResource';
|
||||||
import { OptionalResourcesDictSchema } from '../types/OptionalResource';
|
import { OptionalResourcesDictSchema } from '../types/OptionalResource';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const MANIFEST_URL =
|
const MANIFEST_URL =
|
||||||
'https://updates.signal.org/dynamic/android/emoji/search/manifest.json';
|
'https://updates.signal.org/dynamic/android/emoji/search/manifest.json';
|
||||||
|
@ -29,7 +30,7 @@ async function fetchJSON(url: string): Promise<unknown> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const manifest = ManifestSchema.parse(await fetchJSON(MANIFEST_URL));
|
const manifest = parseUnknown(ManifestSchema, await fetchJSON(MANIFEST_URL));
|
||||||
|
|
||||||
// eslint-disable-next-line dot-notation
|
// eslint-disable-next-line dot-notation
|
||||||
manifest.languageToSmartlingLocale['zh_TW'] = 'zh-Hant';
|
manifest.languageToSmartlingLocale['zh_TW'] = 'zh-Hant';
|
||||||
|
@ -75,8 +76,9 @@ async function main(): Promise<void> {
|
||||||
'build',
|
'build',
|
||||||
'optional-resources.json'
|
'optional-resources.json'
|
||||||
);
|
);
|
||||||
const resources = OptionalResourcesDictSchema.parse(
|
const resources = parseUnknown(
|
||||||
JSON.parse(await readFile(resourcesPath, 'utf8'))
|
OptionalResourcesDictSchema,
|
||||||
|
JSON.parse(await readFile(resourcesPath, 'utf8')) as unknown
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const [locale, resource] of extraResources) {
|
for (const [locale, resource] of extraResources) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import prettier from 'prettier';
|
||||||
|
|
||||||
import type { OptionalResourceType } from '../types/OptionalResource';
|
import type { OptionalResourceType } from '../types/OptionalResource';
|
||||||
import { OptionalResourcesDictSchema } from '../types/OptionalResource';
|
import { OptionalResourcesDictSchema } from '../types/OptionalResource';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const VERSION = 10;
|
const VERSION = 10;
|
||||||
|
|
||||||
|
@ -28,7 +29,10 @@ async function fetchJSON(url: string): Promise<unknown> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const { jumbomoji } = ManifestSchema.parse(await fetchJSON(MANIFEST_URL));
|
const { jumbomoji } = parseUnknown(
|
||||||
|
ManifestSchema,
|
||||||
|
await fetchJSON(MANIFEST_URL)
|
||||||
|
);
|
||||||
|
|
||||||
const extraResources = new Map<string, OptionalResourceType>();
|
const extraResources = new Map<string, OptionalResourceType>();
|
||||||
|
|
||||||
|
@ -68,8 +72,9 @@ async function main(): Promise<void> {
|
||||||
'build',
|
'build',
|
||||||
'optional-resources.json'
|
'optional-resources.json'
|
||||||
);
|
);
|
||||||
const resources = OptionalResourcesDictSchema.parse(
|
const resources = parseUnknown(
|
||||||
JSON.parse(await readFile(resourcesPath, 'utf8'))
|
OptionalResourcesDictSchema,
|
||||||
|
JSON.parse(await readFile(resourcesPath, 'utf8')) as unknown
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const [sheet, resource] of extraResources) {
|
for (const [sheet, resource] of extraResources) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import logSymbols from 'log-symbols';
|
||||||
import { explodePromise } from '../util/explodePromise';
|
import { explodePromise } from '../util/explodePromise';
|
||||||
import { missingCaseError } from '../util/missingCaseError';
|
import { missingCaseError } from '../util/missingCaseError';
|
||||||
import { SECOND } from '../util/durations';
|
import { SECOND } from '../util/durations';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const ROOT_DIR = join(__dirname, '..', '..');
|
const ROOT_DIR = join(__dirname, '..', '..');
|
||||||
|
|
||||||
|
@ -137,7 +138,10 @@ async function launchElectron(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = eventSchema.parse(JSON.parse(match[1]));
|
const event = parseUnknown(
|
||||||
|
eventSchema,
|
||||||
|
JSON.parse(match[1]) as unknown
|
||||||
|
);
|
||||||
if (event.type === 'pass') {
|
if (event.type === 'pass') {
|
||||||
pass += 1;
|
pass += 1;
|
||||||
|
|
||||||
|
|
|
@ -17,9 +17,10 @@ import type { WebAPIType } from '../textsecure/WebAPI';
|
||||||
import { SignalService as Proto } from '../protobuf';
|
import { SignalService as Proto } from '../protobuf';
|
||||||
|
|
||||||
import SenderCertificate = Proto.SenderCertificate;
|
import SenderCertificate = Proto.SenderCertificate;
|
||||||
|
import { safeParseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
function isWellFormed(data: unknown): data is SerializedCertificateType {
|
function isWellFormed(data: unknown): data is SerializedCertificateType {
|
||||||
return serializedCertificateSchema.safeParse(data).success;
|
return safeParseUnknown(serializedCertificateSchema, data).success;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case your clock is different from the server's, we "fake" expire certificates early.
|
// In case your clock is different from the server's, we "fake" expire certificates early.
|
||||||
|
|
|
@ -207,6 +207,7 @@ import {
|
||||||
} from '../types/AttachmentBackup';
|
} from '../types/AttachmentBackup';
|
||||||
import { redactGenericText } from '../util/privacy';
|
import { redactGenericText } from '../util/privacy';
|
||||||
import { getAttachmentCiphertextLength } from '../AttachmentCrypto';
|
import { getAttachmentCiphertextLength } from '../AttachmentCrypto';
|
||||||
|
import { parseStrict, parseUnknown, safeParseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
type ConversationRow = Readonly<{
|
type ConversationRow = Readonly<{
|
||||||
json: string;
|
json: string;
|
||||||
|
@ -3664,7 +3665,7 @@ function getCallHistory(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callHistoryDetailsSchema.parse(row);
|
return parseUnknown(callHistoryDetailsSchema, row as unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SEEN_STATUS_UNSEEN = sqlConstant(SeenStatus.Unseen);
|
const SEEN_STATUS_UNSEEN = sqlConstant(SeenStatus.Unseen);
|
||||||
|
@ -3746,7 +3747,7 @@ function getCallHistoryForCallLogEventTarget(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callHistoryDetailsSchema.parse(row);
|
return parseUnknown(callHistoryDetailsSchema, row as unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConversationIdForCallHistory(
|
function getConversationIdForCallHistory(
|
||||||
|
@ -4110,7 +4111,7 @@ function getCallHistoryGroupsCount(
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return countSchema.parse(result);
|
return parseUnknown(countSchema, result as unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupsDataSchema = z.array(
|
const groupsDataSchema = z.array(
|
||||||
|
@ -4135,7 +4136,8 @@ function getCallHistoryGroups(
|
||||||
// getCallHistoryGroupData creates a temporary table and thus requires
|
// getCallHistoryGroupData creates a temporary table and thus requires
|
||||||
// write access.
|
// write access.
|
||||||
const writable = toUnsafeWritableDB(db, 'only temp table use');
|
const writable = toUnsafeWritableDB(db, 'only temp table use');
|
||||||
const groupsData = groupsDataSchema.parse(
|
const groupsData = parseUnknown(
|
||||||
|
groupsDataSchema,
|
||||||
getCallHistoryGroupData(writable, false, filter, pagination)
|
getCallHistoryGroupData(writable, false, filter, pagination)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -4145,8 +4147,9 @@ function getCallHistoryGroups(
|
||||||
.map(groupData => {
|
.map(groupData => {
|
||||||
return {
|
return {
|
||||||
...groupData,
|
...groupData,
|
||||||
possibleChildren: possibleChildrenSchema.parse(
|
possibleChildren: parseUnknown(
|
||||||
JSON.parse(groupData.possibleChildren)
|
possibleChildrenSchema,
|
||||||
|
JSON.parse(groupData.possibleChildren) as unknown
|
||||||
),
|
),
|
||||||
inPeriod: new Set(groupData.inPeriod.split(',')),
|
inPeriod: new Set(groupData.inPeriod.split(',')),
|
||||||
};
|
};
|
||||||
|
@ -4167,7 +4170,7 @@ function getCallHistoryGroups(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return callHistoryGroupSchema.parse({ ...rest, type, children });
|
return parseStrict(callHistoryGroupSchema, { ...rest, type, children });
|
||||||
})
|
})
|
||||||
.reverse();
|
.reverse();
|
||||||
}
|
}
|
||||||
|
@ -4788,14 +4791,14 @@ function getAttachmentDownloadJob(
|
||||||
|
|
||||||
function removeAllBackupAttachmentDownloadJobs(db: WritableDB): void {
|
function removeAllBackupAttachmentDownloadJobs(db: WritableDB): void {
|
||||||
const [query, params] = sql`
|
const [query, params] = sql`
|
||||||
DELETE FROM attachment_downloads
|
DELETE FROM attachment_downloads
|
||||||
WHERE source = ${AttachmentDownloadSource.BACKUP_IMPORT};`;
|
WHERE source = ${AttachmentDownloadSource.BACKUP_IMPORT};`;
|
||||||
db.prepare(query).run(params);
|
db.prepare(query).run(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSizeOfPendingBackupAttachmentDownloadJobs(db: ReadableDB): number {
|
function getSizeOfPendingBackupAttachmentDownloadJobs(db: ReadableDB): number {
|
||||||
const [query, params] = sql`
|
const [query, params] = sql`
|
||||||
SELECT SUM(ciphertextSize) FROM attachment_downloads
|
SELECT SUM(ciphertextSize) FROM attachment_downloads
|
||||||
WHERE source = ${AttachmentDownloadSource.BACKUP_IMPORT};`;
|
WHERE source = ${AttachmentDownloadSource.BACKUP_IMPORT};`;
|
||||||
return db.prepare(query).pluck().get(params);
|
return db.prepare(query).pluck().get(params);
|
||||||
}
|
}
|
||||||
|
@ -4842,7 +4845,7 @@ function getNextAttachmentDownloadJobs(
|
||||||
})
|
})
|
||||||
AND
|
AND
|
||||||
messageId IN (${sqlJoin(prioritizeMessageIds)})
|
messageId IN (${sqlJoin(prioritizeMessageIds)})
|
||||||
AND
|
AND
|
||||||
${sourceWhereFragment}
|
${sourceWhereFragment}
|
||||||
-- for priority messages, let's load them oldest first; this helps, e.g. for stories where we
|
-- for priority messages, let's load them oldest first; this helps, e.g. for stories where we
|
||||||
-- want the oldest one first
|
-- want the oldest one first
|
||||||
|
@ -4862,7 +4865,7 @@ function getNextAttachmentDownloadJobs(
|
||||||
active = 0
|
active = 0
|
||||||
AND
|
AND
|
||||||
(retryAfter is NULL OR retryAfter <= ${timestamp})
|
(retryAfter is NULL OR retryAfter <= ${timestamp})
|
||||||
AND
|
AND
|
||||||
${sourceWhereFragment}
|
${sourceWhereFragment}
|
||||||
ORDER BY receivedAt DESC
|
ORDER BY receivedAt DESC
|
||||||
LIMIT ${numJobsRemaining}
|
LIMIT ${numJobsRemaining}
|
||||||
|
@ -4876,14 +4879,14 @@ function getNextAttachmentDownloadJobs(
|
||||||
try {
|
try {
|
||||||
return allJobs.map(row => {
|
return allJobs.map(row => {
|
||||||
try {
|
try {
|
||||||
return attachmentDownloadJobSchema.parse({
|
return parseUnknown(attachmentDownloadJobSchema, {
|
||||||
...row,
|
...row,
|
||||||
active: Boolean(row.active),
|
active: Boolean(row.active),
|
||||||
attachment: jsonToObject(row.attachmentJson),
|
attachment: jsonToObject(row.attachmentJson),
|
||||||
ciphertextSize:
|
ciphertextSize:
|
||||||
row.ciphertextSize ||
|
row.ciphertextSize ||
|
||||||
getAttachmentCiphertextLength(row.attachment.size),
|
getAttachmentCiphertextLength(row.attachment.size),
|
||||||
});
|
} as unknown);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`getNextAttachmentDownloadJobs: Error with job for message ${row.messageId}, deleting.`
|
`getNextAttachmentDownloadJobs: Error with job for message ${row.messageId}, deleting.`
|
||||||
|
@ -5040,11 +5043,11 @@ function getNextAttachmentBackupJobs(
|
||||||
const rows = db.prepare(query).all(params);
|
const rows = db.prepare(query).all(params);
|
||||||
return rows
|
return rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const parseResult = attachmentBackupJobSchema.safeParse({
|
const parseResult = safeParseUnknown(attachmentBackupJobSchema, {
|
||||||
...row,
|
...row,
|
||||||
active: Boolean(row.active),
|
active: Boolean(row.active),
|
||||||
data: jsonToObject(row.data),
|
data: jsonToObject(row.data),
|
||||||
});
|
} as unknown);
|
||||||
if (!parseResult.success) {
|
if (!parseResult.success) {
|
||||||
const redactedMediaName = redactGenericText(row.mediaName);
|
const redactedMediaName = redactGenericText(row.mediaName);
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import type { AttachmentType } from '../../types/Attachment';
|
import type { AttachmentType } from '../../types/Attachment';
|
||||||
import { jsonToObject, objectToJSON, sql } from '../util';
|
import { jsonToObject, objectToJSON, sql } from '../util';
|
||||||
import { AttachmentDownloadSource } from '../Interface';
|
import { AttachmentDownloadSource } from '../Interface';
|
||||||
|
import { parsePartial } from '../../util/schemas';
|
||||||
|
|
||||||
export const version = 1040;
|
export const version = 1040;
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ export function updateToSchemaVersion1040(
|
||||||
attempts INTEGER NOT NULL,
|
attempts INTEGER NOT NULL,
|
||||||
retryAfter INTEGER,
|
retryAfter INTEGER,
|
||||||
lastAttemptTimestamp INTEGER,
|
lastAttemptTimestamp INTEGER,
|
||||||
|
|
||||||
PRIMARY KEY (messageId, attachmentType, digest)
|
PRIMARY KEY (messageId, attachmentType, digest)
|
||||||
) STRICT;
|
) STRICT;
|
||||||
`);
|
`);
|
||||||
|
@ -84,7 +85,7 @@ export function updateToSchemaVersion1040(
|
||||||
// 5. Add new index on active & receivedAt. For most queries when there are lots of
|
// 5. Add new index on active & receivedAt. For most queries when there are lots of
|
||||||
// jobs (like during backup restore), many jobs will match the the WHERE clause, so
|
// jobs (like during backup restore), many jobs will match the the WHERE clause, so
|
||||||
// the ORDER BY on receivedAt is probably the most expensive part.
|
// the ORDER BY on receivedAt is probably the most expensive part.
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE INDEX attachment_downloads_active_receivedAt
|
CREATE INDEX attachment_downloads_active_receivedAt
|
||||||
ON attachment_downloads (
|
ON attachment_downloads (
|
||||||
active, receivedAt
|
active, receivedAt
|
||||||
|
@ -94,7 +95,7 @@ export function updateToSchemaVersion1040(
|
||||||
// 6. Add new index on active & messageId. In order to prioritize visible messages,
|
// 6. Add new index on active & messageId. In order to prioritize visible messages,
|
||||||
// we'll also query for rows with a matching messageId. For these, the messageId
|
// we'll also query for rows with a matching messageId. For these, the messageId
|
||||||
// matching is likely going to be the most expensive part.
|
// matching is likely going to be the most expensive part.
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE INDEX attachment_downloads_active_messageId
|
CREATE INDEX attachment_downloads_active_messageId
|
||||||
ON attachment_downloads (
|
ON attachment_downloads (
|
||||||
active, messageId
|
active, messageId
|
||||||
|
@ -103,7 +104,7 @@ export function updateToSchemaVersion1040(
|
||||||
|
|
||||||
// 7. Add new index just on messageId, for the ON DELETE CASCADE foreign key
|
// 7. Add new index just on messageId, for the ON DELETE CASCADE foreign key
|
||||||
// constraint
|
// constraint
|
||||||
db.exec(`
|
db.exec(`
|
||||||
CREATE INDEX attachment_downloads_messageId
|
CREATE INDEX attachment_downloads_messageId
|
||||||
ON attachment_downloads (
|
ON attachment_downloads (
|
||||||
messageId
|
messageId
|
||||||
|
@ -139,7 +140,7 @@ export function updateToSchemaVersion1040(
|
||||||
ciphertextSize: 0,
|
ciphertextSize: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsed = attachmentDownloadJobSchema.parse(updatedJob);
|
const parsed = parsePartial(attachmentDownloadJobSchema, updatedJob);
|
||||||
|
|
||||||
rowsToTransfer.push(parsed as AttachmentDownloadJobType);
|
rowsToTransfer.push(parsed as AttachmentDownloadJobType);
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -160,13 +161,13 @@ export function updateToSchemaVersion1040(
|
||||||
(
|
(
|
||||||
messageId,
|
messageId,
|
||||||
attachmentType,
|
attachmentType,
|
||||||
receivedAt,
|
receivedAt,
|
||||||
sentAt,
|
sentAt,
|
||||||
digest,
|
digest,
|
||||||
contentType,
|
contentType,
|
||||||
size,
|
size,
|
||||||
attachmentJson,
|
attachmentJson,
|
||||||
active,
|
active,
|
||||||
attempts,
|
attempts,
|
||||||
retryAfter,
|
retryAfter,
|
||||||
lastAttemptTimestamp
|
lastAttemptTimestamp
|
||||||
|
@ -181,7 +182,7 @@ export function updateToSchemaVersion1040(
|
||||||
${row.contentType},
|
${row.contentType},
|
||||||
${row.size},
|
${row.size},
|
||||||
${objectToJSON(row.attachment)},
|
${objectToJSON(row.attachment)},
|
||||||
${row.active ? 1 : 0},
|
${row.active ? 1 : 0},
|
||||||
${row.attempts},
|
${row.attempts},
|
||||||
${row.retryAfter},
|
${row.retryAfter},
|
||||||
${row.lastAttemptTimestamp}
|
${row.lastAttemptTimestamp}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import type { WritableDB, MessageType, ConversationType } from '../Interface';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { missingCaseError } from '../../util/missingCaseError';
|
import { missingCaseError } from '../../util/missingCaseError';
|
||||||
import { isAciString } from '../../util/isAciString';
|
import { isAciString } from '../../util/isAciString';
|
||||||
|
import { safeParseStrict } from '../../util/schemas';
|
||||||
|
|
||||||
// Legacy type for calls that never had a call id
|
// Legacy type for calls that never had a call id
|
||||||
type DirectCallHistoryDetailsType = {
|
type DirectCallHistoryDetailsType = {
|
||||||
|
@ -177,7 +178,7 @@ function convertLegacyCallDetails(
|
||||||
endedTimestamp: null,
|
endedTimestamp: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = callHistoryDetailsSchema.safeParse(callHistory);
|
const result = safeParseStrict(callHistoryDetailsSchema, callHistory);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return result.data;
|
return result.data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { prepare } from '../Server';
|
||||||
import { sql } from '../util';
|
import { sql } from '../util';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
import { CallStatusValue } from '../../types/CallDisposition';
|
import { CallStatusValue } from '../../types/CallDisposition';
|
||||||
|
import { parseStrict, parseUnknown } from '../../util/schemas';
|
||||||
|
|
||||||
export function callLinkExists(db: ReadableDB, roomId: string): boolean {
|
export function callLinkExists(db: ReadableDB, roomId: string): boolean {
|
||||||
const [query, params] = sql`
|
const [query, params] = sql`
|
||||||
|
@ -58,7 +59,7 @@ export function getCallLinkRecordByRoomId(
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callLinkRecordSchema.parse(row);
|
return parseUnknown(callLinkRecordSchema, row as unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllCallLinks(db: ReadableDB): ReadonlyArray<CallLinkType> {
|
export function getAllCallLinks(db: ReadableDB): ReadonlyArray<CallLinkType> {
|
||||||
|
@ -68,7 +69,9 @@ export function getAllCallLinks(db: ReadableDB): ReadonlyArray<CallLinkType> {
|
||||||
return db
|
return db
|
||||||
.prepare(query)
|
.prepare(query)
|
||||||
.all()
|
.all()
|
||||||
.map(item => callLinkFromRecord(callLinkRecordSchema.parse(item)));
|
.map((item: unknown) =>
|
||||||
|
callLinkFromRecord(parseUnknown(callLinkRecordSchema, item))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _insertCallLink(db: WritableDB, callLink: CallLinkType): void {
|
function _insertCallLink(db: WritableDB, callLink: CallLinkType): void {
|
||||||
|
@ -142,7 +145,10 @@ export function updateCallLinkState(
|
||||||
callLinkState: CallLinkStateType
|
callLinkState: CallLinkStateType
|
||||||
): CallLinkType {
|
): CallLinkType {
|
||||||
const { name, restrictions, expiration, revoked } = callLinkState;
|
const { name, restrictions, expiration, revoked } = callLinkState;
|
||||||
const restrictionsValue = callLinkRestrictionsSchema.parse(restrictions);
|
const restrictionsValue = parseStrict(
|
||||||
|
callLinkRestrictionsSchema,
|
||||||
|
restrictions
|
||||||
|
);
|
||||||
const [query, params] = sql`
|
const [query, params] = sql`
|
||||||
UPDATE callLinks
|
UPDATE callLinks
|
||||||
SET
|
SET
|
||||||
|
@ -153,9 +159,9 @@ export function updateCallLinkState(
|
||||||
WHERE roomId = ${roomId}
|
WHERE roomId = ${roomId}
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
`;
|
`;
|
||||||
const row = db.prepare(query).get(params);
|
const row: unknown = db.prepare(query).get(params);
|
||||||
strictAssert(row, 'Expected row to be returned');
|
strictAssert(row, 'Expected row to be returned');
|
||||||
return callLinkFromRecord(callLinkRecordSchema.parse(row));
|
return callLinkFromRecord(parseUnknown(callLinkRecordSchema, row));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateCallLinkAdminKeyByRoomId(
|
export function updateCallLinkAdminKeyByRoomId(
|
||||||
|
@ -302,7 +308,7 @@ export function getAllCallLinkRecordsWithAdminKey(
|
||||||
return db
|
return db
|
||||||
.prepare(query)
|
.prepare(query)
|
||||||
.all()
|
.all()
|
||||||
.map(item => callLinkRecordSchema.parse(item));
|
.map((item: unknown) => parseUnknown(callLinkRecordSchema, item));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAllMarkedDeletedCallLinkRoomIds(
|
export function getAllMarkedDeletedCallLinkRoomIds(
|
||||||
|
|
|
@ -8,7 +8,6 @@ import type {
|
||||||
} from '../../types/GroupSendEndorsements';
|
} from '../../types/GroupSendEndorsements';
|
||||||
import {
|
import {
|
||||||
groupSendEndorsementExpirationSchema,
|
groupSendEndorsementExpirationSchema,
|
||||||
groupSendCombinedEndorsementSchema,
|
|
||||||
groupSendMemberEndorsementSchema,
|
groupSendMemberEndorsementSchema,
|
||||||
groupSendEndorsementsDataSchema,
|
groupSendEndorsementsDataSchema,
|
||||||
} from '../../types/GroupSendEndorsements';
|
} from '../../types/GroupSendEndorsements';
|
||||||
|
@ -17,6 +16,7 @@ import type { ReadableDB, WritableDB } from '../Interface';
|
||||||
import { sql } from '../util';
|
import { sql } from '../util';
|
||||||
import type { AciString } from '../../types/ServiceId';
|
import type { AciString } from '../../types/ServiceId';
|
||||||
import { strictAssert } from '../../util/assert';
|
import { strictAssert } from '../../util/assert';
|
||||||
|
import { parseLoose, parseUnknown } from '../../util/schemas';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We don't need to store more than one endorsement per group or per member.
|
* We don't need to store more than one endorsement per group or per member.
|
||||||
|
@ -110,7 +110,7 @@ export function getGroupSendCombinedEndorsementExpiration(
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return groupSendEndorsementExpirationSchema.parse(value);
|
return parseUnknown(groupSendEndorsementExpirationSchema, value as unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGroupSendEndorsementsData(
|
export function getGroupSendEndorsementsData(
|
||||||
|
@ -128,24 +128,21 @@ export function getGroupSendEndorsementsData(
|
||||||
WHERE groupId IS ${groupId}
|
WHERE groupId IS ${groupId}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const combinedEndorsement = groupSendCombinedEndorsementSchema
|
const combinedEndorsement: unknown = prepare<Array<unknown>>(
|
||||||
.optional()
|
db,
|
||||||
.parse(
|
selectCombinedEndorsement
|
||||||
prepare<Array<unknown>>(db, selectCombinedEndorsement).get(
|
).get(selectCombinedEndorsementParams);
|
||||||
selectCombinedEndorsementParams
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (combinedEndorsement == null) {
|
if (combinedEndorsement == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const memberEndorsements = prepare<Array<unknown>>(
|
const memberEndorsements: Array<unknown> = prepare<Array<unknown>>(
|
||||||
db,
|
db,
|
||||||
selectMemberEndorsements
|
selectMemberEndorsements
|
||||||
).all(selectMemberEndorsementsParams);
|
).all(selectMemberEndorsementsParams);
|
||||||
|
|
||||||
return groupSendEndorsementsDataSchema.parse({
|
return parseLoose(groupSendEndorsementsDataSchema, {
|
||||||
combinedEndorsement,
|
combinedEndorsement,
|
||||||
memberEndorsements,
|
memberEndorsements,
|
||||||
});
|
});
|
||||||
|
@ -168,5 +165,5 @@ export function getGroupSendMemberEndorsement(
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return groupSendMemberEndorsementSchema.parse(row);
|
return parseUnknown(groupSendMemberEndorsementSchema, row as unknown);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import type { JOB_STATUS } from '../../jobs/JobQueue';
|
||||||
import { JobQueue } from '../../jobs/JobQueue';
|
import { JobQueue } from '../../jobs/JobQueue';
|
||||||
import type { ParsedJob, StoredJob, JobQueueStore } from '../../jobs/types';
|
import type { ParsedJob, StoredJob, JobQueueStore } from '../../jobs/types';
|
||||||
import { sleep } from '../../util/sleep';
|
import { sleep } from '../../util/sleep';
|
||||||
|
import { parseUnknown } from '../../util/schemas';
|
||||||
|
|
||||||
describe('JobQueue', () => {
|
describe('JobQueue', () => {
|
||||||
describe('end-to-end tests', () => {
|
describe('end-to-end tests', () => {
|
||||||
|
@ -36,7 +37,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class Queue extends JobQueue<TestJobData> {
|
class Queue extends JobQueue<TestJobData> {
|
||||||
parseData(data: unknown): TestJobData {
|
parseData(data: unknown): TestJobData {
|
||||||
return testJobSchema.parse(data);
|
return parseUnknown(testJobSchema, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run({
|
async run({
|
||||||
|
@ -86,7 +87,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class Queue extends JobQueue<number> {
|
class Queue extends JobQueue<number> {
|
||||||
parseData(data: unknown): number {
|
parseData(data: unknown): number {
|
||||||
return z.number().parse(data);
|
return parseUnknown(z.number(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(): Promise<typeof JOB_STATUS.NEEDS_RETRY | undefined> {
|
async run(): Promise<typeof JOB_STATUS.NEEDS_RETRY | undefined> {
|
||||||
|
@ -137,7 +138,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class Queue extends JobQueue<number> {
|
class Queue extends JobQueue<number> {
|
||||||
parseData(data: unknown): number {
|
parseData(data: unknown): number {
|
||||||
return z.number().parse(data);
|
return parseUnknown(z.number(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override getInMemoryQueue(
|
protected override getInMemoryQueue(
|
||||||
|
@ -180,7 +181,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class TestQueue extends JobQueue<string> {
|
class TestQueue extends JobQueue<string> {
|
||||||
parseData(data: unknown): string {
|
parseData(data: unknown): string {
|
||||||
return z.string().parse(data);
|
return parseUnknown(z.string(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(): Promise<typeof JOB_STATUS.NEEDS_RETRY | undefined> {
|
async run(): Promise<typeof JOB_STATUS.NEEDS_RETRY | undefined> {
|
||||||
|
@ -248,7 +249,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class TestQueue extends JobQueue<string> {
|
class TestQueue extends JobQueue<string> {
|
||||||
parseData(data: unknown): string {
|
parseData(data: unknown): string {
|
||||||
return z.string().parse(data);
|
return parseUnknown(z.string(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(): Promise<typeof JOB_STATUS.NEEDS_RETRY | undefined> {
|
async run(): Promise<typeof JOB_STATUS.NEEDS_RETRY | undefined> {
|
||||||
|
@ -353,7 +354,6 @@ describe('JobQueue', () => {
|
||||||
// Chai's `assert.instanceOf` doesn't tell TypeScript anything, so we do it here.
|
// Chai's `assert.instanceOf` doesn't tell TypeScript anything, so we do it here.
|
||||||
if (!(booErr instanceof JobError)) {
|
if (!(booErr instanceof JobError)) {
|
||||||
assert.fail('Expected error to be a JobError');
|
assert.fail('Expected error to be a JobError');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
assert.include(booErr.message, 'bar job always fails in this test');
|
assert.include(booErr.message, 'bar job always fails in this test');
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class TestQueue extends JobQueue<string> {
|
class TestQueue extends JobQueue<string> {
|
||||||
parseData(data: unknown): string {
|
parseData(data: unknown): string {
|
||||||
return z.string().parse(data);
|
return parseUnknown(z.string(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(
|
async run(
|
||||||
|
@ -412,7 +412,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class TestQueue extends JobQueue<number> {
|
class TestQueue extends JobQueue<number> {
|
||||||
parseData(data: unknown): number {
|
parseData(data: unknown): number {
|
||||||
return z.number().parse(data);
|
return parseUnknown(z.number(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(
|
async run(
|
||||||
|
@ -490,7 +490,6 @@ describe('JobQueue', () => {
|
||||||
// Chai's `assert.instanceOf` doesn't tell TypeScript anything, so we do it here.
|
// Chai's `assert.instanceOf` doesn't tell TypeScript anything, so we do it here.
|
||||||
if (!(jobError instanceof JobError)) {
|
if (!(jobError instanceof JobError)) {
|
||||||
assert.fail('Expected error to be a JobError');
|
assert.fail('Expected error to be a JobError');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
assert.include(
|
assert.include(
|
||||||
jobError.message,
|
jobError.message,
|
||||||
|
@ -740,7 +739,7 @@ describe('JobQueue', () => {
|
||||||
while (true) {
|
while (true) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
const [job] = await once(this.eventEmitter, 'drip');
|
const [job] = await once(this.eventEmitter, 'drip');
|
||||||
yield storedJobSchema.parse(job);
|
yield parseUnknown(storedJobSchema, job as unknown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -766,7 +765,7 @@ describe('JobQueue', () => {
|
||||||
|
|
||||||
class TestQueue extends JobQueue<number> {
|
class TestQueue extends JobQueue<number> {
|
||||||
parseData(data: unknown): number {
|
parseData(data: unknown): number {
|
||||||
return z.number().parse(data);
|
return parseUnknown(z.number(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async run({
|
async run({
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { getCallIdFromEra } from '../../util/callDisposition';
|
||||||
import { isValidUuid } from '../../util/isValidUuid';
|
import { isValidUuid } from '../../util/isValidUuid';
|
||||||
import { createDB, updateToVersion } from './helpers';
|
import { createDB, updateToVersion } from './helpers';
|
||||||
import type { WritableDB, MessageType } from '../../sql/Interface';
|
import type { WritableDB, MessageType } from '../../sql/Interface';
|
||||||
|
import { parsePartial } from '../../util/schemas';
|
||||||
|
|
||||||
describe('SQL/updateToSchemaVersion89', () => {
|
describe('SQL/updateToSchemaVersion89', () => {
|
||||||
let db: WritableDB;
|
let db: WritableDB;
|
||||||
|
@ -152,8 +153,8 @@ describe('SQL/updateToSchemaVersion89', () => {
|
||||||
return db
|
return db
|
||||||
.prepare(selectHistoryQuery)
|
.prepare(selectHistoryQuery)
|
||||||
.all()
|
.all()
|
||||||
.map(row => {
|
.map((row: object) => {
|
||||||
return callHistoryDetailsSchema.parse({
|
return parsePartial(callHistoryDetailsSchema, {
|
||||||
...row,
|
...row,
|
||||||
|
|
||||||
// Not present at the time of migration, but required by zod
|
// Not present at the time of migration, but required by zod
|
||||||
|
|
242
ts/test-node/util/schemas_test.ts
Normal file
242
ts/test-node/util/schemas_test.ts
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import {
|
||||||
|
parseLoose,
|
||||||
|
parsePartial,
|
||||||
|
parseStrict,
|
||||||
|
parseUnknown,
|
||||||
|
SchemaParseError,
|
||||||
|
} from '../../util/schemas';
|
||||||
|
|
||||||
|
describe('schemas', () => {
|
||||||
|
const schema = z.object({ prop: z.literal('value') });
|
||||||
|
|
||||||
|
it('rejects invalid inputs', () => {
|
||||||
|
function assertThrows(fn: () => void) {
|
||||||
|
assert.throws(fn, SchemaParseError);
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = { prop: 42 };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertThrows(() => parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: invalid type
|
||||||
|
assertThrows(() => parseStrict(schema, input));
|
||||||
|
assertThrows(() => parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: invalid type
|
||||||
|
assertThrows(() => parsePartial(schema, input));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts valid inputs', () => {
|
||||||
|
const valid = { prop: 'value' };
|
||||||
|
|
||||||
|
function assertShape(value: { prop: 'value' }) {
|
||||||
|
assert.deepEqual(value, valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknown
|
||||||
|
{
|
||||||
|
const input = valid as unknown;
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// @ts-expect-error: not loose
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// any
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const input = valid as unknown as any;
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// @ts-expect-error: not loose
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// {}
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
const input = valid as unknown as {};
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// @ts-expect-error: not loose
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// never
|
||||||
|
{
|
||||||
|
const input = valid as unknown as never;
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// @ts-expect-error: not loose
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: "value" }
|
||||||
|
{
|
||||||
|
const input = valid as { prop: 'value' };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop?: "value" }
|
||||||
|
{
|
||||||
|
const input = valid as { prop?: 'value' };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// @ts-expect-error: not loose
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: "value" | void }
|
||||||
|
{
|
||||||
|
const input = valid as { prop: 'value' | void };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: "value" | undefined }
|
||||||
|
{
|
||||||
|
const input = valid as { prop: 'value' | undefined };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: "value" | null }
|
||||||
|
{
|
||||||
|
const input = valid as { prop: 'value' | null };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
|
||||||
|
// { prop: string }
|
||||||
|
{
|
||||||
|
const input = valid as { prop: string };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop?: string }
|
||||||
|
{
|
||||||
|
const input = valid as { prop?: string };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// @ts-expect-error: not loose
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: void }
|
||||||
|
{
|
||||||
|
const input = valid as unknown as { prop: void };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: undefined }
|
||||||
|
{
|
||||||
|
const input = valid as unknown as { prop: undefined };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: null }
|
||||||
|
{
|
||||||
|
const input = valid as unknown as { prop: null };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: unknown }
|
||||||
|
{
|
||||||
|
const input = valid as { prop: unknown };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: any }
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const input = valid as { prop: any };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// (ideally not allowed)
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// (ideally not allowed)
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// (ideally not allowed)
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: {} }
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
const input = valid as { prop: {} };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// @ts-expect-error: not strict
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// (ideally not allowed)
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// @ts-expect-error: not partial
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
// { prop: never }
|
||||||
|
{
|
||||||
|
const input = valid as { prop: never };
|
||||||
|
// @ts-expect-error: not unknown
|
||||||
|
assertShape(parseUnknown(schema, input));
|
||||||
|
// (ideally not allowed)
|
||||||
|
assertShape(parseStrict(schema, input));
|
||||||
|
// (ideally not allowed)
|
||||||
|
assertShape(parseLoose(schema, input));
|
||||||
|
// (ideally not allowed)
|
||||||
|
assertShape(parsePartial(schema, input));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -73,6 +73,7 @@ import { safeParseNumber } from '../util/numbers';
|
||||||
import { isStagingServer } from '../util/isStagingServer';
|
import { isStagingServer } from '../util/isStagingServer';
|
||||||
import type { IWebSocketResource } from './WebsocketResources';
|
import type { IWebSocketResource } from './WebsocketResources';
|
||||||
import type { GroupSendToken } from '../types/GroupSendEndorsements';
|
import type { GroupSendToken } from '../types/GroupSendEndorsements';
|
||||||
|
import { parseUnknown, safeParseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
// Note: this will break some code that expects to be able to use err.response when a
|
// Note: this will break some code that expects to be able to use err.response when a
|
||||||
// web request fails, because it will force it to text. But it is very useful for
|
// web request fails, because it will force it to text. But it is very useful for
|
||||||
|
@ -1948,7 +1949,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
|
|
||||||
return whoamiResultZod.parse(response);
|
return parseUnknown(whoamiResultZod, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendChallengeResponse(challengeResponse: ChallengeType) {
|
async function sendChallengeResponse(challengeResponse: ChallengeType) {
|
||||||
|
@ -2027,7 +2028,7 @@ export function initialize({
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
const res = remoteConfigResponseZod.parse(rawRes);
|
const res = parseUnknown(remoteConfigResponseZod, rawRes);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...res,
|
...res,
|
||||||
|
@ -2157,7 +2158,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = verifyServiceIdResponse.safeParse(res);
|
const result = safeParseUnknown(verifyServiceIdResponse, res);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
return result.data;
|
return result.data;
|
||||||
|
@ -2223,7 +2224,8 @@ export function initialize({
|
||||||
hash,
|
hash,
|
||||||
}: GetAccountForUsernameOptionsType) {
|
}: GetAccountForUsernameOptionsType) {
|
||||||
const hashBase64 = toWebSafeBase64(Bytes.toBase64(hash));
|
const hashBase64 = toWebSafeBase64(Bytes.toBase64(hash));
|
||||||
return getAccountForUsernameResultZod.parse(
|
return parseUnknown(
|
||||||
|
getAccountForUsernameResultZod,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'username',
|
call: 'username',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
|
@ -2251,7 +2253,7 @@ export function initialize({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return uploadAvatarHeadersZod.parse(res);
|
return parseUnknown(uploadAvatarHeadersZod, res as unknown);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getProfileUnauth(
|
async function getProfileUnauth(
|
||||||
|
@ -2389,7 +2391,7 @@ export function initialize({
|
||||||
abortSignal,
|
abortSignal,
|
||||||
});
|
});
|
||||||
|
|
||||||
return reserveUsernameResultZod.parse(response);
|
return parseUnknown(reserveUsernameResultZod, response);
|
||||||
}
|
}
|
||||||
async function confirmUsername({
|
async function confirmUsername({
|
||||||
hash,
|
hash,
|
||||||
|
@ -2408,14 +2410,15 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
abortSignal,
|
abortSignal,
|
||||||
});
|
});
|
||||||
return confirmUsernameResultZod.parse(response);
|
return parseUnknown(confirmUsernameResultZod, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function replaceUsernameLink({
|
async function replaceUsernameLink({
|
||||||
encryptedUsername,
|
encryptedUsername,
|
||||||
keepLinkHandle,
|
keepLinkHandle,
|
||||||
}: ReplaceUsernameLinkOptionsType): Promise<ReplaceUsernameLinkResultType> {
|
}: ReplaceUsernameLinkOptionsType): Promise<ReplaceUsernameLinkResultType> {
|
||||||
return replaceUsernameLinkResultZod.parse(
|
return parseUnknown(
|
||||||
|
replaceUsernameLinkResultZod,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'usernameLink',
|
call: 'usernameLink',
|
||||||
httpType: 'PUT',
|
httpType: 'PUT',
|
||||||
|
@ -2440,7 +2443,8 @@ export function initialize({
|
||||||
async function resolveUsernameLink(
|
async function resolveUsernameLink(
|
||||||
serverId: string
|
serverId: string
|
||||||
): Promise<ResolveUsernameLinkResultType> {
|
): Promise<ResolveUsernameLinkResultType> {
|
||||||
return resolveUsernameLinkResultZod.parse(
|
return parseUnknown(
|
||||||
|
resolveUsernameLinkResultZod,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
call: 'usernameLink',
|
call: 'usernameLink',
|
||||||
|
@ -2475,7 +2479,8 @@ export function initialize({
|
||||||
transport: VerificationTransport
|
transport: VerificationTransport
|
||||||
) {
|
) {
|
||||||
// Create a new blank session using just a E164
|
// Create a new blank session using just a E164
|
||||||
let session = verificationSessionZod.parse(
|
let session = parseUnknown(
|
||||||
|
verificationSessionZod,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'verificationSession',
|
call: 'verificationSession',
|
||||||
httpType: 'POST',
|
httpType: 'POST',
|
||||||
|
@ -2490,7 +2495,8 @@ export function initialize({
|
||||||
);
|
);
|
||||||
|
|
||||||
// Submit a captcha solution to the session
|
// Submit a captcha solution to the session
|
||||||
session = verificationSessionZod.parse(
|
session = parseUnknown(
|
||||||
|
verificationSessionZod,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'verificationSession',
|
call: 'verificationSession',
|
||||||
httpType: 'PATCH',
|
httpType: 'PATCH',
|
||||||
|
@ -2511,7 +2517,8 @@ export function initialize({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request an SMS or Voice confirmation
|
// Request an SMS or Voice confirmation
|
||||||
session = verificationSessionZod.parse(
|
session = parseUnknown(
|
||||||
|
verificationSessionZod,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'verificationSession',
|
call: 'verificationSession',
|
||||||
httpType: 'POST',
|
httpType: 'POST',
|
||||||
|
@ -2618,7 +2625,8 @@ export function initialize({
|
||||||
aciPqLastResortPreKey,
|
aciPqLastResortPreKey,
|
||||||
pniPqLastResortPreKey,
|
pniPqLastResortPreKey,
|
||||||
}: CreateAccountOptionsType) {
|
}: CreateAccountOptionsType) {
|
||||||
const session = verificationSessionZod.parse(
|
const session = parseUnknown(
|
||||||
|
verificationSessionZod,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
isRegistration: true,
|
isRegistration: true,
|
||||||
call: 'verificationSession',
|
call: 'verificationSession',
|
||||||
|
@ -2676,7 +2684,7 @@ export function initialize({
|
||||||
jsonData,
|
jsonData,
|
||||||
});
|
});
|
||||||
|
|
||||||
return createAccountResultZod.parse(responseJson);
|
return parseUnknown(createAccountResultZod, responseJson);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2726,7 +2734,7 @@ export function initialize({
|
||||||
jsonData,
|
jsonData,
|
||||||
});
|
});
|
||||||
|
|
||||||
return linkDeviceResultZod.parse(responseJson);
|
return parseUnknown(linkDeviceResultZod, responseJson);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2842,7 +2850,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
|
|
||||||
return getBackupInfoResponseSchema.parse(res);
|
return parseUnknown(getBackupInfoResponseSchema, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBackupStream({
|
async function getBackupStream({
|
||||||
|
@ -2880,7 +2888,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
|
|
||||||
return attachmentUploadFormResponse.parse(res);
|
return parseUnknown(attachmentUploadFormResponse, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFetchForAttachmentUpload({
|
function createFetchForAttachmentUpload({
|
||||||
|
@ -2932,7 +2940,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
|
|
||||||
return attachmentUploadFormResponse.parse(res);
|
return parseUnknown(attachmentUploadFormResponse, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshBackup(headers: BackupPresentationHeadersType) {
|
async function refreshBackup(headers: BackupPresentationHeadersType) {
|
||||||
|
@ -2961,7 +2969,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
|
|
||||||
return getBackupCredentialsResponseSchema.parse(res);
|
return parseUnknown(getBackupCredentialsResponseSchema, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getBackupCDNCredentials({
|
async function getBackupCDNCredentials({
|
||||||
|
@ -2979,7 +2987,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
});
|
});
|
||||||
|
|
||||||
return getBackupCDNCredentialsResponseSchema.parse(res);
|
return parseUnknown(getBackupCDNCredentialsResponseSchema, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setBackupId({
|
async function setBackupId({
|
||||||
|
@ -3051,7 +3059,7 @@ export function initialize({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return backupMediaBatchResponseSchema.parse(res);
|
return parseUnknown(backupMediaBatchResponseSchema, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function backupDeleteMedia({
|
async function backupDeleteMedia({
|
||||||
|
@ -3099,7 +3107,7 @@ export function initialize({
|
||||||
urlParameters: `?${params.join('&')}`,
|
urlParameters: `?${params.join('&')}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return backupListMediaResponseSchema.parse(res);
|
return parseUnknown(backupListMediaResponseSchema, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callLinkCreateAuth(
|
async function callLinkCreateAuth(
|
||||||
|
@ -3111,7 +3119,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
jsonData: { createCallLinkCredentialRequest: requestBase64 },
|
jsonData: { createCallLinkCredentialRequest: requestBase64 },
|
||||||
});
|
});
|
||||||
return callLinkCreateAuthResponseSchema.parse(response);
|
return parseUnknown(callLinkCreateAuthResponseSchema, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setPhoneNumberDiscoverability(newValue: boolean) {
|
async function setPhoneNumberDiscoverability(newValue: boolean) {
|
||||||
|
@ -3354,7 +3362,10 @@ export function initialize({
|
||||||
accessKey: accessKeys != null ? Bytes.toBase64(accessKeys) : undefined,
|
accessKey: accessKeys != null ? Bytes.toBase64(accessKeys) : undefined,
|
||||||
groupSendToken,
|
groupSendToken,
|
||||||
});
|
});
|
||||||
const parseResult = multiRecipient200ResponseSchema.safeParse(response);
|
const parseResult = safeParseUnknown(
|
||||||
|
multiRecipient200ResponseSchema,
|
||||||
|
response
|
||||||
|
);
|
||||||
if (parseResult.success) {
|
if (parseResult.success) {
|
||||||
return parseResult.data;
|
return parseResult.data;
|
||||||
}
|
}
|
||||||
|
@ -3490,8 +3501,10 @@ export function initialize({
|
||||||
urlParameters: `/${encryptedStickers.length}`,
|
urlParameters: `/${encryptedStickers.length}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { packId, manifest, stickers } =
|
const { packId, manifest, stickers } = parseUnknown(
|
||||||
StickerPackUploadFormSchema.parse(formJson);
|
StickerPackUploadFormSchema,
|
||||||
|
formJson
|
||||||
|
);
|
||||||
|
|
||||||
// Upload manifest
|
// Upload manifest
|
||||||
const manifestParams = makePutParams(manifest, encryptedManifest);
|
const manifestParams = makePutParams(manifest, encryptedManifest);
|
||||||
|
@ -3718,7 +3731,8 @@ export function initialize({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAttachmentUploadForm() {
|
async function getAttachmentUploadForm() {
|
||||||
return attachmentUploadFormResponse.parse(
|
return parseUnknown(
|
||||||
|
attachmentUploadFormResponse,
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'attachmentUploadForm',
|
call: 'attachmentUploadForm',
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
|
|
|
@ -62,6 +62,7 @@ import { ToastType } from '../types/Toast';
|
||||||
import { AbortableProcess } from '../util/AbortableProcess';
|
import { AbortableProcess } from '../util/AbortableProcess';
|
||||||
import type { WebAPICredentials } from './Types';
|
import type { WebAPICredentials } from './Types';
|
||||||
import { NORMAL_DISCONNECT_CODE } from './SocketManager';
|
import { NORMAL_DISCONNECT_CODE } from './SocketManager';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const THIRTY_SECONDS = 30 * durations.SECOND;
|
const THIRTY_SECONDS = 30 * durations.SECOND;
|
||||||
|
|
||||||
|
@ -107,7 +108,7 @@ export namespace AggregatedStats {
|
||||||
try {
|
try {
|
||||||
const json = localStorage.getItem(key);
|
const json = localStorage.getItem(key);
|
||||||
return json != null
|
return json != null
|
||||||
? AggregatedStatsSchema.parse(JSON.parse(json))
|
? parseUnknown(AggregatedStatsSchema, JSON.parse(json) as unknown)
|
||||||
: createEmpty();
|
: createEmpty();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|
|
@ -6,6 +6,7 @@ import type { ConversationType } from '../state/ducks/conversations';
|
||||||
import { safeParseInteger } from '../util/numbers';
|
import { safeParseInteger } from '../util/numbers';
|
||||||
import { byteLength } from '../Bytes';
|
import { byteLength } from '../Bytes';
|
||||||
import type { StorageServiceFieldsType } from '../sql/Interface';
|
import type { StorageServiceFieldsType } from '../sql/Interface';
|
||||||
|
import { parsePartial } from '../util/schemas';
|
||||||
|
|
||||||
export enum CallLinkUpdateSyncType {
|
export enum CallLinkUpdateSyncType {
|
||||||
Update = 'Update',
|
Update = 'Update',
|
||||||
|
@ -44,7 +45,10 @@ export const callLinkRestrictionsSchema = z.nativeEnum(CallLinkRestrictions);
|
||||||
export function toCallLinkRestrictions(
|
export function toCallLinkRestrictions(
|
||||||
restrictions: number | string
|
restrictions: number | string
|
||||||
): CallLinkRestrictions {
|
): CallLinkRestrictions {
|
||||||
return callLinkRestrictionsSchema.parse(safeParseInteger(restrictions));
|
return parsePartial(
|
||||||
|
callLinkRestrictionsSchema,
|
||||||
|
safeParseInteger(restrictions)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { aciSchema, type AciString } from './ServiceId';
|
import { aciSchema, type AciString } from './ServiceId';
|
||||||
import * as Bytes from '../Bytes';
|
import * as Bytes from '../Bytes';
|
||||||
|
import { parseStrict } from '../util/schemas';
|
||||||
|
|
||||||
const GROUPV2_ID_LENGTH = 32; // 32 bytes
|
const GROUPV2_ID_LENGTH = 32; // 32 bytes
|
||||||
|
|
||||||
|
@ -94,5 +95,5 @@ export const groupSendTokenSchema = z
|
||||||
export type GroupSendToken = z.infer<typeof groupSendTokenSchema>;
|
export type GroupSendToken = z.infer<typeof groupSendTokenSchema>;
|
||||||
|
|
||||||
export function toGroupSendToken(token: Uint8Array): GroupSendToken {
|
export function toGroupSendToken(token: Uint8Array): GroupSendToken {
|
||||||
return groupSendTokenSchema.parse(token);
|
return parseStrict(groupSendTokenSchema, token);
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,7 @@ import { drop } from './drop';
|
||||||
import { sendCallLinkUpdateSync } from './sendCallLinkUpdateSync';
|
import { sendCallLinkUpdateSync } from './sendCallLinkUpdateSync';
|
||||||
import { storageServiceUploadJob } from '../services/storage';
|
import { storageServiceUploadJob } from '../services/storage';
|
||||||
import { CallLinkDeleteManager } from '../jobs/CallLinkDeleteManager';
|
import { CallLinkDeleteManager } from '../jobs/CallLinkDeleteManager';
|
||||||
|
import { parsePartial, parseStrict } from './schemas';
|
||||||
|
|
||||||
// utils
|
// utils
|
||||||
// -----
|
// -----
|
||||||
|
@ -200,7 +201,7 @@ export function getCallEventForProto(
|
||||||
callEventProto: Proto.SyncMessage.ICallEvent,
|
callEventProto: Proto.SyncMessage.ICallEvent,
|
||||||
eventSource: string
|
eventSource: string
|
||||||
): CallEventDetails {
|
): CallEventDetails {
|
||||||
const callEvent = callEventNormalizeSchema.parse(callEventProto);
|
const callEvent = parsePartial(callEventNormalizeSchema, callEventProto);
|
||||||
const { callId, peerId, timestamp } = callEvent;
|
const { callId, peerId, timestamp } = callEvent;
|
||||||
|
|
||||||
let type: CallType;
|
let type: CallType;
|
||||||
|
@ -251,7 +252,7 @@ export function getCallEventForProto(
|
||||||
throw new TypeError(`Unknown call event ${callEvent.event}`);
|
throw new TypeError(`Unknown call event ${callEvent.event}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callEventDetailsSchema.parse({
|
return parseStrict(callEventDetailsSchema, {
|
||||||
callId,
|
callId,
|
||||||
peerId,
|
peerId,
|
||||||
ringerId: null,
|
ringerId: null,
|
||||||
|
@ -279,7 +280,10 @@ const callLogEventFromProto: Partial<
|
||||||
export function getCallLogEventForProto(
|
export function getCallLogEventForProto(
|
||||||
callLogEventProto: Proto.SyncMessage.ICallLogEvent
|
callLogEventProto: Proto.SyncMessage.ICallLogEvent
|
||||||
): CallLogEventDetails {
|
): CallLogEventDetails {
|
||||||
const callLogEvent = callLogEventNormalizeSchema.parse(callLogEventProto);
|
const callLogEvent = parsePartial(
|
||||||
|
callLogEventNormalizeSchema,
|
||||||
|
callLogEventProto
|
||||||
|
);
|
||||||
|
|
||||||
const type = callLogEventFromProto[callLogEvent.type];
|
const type = callLogEventFromProto[callLogEvent.type];
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
|
@ -496,7 +500,7 @@ export function getCallDetailsFromDirectCall(
|
||||||
call: Call
|
call: Call
|
||||||
): CallDetails {
|
): CallDetails {
|
||||||
const ringerId = call.isIncoming ? call.remoteUserId : null;
|
const ringerId = call.isIncoming ? call.remoteUserId : null;
|
||||||
return callDetailsSchema.parse({
|
return parseStrict(callDetailsSchema, {
|
||||||
callId: Long.fromValue(call.callId).toString(),
|
callId: Long.fromValue(call.callId).toString(),
|
||||||
peerId,
|
peerId,
|
||||||
ringerId,
|
ringerId,
|
||||||
|
@ -518,7 +522,7 @@ export function getCallDetailsFromEndedDirectCall(
|
||||||
wasVideoCall: boolean,
|
wasVideoCall: boolean,
|
||||||
timestamp: number
|
timestamp: number
|
||||||
): CallDetails {
|
): CallDetails {
|
||||||
return callDetailsSchema.parse({
|
return parseStrict(callDetailsSchema, {
|
||||||
callId,
|
callId,
|
||||||
peerId,
|
peerId,
|
||||||
ringerId,
|
ringerId,
|
||||||
|
@ -535,7 +539,7 @@ export function getCallDetailsFromGroupCallMeta(
|
||||||
peerId: AciString | string,
|
peerId: AciString | string,
|
||||||
groupCallMeta: GroupCallMeta
|
groupCallMeta: GroupCallMeta
|
||||||
): CallDetails {
|
): CallDetails {
|
||||||
return callDetailsSchema.parse({
|
return parseStrict(callDetailsSchema, {
|
||||||
callId: groupCallMeta.callId,
|
callId: groupCallMeta.callId,
|
||||||
peerId,
|
peerId,
|
||||||
ringerId: groupCallMeta.ringerId,
|
ringerId: groupCallMeta.ringerId,
|
||||||
|
@ -552,7 +556,7 @@ export function getCallDetailsForAdhocCall(
|
||||||
peerId: AciString | string,
|
peerId: AciString | string,
|
||||||
callId: string
|
callId: string
|
||||||
): CallDetails {
|
): CallDetails {
|
||||||
return callDetailsSchema.parse({
|
return parseStrict(callDetailsSchema, {
|
||||||
callId,
|
callId,
|
||||||
peerId,
|
peerId,
|
||||||
ringerId: null,
|
ringerId: null,
|
||||||
|
@ -575,7 +579,11 @@ export function getCallEventDetails(
|
||||||
event: LocalCallEvent,
|
event: LocalCallEvent,
|
||||||
eventSource: string
|
eventSource: string
|
||||||
): CallEventDetails {
|
): CallEventDetails {
|
||||||
return callEventDetailsSchema.parse({ ...callDetails, event, eventSource });
|
return parseStrict(callEventDetailsSchema, {
|
||||||
|
...callDetails,
|
||||||
|
event,
|
||||||
|
eventSource,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// transitions
|
// transitions
|
||||||
|
@ -646,7 +654,7 @@ export function transitionCallHistory(
|
||||||
`transitionCallHistory: Transitioned call history timestamp (before: ${callHistory?.timestamp}, after: ${timestamp})`
|
`transitionCallHistory: Transitioned call history timestamp (before: ${callHistory?.timestamp}, after: ${timestamp})`
|
||||||
);
|
);
|
||||||
|
|
||||||
return callHistoryDetailsSchema.parse({
|
return parseStrict(callHistoryDetailsSchema, {
|
||||||
callId,
|
callId,
|
||||||
peerId,
|
peerId,
|
||||||
ringerId,
|
ringerId,
|
||||||
|
|
|
@ -33,6 +33,7 @@ import {
|
||||||
getKeyFromCallLink,
|
getKeyFromCallLink,
|
||||||
toAdminKeyBytes,
|
toAdminKeyBytes,
|
||||||
} from './callLinks';
|
} from './callLinks';
|
||||||
|
import { parseStrict } from './schemas';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RingRTC conversions
|
* RingRTC conversions
|
||||||
|
@ -56,7 +57,7 @@ const RingRTCCallLinkRestrictionsSchema = z.nativeEnum(
|
||||||
export function callLinkRestrictionsToRingRTC(
|
export function callLinkRestrictionsToRingRTC(
|
||||||
restrictions: CallLinkRestrictions
|
restrictions: CallLinkRestrictions
|
||||||
): RingRTCCallLinkRestrictions {
|
): RingRTCCallLinkRestrictions {
|
||||||
return RingRTCCallLinkRestrictionsSchema.parse(restrictions);
|
return parseStrict(RingRTCCallLinkRestrictionsSchema, restrictions);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRoomIdFromRootKey(rootKey: CallLinkRootKey): string {
|
export function getRoomIdFromRootKey(rootKey: CallLinkRootKey): string {
|
||||||
|
@ -152,7 +153,7 @@ export function callLinkToRecord(callLink: CallLinkType): CallLinkRecord {
|
||||||
const adminKey = callLink.adminKey
|
const adminKey = callLink.adminKey
|
||||||
? toAdminKeyBytes(callLink.adminKey)
|
? toAdminKeyBytes(callLink.adminKey)
|
||||||
: null;
|
: null;
|
||||||
return callLinkRecordSchema.parse({
|
return parseStrict(callLinkRecordSchema, {
|
||||||
roomId: callLink.roomId,
|
roomId: callLink.roomId,
|
||||||
rootKey,
|
rootKey,
|
||||||
adminKey,
|
adminKey,
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { ToastType } from '../types/Toast';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import { isTestOrMockEnvironment } from '../environment';
|
import { isTestOrMockEnvironment } from '../environment';
|
||||||
import { isAlpha } from './version';
|
import { isAlpha } from './version';
|
||||||
|
import { parseStrict } from './schemas';
|
||||||
|
|
||||||
export function decodeGroupSendEndorsementResponse({
|
export function decodeGroupSendEndorsementResponse({
|
||||||
groupId,
|
groupId,
|
||||||
|
@ -91,7 +92,7 @@ export function decodeGroupSendEndorsementResponse({
|
||||||
`decodeGroupSendEndorsementResponse: Received endorsements (group: ${idForLogging}, expiration: ${expiration}, members: ${groupMembers.length})`
|
`decodeGroupSendEndorsementResponse: Received endorsements (group: ${idForLogging}, expiration: ${expiration}, members: ${groupMembers.length})`
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupEndorsementsData: GroupSendEndorsementsData = {
|
return parseStrict(groupSendEndorsementsDataSchema, {
|
||||||
combinedEndorsement: {
|
combinedEndorsement: {
|
||||||
groupId,
|
groupId,
|
||||||
expiration,
|
expiration,
|
||||||
|
@ -110,9 +111,7 @@ export function decodeGroupSendEndorsementResponse({
|
||||||
endorsement: endorsement.getContents(),
|
endorsement: endorsement.getContents(),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
});
|
||||||
|
|
||||||
return groupSendEndorsementsDataSchema.parse(groupEndorsementsData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TWO_DAYS = DurationInSeconds.fromDays(2);
|
const TWO_DAYS = DurationInSeconds.fromDays(2);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { z } from 'zod';
|
||||||
import { groupBy } from 'lodash';
|
import { groupBy } from 'lodash';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { aciSchema } from '../types/ServiceId';
|
import { aciSchema } from '../types/ServiceId';
|
||||||
|
import { safeParseStrict } from './schemas';
|
||||||
|
|
||||||
const retryItemSchema = z
|
const retryItemSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -53,7 +54,8 @@ export class RetryPlaceholders {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = retryItemListSchema.safeParse(
|
const parsed = safeParseStrict(
|
||||||
|
retryItemListSchema,
|
||||||
window.storage.get(STORAGE_KEY, new Array<RetryItemType>())
|
window.storage.get(STORAGE_KEY, new Array<RetryItemType>())
|
||||||
);
|
);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
|
@ -104,7 +106,7 @@ export class RetryPlaceholders {
|
||||||
// Basic data management
|
// Basic data management
|
||||||
|
|
||||||
async add(item: RetryItemType): Promise<void> {
|
async add(item: RetryItemType): Promise<void> {
|
||||||
const parsed = retryItemSchema.safeParse(item);
|
const parsed = safeParseStrict(retryItemSchema, item);
|
||||||
if (!parsed.success) {
|
if (!parsed.success) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`RetryPlaceholders.add: Item did not match schema ${JSON.stringify(
|
`RetryPlaceholders.add: Item did not match schema ${JSON.stringify(
|
||||||
|
|
179
ts/util/schemas.ts
Normal file
179
ts/util/schemas.ts
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
// Copyright 2024 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type {
|
||||||
|
IfAny,
|
||||||
|
IfEmptyObject,
|
||||||
|
IfNever,
|
||||||
|
IfUnknown,
|
||||||
|
IsLiteral,
|
||||||
|
LiteralToPrimitive,
|
||||||
|
Primitive,
|
||||||
|
} from 'type-fest';
|
||||||
|
import type { SafeParseReturnType, ZodError, ZodType, ZodTypeDef } from 'zod';
|
||||||
|
|
||||||
|
type Schema<Input, Output> = ZodType<Output, ZodTypeDef, Input>;
|
||||||
|
type SafeResult<Output> = SafeParseReturnType<unknown, Output>;
|
||||||
|
|
||||||
|
type LooseInput<T> =
|
||||||
|
IsLiteral<T> extends true ? LiteralToPrimitive<T> : Record<keyof T, unknown>;
|
||||||
|
|
||||||
|
type PartialInput<T> = T extends Primitive
|
||||||
|
? T | null | void
|
||||||
|
: { [Key in keyof T]?: T[Key] | null | void };
|
||||||
|
|
||||||
|
export class SchemaParseError extends TypeError {
|
||||||
|
constructor(schema: Schema<unknown, unknown>, error: ZodError<unknown>) {
|
||||||
|
let message = 'zod: issues found when parsing with schema';
|
||||||
|
if (schema.description) {
|
||||||
|
message += ` (${schema.description})`;
|
||||||
|
}
|
||||||
|
message += ':';
|
||||||
|
for (const issue of error.issues) {
|
||||||
|
message += `\n - ${issue.path.join('.')}: ${issue.message}`;
|
||||||
|
}
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse<Output>(
|
||||||
|
schema: Schema<unknown, Output>,
|
||||||
|
input: unknown
|
||||||
|
): Output {
|
||||||
|
const result = schema.safeParse(input);
|
||||||
|
if (result.success) {
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
throw new SchemaParseError(schema, result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeParse<Output>(
|
||||||
|
schema: Schema<unknown, Output>,
|
||||||
|
input: unknown
|
||||||
|
): SafeResult<Output> {
|
||||||
|
return schema.safeParse(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This uses type-fest to validate that the data being passed into parse() and
|
||||||
|
* safeParse() is not types like `any`, `{}`, `never`, or an unexpected `unknown`.
|
||||||
|
*
|
||||||
|
* `never` is hard to prevent from being passed in, so instead we make the function
|
||||||
|
* arguments themselves not constructable using an intersection with a warning.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Must be exactly `unknown`
|
||||||
|
type UnknownArgs<Data> =
|
||||||
|
IfAny<Data> extends true
|
||||||
|
? [data: Data] & 'Unexpected input `any` must be `unknown`'
|
||||||
|
: IfNever<Data> extends true
|
||||||
|
? [data: Data] & 'Unexpected input `never` must be `unknown`'
|
||||||
|
: IfEmptyObject<Data> extends true
|
||||||
|
? [data: Data] & 'Unexpected input `{}` must be `unknown`'
|
||||||
|
: IfUnknown<Data> extends true
|
||||||
|
? [data: Data]
|
||||||
|
: [data: Data] & 'Unexpected input type must be `unknown`';
|
||||||
|
|
||||||
|
type TypedArgs<Data> =
|
||||||
|
IfAny<Data> extends true
|
||||||
|
? [data: Data] & 'Unexpected input `any` must be typed'
|
||||||
|
: IfNever<Data> extends true
|
||||||
|
? [data: Data] & 'Unexpected input `never` must be typed'
|
||||||
|
: IfEmptyObject<Data> extends true
|
||||||
|
? [data: Data] & 'Unexpected input `{}` must be typed'
|
||||||
|
: IfUnknown<Data> extends true
|
||||||
|
? [data: Data] & 'Unexpected input `unknown` must be typed'
|
||||||
|
: [data: Data];
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
type ParseUnknown = <Input, Output, Data>(schema: Schema<Input, Output>, ...args: UnknownArgs<Data>) => Output;
|
||||||
|
// prettier-ignore
|
||||||
|
type SafeParseUnknown = <Input, Output, Data>(schema: Schema<Input, Output>, ...args: UnknownArgs<Data>) => SafeResult<Output>;
|
||||||
|
// prettier-ignore
|
||||||
|
type ParseStrict = <Input, Output, Data extends Input>(schema: Schema<Input, Output>, ...args: TypedArgs<Data>) => Output;
|
||||||
|
// prettier-ignore
|
||||||
|
type SafeParseStrict = <Input, Output, Data extends Input>(schema: Schema<Input, Output>, ...args: TypedArgs<Data>) => SafeResult<Output>;
|
||||||
|
// prettier-ignore
|
||||||
|
type ParseLoose = <Input, Output, Data extends LooseInput<Input>>(schema: Schema<Input, Output>, ...args: TypedArgs<Data>) => Output;
|
||||||
|
// prettier-ignore
|
||||||
|
type SafeParseLoose = <Input, Output, Data extends LooseInput<Input>>(schema: Schema<Input, Output>, ...args: TypedArgs<Data>) => SafeResult<Output>;
|
||||||
|
// prettier-ignore
|
||||||
|
type ParsePartial = <Input, Output, Data extends PartialInput<Input>>(schema: Schema<Input, Output>, ...args: TypedArgs<Data>) => Output;
|
||||||
|
// prettier-ignore
|
||||||
|
type SafeParsePartial = <Input, Output, Data extends PartialInput<Input>>(schema: Schema<Input, Output>, ...args: TypedArgs<Data>) => SafeResult<Output>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an *unknown* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = unknown // unknown
|
||||||
|
* type Output = { prop: string }
|
||||||
|
* ```
|
||||||
|
* @throws {SchemaParseError}
|
||||||
|
*/
|
||||||
|
export const parseUnknown: ParseUnknown = parse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely parse an *unknown* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = unknown // unknown
|
||||||
|
* type Output = { success: true, error: null, data: { prop: string } }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const safeParseUnknown: SafeParseUnknown = safeParse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a *strict* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = { prop: string } // strict
|
||||||
|
* type Output = { prop: string }
|
||||||
|
* ```
|
||||||
|
* @throws {SchemaParseError}
|
||||||
|
*/
|
||||||
|
export const parseStrict: ParseStrict = parse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely parse a *strict* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = { prop: string } // strict
|
||||||
|
* type Output = { success: true, error: null, data: { prop: string } }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const safeParseStrict: SafeParseStrict = safeParse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a *loose* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = { prop: unknown } // loose
|
||||||
|
* type Output = { prop: string }
|
||||||
|
* ```
|
||||||
|
* @throws {SchemaParseError}
|
||||||
|
*/
|
||||||
|
export const parseLoose: ParseLoose = parse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely parse a *loose* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = { prop: unknown } // loose
|
||||||
|
* type Output = { success: true, error: null, data: { prop: string } }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const safeParseLoose: SafeParseLoose = safeParse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a *partial* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = { prop?: string | null | undefined } // partial
|
||||||
|
* type Output = { prop: string }
|
||||||
|
* ```
|
||||||
|
* @throws {SchemaParseError}
|
||||||
|
*/
|
||||||
|
export const parsePartial: ParsePartial = parse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely parse a *partial* value with a zod schema.
|
||||||
|
* ```ts
|
||||||
|
* type Input = { prop?: string | null | undefined } // partial
|
||||||
|
* type Output = { success: true, error: null, data: { prop: string } }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const safeParsePartial: SafeParsePartial = safeParse;
|
|
@ -73,6 +73,7 @@ import {
|
||||||
import { maybeUpdateGroup } from '../groups';
|
import { maybeUpdateGroup } from '../groups';
|
||||||
import type { GroupSendToken } from '../types/GroupSendEndorsements';
|
import type { GroupSendToken } from '../types/GroupSendEndorsements';
|
||||||
import { isAciString } from './isAciString';
|
import { isAciString } from './isAciString';
|
||||||
|
import { safeParseStrict, safeParseUnknown } from './schemas';
|
||||||
|
|
||||||
const UNKNOWN_RECIPIENT = 404;
|
const UNKNOWN_RECIPIENT = 404;
|
||||||
const INCORRECT_AUTH_KEY = 401;
|
const INCORRECT_AUTH_KEY = 401;
|
||||||
|
@ -603,7 +604,7 @@ export async function sendToGroupViaSenderKey(
|
||||||
{ online, story, urgent }
|
{ online, story, urgent }
|
||||||
);
|
);
|
||||||
|
|
||||||
const parsed = multiRecipient200ResponseSchema.safeParse(result);
|
const parsed = safeParseStrict(multiRecipient200ResponseSchema, result);
|
||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
const { uuids404 } = parsed.data;
|
const { uuids404 } = parsed.data;
|
||||||
if (uuids404 && uuids404.length > 0) {
|
if (uuids404 && uuids404.length > 0) {
|
||||||
|
@ -1022,7 +1023,10 @@ async function handle409Response(
|
||||||
error: HTTPError
|
error: HTTPError
|
||||||
) {
|
) {
|
||||||
const logId = sendTarget.idForLogging();
|
const logId = sendTarget.idForLogging();
|
||||||
const parsed = multiRecipient409ResponseSchema.safeParse(error.response);
|
const parsed = safeParseUnknown(
|
||||||
|
multiRecipient409ResponseSchema,
|
||||||
|
error.response
|
||||||
|
);
|
||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
await waitForAll({
|
await waitForAll({
|
||||||
tasks: parsed.data.map(item => async () => {
|
tasks: parsed.data.map(item => async () => {
|
||||||
|
@ -1068,7 +1072,10 @@ async function handle410Response(
|
||||||
) {
|
) {
|
||||||
const logId = sendTarget.idForLogging();
|
const logId = sendTarget.idForLogging();
|
||||||
|
|
||||||
const parsed = multiRecipient410ResponseSchema.safeParse(error.response);
|
const parsed = safeParseUnknown(
|
||||||
|
multiRecipient410ResponseSchema,
|
||||||
|
error.response
|
||||||
|
);
|
||||||
if (parsed.success) {
|
if (parsed.success) {
|
||||||
await waitForAll({
|
await waitForAll({
|
||||||
tasks: parsed.data.map(item => async () => {
|
tasks: parsed.data.map(item => async () => {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { z } from 'zod';
|
||||||
import { strictAssert } from './assert';
|
import { strictAssert } from './assert';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
|
import { parsePartial, parseUnknown, safeParseUnknown } from './schemas';
|
||||||
|
|
||||||
function toUrl(input: URL | string): URL | null {
|
function toUrl(input: URL | string): URL | null {
|
||||||
if (input instanceof URL) {
|
if (input instanceof URL) {
|
||||||
|
@ -164,7 +165,10 @@ function _route<Key extends string, Args extends object>(
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const parseResult = config.schema.safeParse(rawArgs);
|
const parseResult = safeParseUnknown(
|
||||||
|
config.schema,
|
||||||
|
rawArgs as unknown
|
||||||
|
);
|
||||||
if (parseResult.success) {
|
if (parseResult.success) {
|
||||||
const args = parseResult.data;
|
const args = parseResult.data;
|
||||||
return {
|
return {
|
||||||
|
@ -183,13 +187,13 @@ function _route<Key extends string, Args extends object>(
|
||||||
},
|
},
|
||||||
toWebUrl(args) {
|
toWebUrl(args) {
|
||||||
if (config.toWebUrl) {
|
if (config.toWebUrl) {
|
||||||
return config.toWebUrl(config.schema.parse(args));
|
return config.toWebUrl(parseUnknown(config.schema, args as unknown));
|
||||||
}
|
}
|
||||||
throw new Error('Route does not support web URLs');
|
throw new Error('Route does not support web URLs');
|
||||||
},
|
},
|
||||||
toAppUrl(args) {
|
toAppUrl(args) {
|
||||||
if (config.toAppUrl) {
|
if (config.toAppUrl) {
|
||||||
return config.toAppUrl(config.schema.parse(args));
|
return config.toAppUrl(parseUnknown(config.schema, args as unknown));
|
||||||
}
|
}
|
||||||
throw new Error('Route does not support app URLs');
|
throw new Error('Route does not support app URLs');
|
||||||
},
|
},
|
||||||
|
@ -219,7 +223,7 @@ export const contactByPhoneNumberRoute = _route('contactByPhoneNumber', {
|
||||||
}),
|
}),
|
||||||
parse(result) {
|
parse(result) {
|
||||||
return {
|
return {
|
||||||
phoneNumber: paramSchema.parse(result.hash.groups.phoneNumber),
|
phoneNumber: parsePartial(paramSchema, result.hash.groups.phoneNumber),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
toWebUrl(args) {
|
toWebUrl(args) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
onSync as onViewSync,
|
onSync as onViewSync,
|
||||||
viewSyncTaskSchema,
|
viewSyncTaskSchema,
|
||||||
} from '../messageModifiers/ViewSyncs';
|
} from '../messageModifiers/ViewSyncs';
|
||||||
|
import { safeParseUnknown } from './schemas';
|
||||||
|
|
||||||
const syncTaskDataSchema = z.union([
|
const syncTaskDataSchema = z.union([
|
||||||
deleteMessageSchema,
|
deleteMessageSchema,
|
||||||
|
@ -86,7 +87,7 @@ export async function queueSyncTasks(
|
||||||
await removeSyncTaskById(id);
|
await removeSyncTaskById(id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parseResult = syncTaskDataSchema.safeParse(data);
|
const parseResult = safeParseUnknown(syncTaskDataSchema, data);
|
||||||
if (!parseResult.success) {
|
if (!parseResult.success) {
|
||||||
log.error(
|
log.error(
|
||||||
`${innerLogId}: Failed to parse. Deleting. Error: ${parseResult.error}`
|
`${innerLogId}: Failed to parse. Deleting. Error: ${parseResult.error}`
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
} from '../context/localeMessages';
|
} from '../context/localeMessages';
|
||||||
import { waitForSettingsChange } from '../context/waitForSettingsChange';
|
import { waitForSettingsChange } from '../context/waitForSettingsChange';
|
||||||
import { isTestOrMockEnvironment } from '../environment';
|
import { isTestOrMockEnvironment } from '../environment';
|
||||||
|
import { parseUnknown } from '../util/schemas';
|
||||||
|
|
||||||
const emojiListCache = new Map<string, LocaleEmojiListType>();
|
const emojiListCache = new Map<string, LocaleEmojiListType>();
|
||||||
|
|
||||||
|
@ -55,8 +56,8 @@ export const MinimalSignalContext: MinimalSignalContextType = {
|
||||||
'OptionalResourceService:getData',
|
'OptionalResourceService:getData',
|
||||||
`emoji-index-${locale}.json`
|
`emoji-index-${locale}.json`
|
||||||
);
|
);
|
||||||
const json = JSON.parse(Buffer.from(buf).toString());
|
const json: unknown = JSON.parse(Buffer.from(buf).toString());
|
||||||
const result = LocaleEmojiListSchema.parse(json);
|
const result = parseUnknown(LocaleEmojiListSchema, json);
|
||||||
emojiListCache.set(locale, result);
|
emojiListCache.set(locale, result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue