UNPKG

@signalapp/mock-server

Version:
365 lines (364 loc) 17.4 kB
import { SenderCertificate } from '@signalapp/libsignal-client'; import { AuthCredentialPresentation, CreateCallLinkCredentialRequest, CreateCallLinkCredentialResponse, GenericServerSecretParams, ProfileKeyCredentialRequest, ServerSecretParams } from '@signalapp/libsignal-client/zkgroup'; import http2 from 'http2'; import { AddressInfo } from 'net'; import { signalservice as Proto } from '../../protos/compiled'; import { ServerCertificate } from '../crypto'; import { ChangeNumberOptions, Device, DeviceKeys } from '../data/device'; import { BackupHeaders, BackupMediaBatch, CreateCallLink, DeleteCallLink, Message, RegisterAccountResponse, SetBackupId, SetBackupKey, UpdateCallLink, UsernameConfirmation, UsernameReservation, VerificationSessionStorage } from '../data/schemas'; import { AciString, AttachmentId, DeviceId, PniString, ProvisionIdString, ProvisioningCode, RegistrationId, ServiceIdKind, ServiceIdString } from '../types'; import { ModifyGroupResult, ServerGroup } from './group'; import { CallingEraId, CallingRoomId, CallInfo, CallingUserId, CallType, CallingDemuxId } from '../calling'; import { SfuService } from '../sfu/service'; import { SfuClientStatus } from '../sfu/call'; import { IcePassword, IceUsernameFragment } from '../sfu/ice'; import { ServerMediaAddress } from '../sfu/config'; import { CallingPublicKey } from '../sfu/crypto'; import type { JsonValue, PartialDeep } from 'type-fest'; export declare enum EnvelopeType { CipherText = "CipherText", Plaintext = "Plaintext", PreKey = "PreKey", SealedSender = "SealedSender", SenderKey = "SenderKey" } export type ProvisioningResponse = Readonly<{ envelope: Buffer<ArrayBuffer>; }>; export type CredentialsRange = Readonly<{ from: number; to: number; }>; export type StorageCredentials = Readonly<{ username: string; password: string; }>; export type Credentials = Array<{ credential: string; redemptionTime: number; }>; export type BackupCredentials = Readonly<{ messages: Credentials; media: Credentials; }>; export type ChallengeResponse = Readonly<{ code: number; data: unknown; }>; export type PreparedMultiDeviceMessage = Readonly<{ timestamp: bigint; targets: ReadonlyArray<[Device, Message]>; }>; export type ProvisionDeviceOptions = Readonly<{ number: string; password: string; provisioningCode: ProvisioningCode; registrationId: RegistrationId; pniRegistrationId: RegistrationId; }>; export type RegisterDeviceOptions = Readonly<({ primary?: undefined; provisionId?: ProvisionIdString; number: string; password: string; } | { primary: Device; provisionId?: undefined; number?: undefined; password?: string; }) & { registrationId: RegistrationId; pniRegistrationId: RegistrationId; }>; export type PrepareMultiDeviceMessageResult = Readonly<{ status: 'stale'; staleDevices: ReadonlyArray<number>; } | { status: 'incomplete'; missingDevices: ReadonlyArray<number>; extraDevices: ReadonlyArray<number>; } | { status: 'unknown'; } | { status: 'ok'; targetServiceId: ServiceIdString; result: PreparedMultiDeviceMessage; }>; export type ConfirmUsernameResult = Readonly<{ usernameLinkHandle?: string; }>; export type SetUsernameLinkResult = Readonly<{ entropy: Uint8Array<ArrayBuffer>; serverId: string; }>; export type StorageWriteResult = Readonly<{ updated: false; manifest: Proto.StorageManifest.Params; error?: void; } | { updated: true; manifest?: void; error?: void; } | { updated?: void; error: string; }>; export type ModifyGroupOptions = Readonly<{ group: ServerGroup; actions: Proto.GroupChange.Actions.Params; aciCiphertext: Uint8Array<ArrayBuffer>; pniCiphertext: Uint8Array<ArrayBuffer>; }>; export type EncryptedStickerPack = Readonly<{ id: Buffer<ArrayBuffer>; manifest: Buffer<ArrayBuffer>; stickers: ReadonlyArray<Buffer<ArrayBuffer>>; }>; export type IsSendRateLimitedOptions = Readonly<{ source: ServiceIdString; target: ServiceIdString; }>; export { type ModifyGroupResult }; interface WebSocket { sendMessage: (message: Buffer<ArrayBuffer> | 'empty') => Promise<void>; close: (code: number) => void; } export type ServerJoinCallRequest = Readonly<{ roomId: CallingRoomId; userId: CallingUserId; isAllowedToInitiateGroupCall: boolean; clientIceUsernameFragment: IceUsernameFragment; clientIcePassword: IcePassword; clientPublicKey: CallingPublicKey; clientHkdfExtraInfo: Uint8Array<ArrayBuffer> | null; callType: CallType; isAdmin: boolean; newClientsRequireApproval: boolean; approvedUsers: ReadonlyArray<CallingUserId> | null; }>; export type ServerJoinCallResponse = Readonly<{ demuxId: CallingDemuxId; serverMediaAddress: ServerMediaAddress; serverIceUsernameFragment: IceUsernameFragment; serverIcePassword: IcePassword; serverPublicKey: CallingPublicKey; callEraId: CallingEraId; callCreatorUserId: CallingUserId; clientStatus: SfuClientStatus; }>; export type CallLinkEntry = Readonly<{ adminPasskey: Buffer<ArrayBuffer>; encryptedName: string; restrictions: 'none' | 'adminApproval'; revoked: boolean; expiration: number; }>; export type BackupInfo = Readonly<{ cdn: 3; backupDir: string; mediaDir: string; backupName: string; usedSpace?: number; }>; export type BackupMediaObject = Readonly<{ cdn: 3; mediaId: string; objectLength: number; }>; export type BackupMediaList = Readonly<{ storedMediaObjects: ReadonlyArray<BackupMediaObject>; backupDir: string; mediaDir: string; cursor: string | undefined; }>; export type BackupMediaCursor = { readonly backupId: string; remainingMedia: ReadonlyArray<BackupMediaObject>; }; export type ListBackupMediaOptions = Readonly<{ cursor: string | undefined; limit: number; }>; export type BackupMediaBatchResponse = Readonly<{ status: number; failureReason?: string; cdn: 3; mediaId: string; }>; export type BackupMediaBatchResult = Readonly<{ responses: ReadonlyArray<BackupMediaBatchResponse>; }>; export type TransferArchiveResponse = Readonly<{ error: 'RELINK_REQUESTED' | 'CONTINUE_WITHOUT_UPLOAD'; } | { cdn: 3; key: string; }>; export type AttachmentUploadForm = Readonly<{ cdn: 3; key: string; headers: Record<string, string>; signedUploadLocation: string; }>; export type RemoteConfigValueType = { enabled: boolean; value?: string; }; export type HardcodedResponseError = { code: number; data: PartialDeep<JsonValue>; }; export declare abstract class Server { private readonly devices; private readonly devicesByServiceId; private readonly devicesByAuth; private readonly usedServiceIds; private readonly usedProvisionIds; private readonly storageAuthByUsername; private readonly storageAuthByDevice; private readonly storageManifestByAci; private readonly storageItemsByAci; private readonly provisioningCodes; private readonly attachments; private readonly stickerPacks; private readonly webSockets; private readonly messageQueue; private readonly groups; private readonly aciByUsername; private readonly aciByReservedUsername; private readonly usernameByAci; private readonly reservedUsernameByAci; private readonly usernameLinkIdByServiceId; private readonly usernameLinkById; private readonly callsByRoomId; private readonly callLinksByRoomId; private readonly backupAuthReqByAci; private readonly backupKeyById; private readonly backupCDNPasswordById; private readonly backupMediaById; private readonly backupMediaCursorById; private readonly remoteConfig; protected privCertificate: ServerCertificate | undefined; protected privZKSecret: ServerSecretParams | undefined; protected privGenericServerSecret: GenericServerSecretParams | undefined; protected privBackupServerSecret: GenericServerSecretParams | undefined; protected https: http2.Http2SecureServer | undefined; protected verificationStore: Map<string, VerificationSessionStorage>; protected backupAuth: { username: string; password: string; }; protected registerResponseData: Partial<RegisterAccountResponse> | undefined; protected registerResponseError: HardcodedResponseError | undefined; protected sfuService: SfuService; address(): AddressInfo; generateAci(): Promise<AciString>; generatePni(): Promise<PniString>; generateProvisionId(): Promise<ProvisionIdString>; releaseProvisionId(id: ProvisionIdString): Promise<void>; abstract getProvisioningResponse(id: ProvisionIdString, abortSignal?: AbortSignal): Promise<ProvisioningResponse>; setRegisterResponseData(data: Partial<RegisterAccountResponse>): void; getRegisterResponseData(): Partial<RegisterAccountResponse> | undefined; setRegisterResponseError(error: HardcodedResponseError | undefined): void; getRegisterResponseError(): HardcodedResponseError | undefined; registerDevice({ primary, provisionId, number: maybeNumber, registrationId, pniRegistrationId, password, }: RegisterDeviceOptions): Promise<Device>; getProvisioningCode(id: ProvisionIdString, number: string): Promise<ProvisioningCode>; provisionDevice({ number, password, provisioningCode, registrationId, pniRegistrationId, }: ProvisionDeviceOptions): Promise<Device>; private setDeviceAuthPassword; updateDeviceKeys(device: Device, serviceIdKind: ServiceIdKind, keys: Omit<DeviceKeys, 'identityKey'>): Promise<void>; changeDeviceNumber(device: Device, options: ChangeNumberOptions): Promise<void>; getVerificationSession(id: string): VerificationSessionStorage | undefined; saveVerificationSession(store: VerificationSessionStorage): void; auth(username: string, password: string): Promise<Device | undefined>; setRemoteConfig(key: string, value: RemoteConfigValueType): void; getRemoteConfig(): Map<string, RemoteConfigValueType>; protected storeAttachment(attachment: Buffer<ArrayBuffer>): Promise<AttachmentId>; fetchAttachment(id: AttachmentId): Promise<Buffer<ArrayBuffer> | undefined>; fetchStickerPack(packId: string): Promise<Buffer<ArrayBuffer> | undefined>; fetchSticker(packId: string, stickerId: number): Promise<Buffer<ArrayBuffer> | undefined>; storeStickerPack(pack: EncryptedStickerPack): Promise<void>; getAttachmentUploadForm(folder: string, key: string): Promise<AttachmentUploadForm>; prepareMultiDeviceMessage(source: Device | undefined, targetServiceId: ServiceIdString, messages: ReadonlyArray<Message>, timestamp: bigint): Promise<PrepareMultiDeviceMessageResult>; handlePreparedMultiDeviceMessage(source: Device | undefined, targetServiceId: ServiceIdString, prepared: PreparedMultiDeviceMessage): Promise<void>; abstract handleMessage(source: Device | undefined, serviceIdKind: ServiceIdKind, envelopeType: EnvelopeType, target: Device, encrypted: Buffer<ArrayBuffer>, timestamp: bigint): Promise<void>; addWebSocket(device: Device, socket: WebSocket): Promise<void>; removeWebSocket(device: Device, socket: WebSocket): void; send(target: Device, message: Buffer<ArrayBuffer>): Promise<void>; createGroup(group: Proto.Group.Params): Promise<ServerGroup>; modifyGroup({ group, actions, aciCiphertext, pniCiphertext, }: ModifyGroupOptions): Promise<ModifyGroupResult>; getGroup(publicParams: Uint8Array<ArrayBuffer>): Promise<ServerGroup | undefined>; getStorageAuth(device: Device): Promise<StorageCredentials>; storageAuth(username: string, password: string): Promise<Device | undefined>; getStorageManifest(device: Device): Promise<Proto.StorageManifest.Params | undefined>; applyStorageWrite(device: Device, { manifest, clearAll, insertItem, deleteKey }: Proto.WriteOperation.Params, shouldNotify?: boolean): Promise<StorageWriteResult>; private clearStorageItems; private setStorageItem; getStorageItem(device: Device, key: Buffer<ArrayBuffer>): Promise<Buffer<ArrayBuffer> | undefined>; getAllStorageKeys(device: Device): Promise<Array<Buffer<ArrayBuffer>>>; getStorageItems(device: Device, keys: ReadonlyArray<Buffer<ArrayBuffer>>): Promise<Array<Proto.StorageItem.Params> | undefined>; deleteStorageItem(device: Device, key: Buffer<ArrayBuffer>): Promise<void>; protected abstract onStorageManifestUpdate(device: Device, version: bigint): Promise<void>; joinCall(request: ServerJoinCallRequest): Promise<ServerJoinCallResponse>; removeCall(roomId: CallingRoomId, eraId: CallingEraId): Promise<void>; peekCall(roomId: CallingRoomId, userId: CallingUserId): Promise<CallInfo>; reserveUsername(aci: AciString, { usernameHashes }: UsernameReservation): Promise<Buffer<ArrayBuffer> | undefined>; confirmUsername(aci: AciString, { usernameHash, zkProof, encryptedUsername }: UsernameConfirmation): Promise<ConfirmUsernameResult | undefined>; deleteUsername(aci: AciString): Promise<void>; lookupByUsernameHash(usernameHash: Buffer<ArrayBuffer>): Promise<AciString | undefined>; replaceUsernameLink(aci: AciString, encryptedValue: Uint8Array<ArrayBuffer>): Promise<string>; lookupByUsernameLink(lookupId: string): Promise<Buffer<ArrayBuffer> | undefined>; lookupByUsername(username: string): Promise<AciString | undefined>; setUsername(aci: AciString, username: string): Promise<void>; setUsernameLink(aci: AciString, username: string): Promise<SetUsernameLinkResult>; createCallLinkAuth(device: Device, request: CreateCallLinkCredentialRequest): Promise<CreateCallLinkCredentialResponse>; hasCallLink(roomId: string): boolean; createCallLink(roomId: string, { adminPasskey }: CreateCallLink): Promise<CallLinkEntry>; getCallLink(roomId: string): Promise<CallLinkEntry | undefined>; updateCallLink(roomId: string, { adminPasskey, name, restrictions, revoked }: UpdateCallLink): Promise<CallLinkEntry>; deleteCallLink(roomId: string, { adminPasskey }: DeleteCallLink): Promise<void>; getDevice(number: string, deviceId: DeviceId): Promise<Device | undefined>; removeDevice(number: string, deviceId: DeviceId): Promise<void>; getDeviceByServiceId(serviceId: ServiceIdString, deviceId?: DeviceId): Promise<Device | undefined>; getAllDevicesByServiceId(serviceId: ServiceIdString): Promise<ReadonlyArray<Device>>; getSenderCertificate(device: Device): Promise<SenderCertificate>; getGroupCredentials({ aci, pni }: Device, range: CredentialsRange): Promise<Credentials>; verifyGroupCredentials(publicParams: Buffer<ArrayBuffer>, credential: Buffer<ArrayBuffer>): Promise<AuthCredentialPresentation>; getCallLinkAuthCredentials({ aci }: Device, range: CredentialsRange): Promise<Credentials>; issueExpiringProfileKeyCredential({ aci, profileKeyCommitment }: Device, request: ProfileKeyCredentialRequest): Promise<Buffer<ArrayBuffer> | undefined>; setBackupId({ aci }: Device, { messagesBackupAuthCredentialRequest, mediaBackupAuthCredentialRequest, }: SetBackupId): Promise<void>; setBackupKey(headers: BackupHeaders, { backupIdPublicKey }: SetBackupKey): Promise<void>; refreshBackup(headers: BackupHeaders): Promise<void>; getBackupInfo(headers: BackupHeaders): Promise<BackupInfo>; listBackupMedia(headers: BackupHeaders, { cursor, limit }: ListBackupMediaOptions): Promise<BackupMediaList>; getBackupMediaUploadForm(headers: BackupHeaders): Promise<AttachmentUploadForm>; getBackupUploadForm(headers: BackupHeaders): Promise<AttachmentUploadForm>; backupMediaBatch(headers: BackupHeaders, batch: BackupMediaBatch): Promise<BackupMediaBatchResult>; getBackupCDNAuth(headers: BackupHeaders): Promise<Record<string, string>>; getBackupAuth(): { username: string; password: string; }; setBackupAuth(auth: { username: string; password: string; }): void; authorizeBackupCDN(backupId: string, password: string): Promise<boolean>; getBackupCredentials({ aci, backupLevel }: Device, range: CredentialsRange): Promise<BackupCredentials | undefined>; protected onNewBackupMediaObject(backupId: string, media: BackupMediaObject): Promise<void>; protected abstract backupTransitAttachments(backupId: string, batch: BackupMediaBatch): Promise<Array<BackupMediaBatchResponse>>; abstract getTransferArchive(device: Device): Promise<TransferArchiveResponse>; abstract isUnregistered(serviceId: ServiceIdString): boolean; abstract isSendRateLimited(options: IsSendRateLimitedOptions): boolean; abstract getResponseForChallenges(): ChallengeResponse | undefined; protected set certificate(value: ServerCertificate); protected get certificate(): ServerCertificate; protected set genericServerSecret(value: GenericServerSecretParams); protected get genericServerSecret(): GenericServerSecretParams; protected set backupServerSecret(value: GenericServerSecretParams); protected get backupServerSecret(): GenericServerSecretParams; protected set zkSecret(value: ServerSecretParams); protected get zkSecret(): ServerSecretParams; private sendQueue; private issueCredentials; private authenticateBackup; }