mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-07-13 00:26:56 +01:00
@signalapp/types
This package provides shared opaque and utility types and related helper functions for use across different Signal libraries and desktop apps.
import { Aci, SentTimestampMs, DurationSecs } from '@signalapp/types';
export type PinnedMessage = Readonly<{
targetSentTimestamp: SentTimestampMs;
targetAuthorAci: Aci;
pinDuration: DurationSecs;
}>;
API
import type { Tagged } from 'type-fest';
// Result
type Result<T, E = string> = Result.Ok<T> | Result.Err<E>;
type Result.Ok<T> = Readonly<{ ok: true; value: T; error?: never; }>;
type Result.Err<E = string> = Readonly<{ ok: false; value?: never; error: E; }>;
// Numbers
type Uint8 = Tagged<number, 'Uint8'>;
type Uint16 = Tagged<number, 'Uint16'>;
type Uint32 = Tagged<number, 'Uint32'>;
type BigUint64 = Tagged<bigint, 'BigUint64'>;
type BigUint128 = Tagged<bigint, 'BigUint128'>;
type Int8 = Tagged<number, 'Int8'>;
type Int16 = Tagged<number, 'Int16'>;
type Int32 = Tagged<number, 'Int32'>;
type BigInt64 = Tagged<bigint, 'BigInt64'>;
type BigInt128 = Tagged<bigint, 'BigInt128'>;
type Float16 = Tagged<number, 'Float16'>;
type Float32 = Tagged<number, 'Float32'>;
type Float64 = Tagged<number, 'Float64'>;
// Units
type UnitBytes = Tagged<Float64, 'UnitBytes'>;;
type UnitKilobytes = Tagged<Float64, 'UnitKilobytes'>;
type UnitKibibytes = Tagged<Float64, 'UnitKibibytes'>;
// Datetime
type TimestampMs = Tagged<number, 'TimestampMs'>;
type TimestampSecs = Tagged<number, 'TimestampSecs'>;
type DurationMs = Tagged<Uint32, 'DurationMs'>;
type DurationSecs = Tagged<Uint32, 'DurationSecs'>;
type DurationDays = Tagged<Uint32, 'DurationDays'>;
type PlainTimeHourMin = Tagged<Uint32, 'PlainTimeHourMin'>;
// Encodings
type Bytes = Uint8Array<ArrayBuffer>;
type Bytes.Of<T> = Tagged<Bytes, 'Bytes.Of', T>;
type Base64 = Tagged<string, 'Base64'>;
type Base64.Of<T> = Tagged<Base64, 'Base64.Of', T>;
type Base64Url = Tagged<string, 'Base64Url'>;
type Base64Url.Of<T> = Tagged<Base64Url, 'Base64URL.Of', T>;
type Hex = Tagged<string, 'Hex'>;
type Hex.Of<T> = Tagged<Hex, 'Hex.Of', T>;
type Json = Tagged<string, 'Json'>;
type Json.Of<T extends JsonValue> = Tagged<Json, 'Json.Of', T>;
type JsonPrimitive = null | boolean | number | string;
type JsonArray = Array<JsonValue> | ReadonlyArray<JsonValue>;
type JsonObject = { [Key in string]: JsonValue };
type JsonValue = JsonPrimitive | JsonArray | JsonObject;
type Utf8.Of<T extends string> = Tagged<T, 'Utf8.Of'>;
// Formats
type HexColor = Tagged<`#${string}`, 'HexColor'>;
type HttpUrl = Tagged<string, 'HttpUrl'>;
type LanguageTag = Tagged<string, 'LanguageTag'>;
type MimeType = Tagged<`${string}/${string}`, 'MimeType'>;
type Semver = Tagged<string, 'Semver'>;
type Uuid = Tagged<string, 'Uuid'>;
// Service
type E164 = E164.Decoded;
type E164.Opaque = Tagged<`+${string}`, 'E164'>;
type E164.Encoded = Utf8.Of<E164.Opaque>;
type E164.Decoded = Bytes.Of<E164.Opaque>;
type Aci = Aci.Decoded;
type Aci.Opaque = Tagged<Uuid, 'Aci'>;
type Aci.Decoded = Utf8.Of<Aci.Opaque>;
type Aci.Encoded = Bytes.Of<Aci.Opaque>;
type Pni = Utf8.Of<Pni.Opaque>;
type Pni.Opaque = Tagged<`PNI:${Uuid}`, 'Pni'>;
type Pni.Decoded = Utf8.Of<Pni.Opaque>;
type Pni.Encoded = Bytes.Of<Pni.Opaque>;
type UntaggedPni = UntaggedPni.Decoded;
type UntaggedPni.Opaque = Tagged<Uuid, 'UntaggedPni'>;
type UntaggedPni.Decoded = Utf8.Of<UntaggedPni.Opaque>;
type UntaggedPni.Encoded = Bytes.Of<UntaggedPni.Opaque>;
type ServiceId = ServiceId.Decoded;
type ServiceId.Decoded = Aci | Pni;
type ServiceId.Encoded = Bytes.Of<Decoded>;
type DeviceId = Tagged<Uint32, 'DeviceId'>;
type Address = Address.Decoded;
type Address.Opaque = Tagged<`${ServiceId}.${DeviceId}`, 'Address'>;
type Address.Decoded = Utf8.Of<Address.Opaque>;
type Address.Encoded = Bytes.Of<Address.Opaque>;
type AddressInfo = Tagged<AddressInfo.Params, 'AddressInfo'>;
type AddressInfo.Params = Readonly<{ serviceId: ServiceId; deviceId: DeviceId; }>;
type DistributionId = Utf8.Of<DistributionId.Opaque>;
type DistributionId.Opaque = Tagged<Uuid, 'DistributionId'>;
type DistributionId = DistributionId.Decoded;
type DistributionId.Opaque = Tagged<Uuid, 'DistributionId'>;
type DistributionId.Decoded = Utf8.Of<DistributionId.Opaque>;
type DistributionId.Encoded = Bytes.Of<DistributionId.Opaque>;
type GroupId = GroupId.Decoded;
type GroupId.Opaque = Tagged<unknown, 'GroupId'>;
type GroupId.Decoded = Base64.Of<GroupId.Opaque>;
type GroupId.Encoded = Bytes.Of<GroupId.Opaque>;
type DistributionListId = DistributionListId.Decoded;
type DistributionListId.Opaque = Tagged<Uuid, 'DistributionListId'>;
type DistributionListId.Decoded = Utf8.Of<DistributionListId.Opaque>;
type DistributionListId.Encoded = Bytes.Of<DistributionListId.Opaque>;
type SentTimestampMs = Tagged<TimestampMs, 'SentTimestampMs'>;
type ServerTimestampMs = Tagged<TimestampMs, 'ServerTimestampMs'>;
type ReceivedTimestampMs = Tagged<TimestampMs, 'ReceivedTimestampMs'>;
type StorageManifestVersion = Tagged<BigUint64, 'StorageManifestVersion'>;
type StorageItemKey = StorageItemKey.Decoded;
type StorageItemKey.Opaque = Tagged<unknown, 'StorageItemKey'>;
type StorageItemKey.Decoded = Base64.Of<Opaque>;
type StorageItemKey.Encoded = Bytes.Of<Opaque>;
Schemas
Almost all of these types provide Zod schemas:
import { Aci, SentTimestampMs, DurationSecs } from '@signalapp/types';
export const PinnedMessageSchema = z.object({
targetSentTimestamp: SentTimestampMs.Schema,
targetAuthorAci: Aci.Schema,
pinDuration: DurationSecs.Schema,
});
Helpers
Types have helpers for casting loosely typed data to strict types:
Note: You should prefer to use the most specific
from*method that you can, it will generally have less runtime cost checking the type is valid.
Aci.isValid(input: string): input is Aci; // boolean
Aci.fromUuid(input: Uuid): Aci; // throws if invalid
Aci.fromString(input: string): Aci; // throws if invalid
Encodings
Encodings have special <Encoding>.Of<T> types to represent internal opaque
types:
// Maybe this just represents some random number generation that we should not be aware of
export type MyOpaqueType = Tagged<unknown, 'MyOpaqueType'>;
export function decode(input: Bytes.Of<MyOpaqueType>): Base64.Of<MyOpaqueType> {
return Base64.fromBytes(input);
}
export function encode(input: Bytes.Of<MyOpaqueType>): Base64.Of<MyOpaqueType> {
return Base64.toBytes(input);
}
Many of the "service" types use these <Encoding>.Of types to wrap their own
internal Opaque type with expected encodings along with encode and decode
helpers:
export type StorageItemKey = StorageItemKey.Decoded;
export namespace StorageItemKey {
type Opaque = Tagged<unknown, 'StorageItemKey'>;
export type Decoded = Base64.Of<Opaque>;
export type Encoded = Bytes.Of<Opaque>;
export function decode(input: Encoded): StorageItemKey {
return Base64.fromBytes(input);
}
export function encode(input: StorageItemKey): Encoded {
return Base64.toBytes(input);
}
}
Future Ideas
- General:
NonEmpty<T extends { length: number }>NonZero<T extends number | bigint>
- Units:
DimensionWidthHeight
- Formats:
BlurHashRegionIdCurrencyCodeEmail
- File System:
FileNameFilePathFileExtension
- Net:
BasicAuthIpV4IpV6IpAddrDomain/HostnameProtocolPort
- Service:
RegistrationIdServerGuidProfileKey- `ProfileKeyVersion
UsernameReportingTokenGroupVersionMasterKeyPublicParamsSecretParamsGroupInviteLinkPasswordUserText/TrustedText