import type { DeviceHardware, Fingerprint } from '../../../../domain/device.js' import type { EncryptedDeviceRegistration } from '../../../../domain/device-registration-manifest.js' import type { EncryptionPublicKey, SigningPublicKey, } from '../../../../domain/keypair.js' import type { Timestamp } from '../../../../domain/timestamp.js' import type { Infrastructure } from '../../../../infrastructure/mod.js' import type { Result } from '../../../../framework/types/result.js' import { isError, makeError, makeSuccess, unwrapResult, } from '../../../../framework/types/result.js' import { DeviceAlreadyRegisteredError, DeviceQuotaExceededError, DeviceRegistrationEligibilityError, InvalidDeviceRegistrationCodeError, InvalidDeviceRegistrationWindowError, RegisterDeviceClientError, RegisterDevicePlatformError, } from './errors.js' import { encodeJson } from '../../../../framework/json/mod.js' interface Options { infrastructure: Infrastructure } export interface Dto { readonly code: string readonly nonce: Timestamp readonly device: Readonly<{ fingerprint: Fingerprint hardware: DeviceHardware }> readonly encryption: Readonly<{ publicKey: EncryptionPublicKey }> readonly signing: Readonly<{ publicKey: SigningPublicKey }> } export interface ErrorResponse { readonly type: string readonly message: string } type RegisterDeviceError = | DeviceAlreadyRegisteredError | DeviceQuotaExceededError | DeviceRegistrationEligibilityError | InvalidDeviceRegistrationCodeError | InvalidDeviceRegistrationWindowError | RegisterDeviceClientError | RegisterDevicePlatformError export function buildRegisterDevice(options: Options) { return async function registerDevice( dto: Dto, ): Promise> { const { httpAdapter } = options.infrastructure const isCodeEmpty = /^\s*$/.test(dto.code) if (isCodeEmpty) { return makeError( new InvalidDeviceRegistrationCodeError( "Device registration code can't be empty.", ), ) } const dtoResult = encodeJson(dto) if (isError(dtoResult)) { return makeError( new RegisterDeviceClientError( `failed to encode the DTO: ${dtoResult.error}`, ), ) } const requestResult = await httpAdapter.request('/licensing/devices', { method: 'POST', body: unwrapResult(dtoResult), }) if (isError(requestResult)) { return makeError(new RegisterDeviceClientError(requestResult.error)) } const response = unwrapResult(requestResult) const responseResult = await httpAdapter.parseResponseBody< ErrorResponse | { registration: EncryptedDeviceRegistration } >(response) if (isError(responseResult)) { return makeError(new RegisterDeviceClientError(responseResult.error)) } const { body, status } = unwrapResult(responseResult) if (status !== 201) { const error = body as ErrorResponse const type = error.type const message = error.message switch (status) { case 429: { return makeError(new DeviceQuotaExceededError(message)) } case 409: { return makeError(new DeviceAlreadyRegisteredError(message)) } case 403: { return makeError(new DeviceRegistrationEligibilityError(message)) } case 400: { return makeError( type === 'InvalidDeviceRegistrationCodeError' ? new InvalidDeviceRegistrationCodeError(message) : new InvalidDeviceRegistrationWindowError(message), ) } default: { return makeError(new RegisterDevicePlatformError(message)) } } } const { registration } = body as { registration: EncryptedDeviceRegistration } return makeSuccess(registration) } }