Files
Desktop/packages/types

@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 CountryCode = Tagged<string, 'CountryCode'>;
type CurrencyCode = Tagged<string, 'CurrencyCode'>;
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 AccountEntropyPool = Tagged<string, 'AccountEntropyPool'>;

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 Username = Tagged<Utf8.Of<`${UsernameNickname}.${UsernameDiscriminator}`>, 'Username'>;
type Username.Params = Readonly<{ nickname: UsernameNickname; discriminator: UsernameDiscriminator }>;
type UsernameNickname = Tagged<Utf8.Of<string>, 'UsernameNickname'>;
type UsernameDiscriminator = Tagged<`${number}`, 'UsernameDiscriminator'>;
type UsernameHash = Tagged<Bytes, 'UsernameHash'>;
type UsernameProof = Tagged<Bytes, 'UsernameProof'>;

type RegistrationId = Tagged<Uint16, 'RegistrationId'>;

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 ProfileKey = ProfileKey.Decoded;
type ProfileKey.Opaque = Tagged<unknown, 'ProfileKey'>;
type ProfileKey.Decoded = Base64.Of<ProfileKey.Opaque>;
type ProfileKey.Encoded = Bytes.Of<ProfileKey.Opaque>;

type ProfileKeyVersion = ProfileKeyVersion.Decoded;
type ProfileKeyVersion.Opaque = Tagged<string, 'ProfileKeyVersion'>;
type ProfileKeyVersion.Decoded = Utf8.Of<ProfileKeyVersion.Opaque>;
type ProfileKeyVersion.Encoded = Bytes.Of<ProfileKeyVersion.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 GroupVersion = Tagged<Uint32, 'GroupVersion'>;

type GroupMasterKey = GroupMasterKey.Decoded;
type GroupMasterKey.Opaque = Tagged<unknown, 'GroupMasterKey'>;
type GroupMasterKey.Decoded = Base64.Of<GroupMasterKey.Opaque>;
type GroupMasterKey.Encoded = Bytes.Of<GroupMasterKey.Opaque>;

type GroupSecretParams = GroupSecretParams.Decoded;
type GroupSecretParams.Opaque = Tagged<unknown, 'GroupSecretParams'>;
type GroupSecretParams.Decoded = Base64.Of<GroupSecretParams.Opaque>;
type GroupSecretParams.Encoded = Bytes.Of<GroupSecretParams.Opaque>;

type GroupPublicParams = GroupPublicParams.Decoded;
type GroupPublicParams.Opaque = Tagged<unknown, 'GroupPublicParams'>;
type GroupPublicParams.Decoded = Base64.Of<GroupPublicParams.Opaque>;
type GroupPublicParams.Encoded = Bytes.Of<GroupPublicParams.Opaque>;

type GroupInviteLinkPassword = GroupInviteLinkPassword.Decoded;
type GroupInviteLinkPassword.Opaque = Tagged<unknown, 'GroupInviteLinkPassword'>;
type GroupInviteLinkPassword.Decoded = Base64.Of<GroupInviteLinkPassword.Opaque>;
type GroupInviteLinkPassword.Encoded = Bytes.Of<GroupInviteLinkPassword.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>;

type UnidentifiedAccessKey = UnidentifiedAccessKey.Decoded;
type UnidentifiedAccessKey.Opaque = Tagged<unknown, 'UnidentifiedAccessKey'>;
type UnidentifiedAccessKey.Decoded = Base64.Of<UnidentifiedAccessKey.Opaque>;
type UnidentifiedAccessKey.Encoded = Bytes.Of<UnidentifiedAccessKey.Opaque>;

type ServerGuid = ServerGuid.Decoded;
type ServerGuid.Opaque = Tagged<Uuid, 'ServerGuid'>;
type ServerGuid.Decoded = Utf8.Of<ServerGuid.Opaque>;
type ServerGuid.Encoded = Bytes.Of<ServerGuid.Opaque>;

type ReportSpamToken = ReportSpamToken.Decoded;
type ReportSpamToken.Opaque = Tagged<unknown, 'ReportSpamToken'>;
type ReportSpamToken.Decoded = Base64.Of<ReportSpamToken.Opaque>;
type ReportSpamToken.Encoded = Bytes.Of<ReportSpamToken.Opaque>;

type MuteExpiration = Tagged<Float64, 'MuteExpiration'>;

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:
    • Dimension
    • Width
    • Height
    • BlurHash
    • Email
  • File System:
    • FileName
    • FilePath
    • FileExtension
  • Net:
    • BasicAuth
    • IpV4
    • IpV6
    • IpAddr
    • Domain/Hostname
    • Protocol
    • Port
  • Service:
    • UserText / TrustedText