@signalapp/mock-server
Version:
Mock Signal Server for writing tests
211 lines (210 loc) • 7.53 kB
JavaScript
"use strict";
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Device = void 0;
const node_crypto_1 = require("node:crypto");
const debug_1 = __importDefault(require("debug"));
const libsignal_client_1 = require("@signalapp/libsignal-client");
const zkgroup_1 = require("@signalapp/libsignal-client/zkgroup");
const types_1 = require("../types");
const debug = (0, debug_1.default)('mock:device');
// Technically, it is infinite.
const PRE_KEY_ITERATOR_COUNT = 100;
class Device {
aci;
deviceId;
address;
// If `true` - the device was provisioned and should receive messages over
// the websocket.
isProvisioned;
capabilities;
backupLevel = zkgroup_1.BackupLevel.Paid;
accessKey;
profileKeyCommitment;
profileName;
keys = new Map();
privPni;
privNumber;
privPniAddress;
registrationId;
pniRegistrationId;
constructor(options) {
this.aci = options.aci;
this.deviceId = options.deviceId;
this.registrationId = options.registrationId;
this.privPni = options.pni;
this.privNumber = options.number;
this.pniRegistrationId = options.pniRegistrationId;
this.isProvisioned = options.isProvisioned;
this.address = libsignal_client_1.ProtocolAddress.new(this.aci, this.deviceId);
this.privPniAddress = libsignal_client_1.ProtocolAddress.new(this.pni, this.deviceId);
this.capabilities = {
deleteSync: true,
versionedExpirationTimer: true,
ssre2: true,
usernameChangeSyncMessage: true,
};
}
get debugId() {
return `${this.aci}.${this.deviceId}`;
}
getRegistrationId(serviceIdKind) {
switch (serviceIdKind) {
case types_1.ServiceIdKind.ACI:
return this.registrationId;
case types_1.ServiceIdKind.PNI:
return this.pniRegistrationId;
}
}
get aciBinary() {
return libsignal_client_1.Aci.parseFromServiceIdString(this.aci).getServiceIdBinary();
}
get pni() {
return this.privPni;
}
get pniBinary() {
return libsignal_client_1.Pni.parseFromServiceIdString(this.pni).getServiceIdBinary();
}
get aciRawUuid() {
return libsignal_client_1.Aci.parseFromServiceIdString(this.aci).getRawUuidBytes();
}
get pniRawUuid() {
return libsignal_client_1.Pni.parseFromServiceIdString(this.pni).getRawUuidBytes();
}
get number() {
return this.privNumber;
}
get pniAddress() {
return this.privPniAddress;
}
async changeNumber({ number, pni, pniRegistrationId, }) {
this.privNumber = number;
this.privPni = pni;
this.pniRegistrationId = pniRegistrationId;
this.privPniAddress = libsignal_client_1.ProtocolAddress.new(this.pni, this.deviceId);
}
async setKeys(serviceIdKind, keys) {
debug('setting %s keys for %s', serviceIdKind, this.debugId);
const existingKeys = this.keys.get(serviceIdKind);
const { signedPreKey = existingKeys?.signedPreKey, lastResortKey = existingKeys?.lastResortKey, } = keys;
if (!signedPreKey) {
throw new Error('setKeys: Missing signedPreKey');
}
if (!lastResortKey) {
throw new Error('setKeys: Missing lastResortKey');
}
this.keys.set(serviceIdKind, {
identityKey: keys.identityKey,
signedPreKey,
preKeys: keys.preKeys?.slice() ?? [],
kyberPreKeys: keys.kyberPreKeys?.slice() ?? [],
lastResortKey,
preKeyIterator: keys.preKeyIterator,
kyberPreKeyIterator: keys.kyberPreKeyIterator,
});
}
async getIdentityKey(serviceIdKind = types_1.ServiceIdKind.ACI) {
const keys = this.keys.get(serviceIdKind);
if (!keys) {
throw new Error('No keys available for device');
}
return keys.identityKey;
}
async popSingleUseKey(serviceIdKind = types_1.ServiceIdKind.ACI) {
const keys = this.keys.get(serviceIdKind);
if (!keys) {
throw new Error('No keys available for device');
}
debug('popping single use key for %s', this.debugId);
let preKey;
if (keys.preKeyIterator) {
const { value } = await keys.preKeyIterator.next();
preKey = value;
}
preKey ??= keys.preKeys.shift();
let pqPreKey;
if (keys.kyberPreKeyIterator) {
const { value } = await keys.kyberPreKeyIterator.next();
pqPreKey = value;
}
pqPreKey ??= keys.kyberPreKeys.shift();
pqPreKey ??= keys.lastResortKey;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, @typescript-eslint/strict-boolean-expressions
if (!pqPreKey) {
throw new Error('popSingleUseKey: Missing pqPreKey; checked iterator/array/lastResort');
}
return {
identityKey: keys.identityKey,
signedPreKey: keys.signedPreKey,
preKey,
pqPreKey,
};
}
async getPreKeyCount(serviceIdKind = types_1.ServiceIdKind.ACI) {
const keys = this.keys.get(serviceIdKind);
if (!keys) {
throw new Error('No keys available for device');
}
if (keys.preKeyIterator) {
return PRE_KEY_ITERATOR_COUNT;
}
return keys.preKeys.length;
}
async getKyberPreKeyCount(serviceIdKind = types_1.ServiceIdKind.ACI) {
const keys = this.keys.get(serviceIdKind);
if (!keys) {
throw new Error('No keys available for device');
}
if (keys.kyberPreKeyIterator) {
return PRE_KEY_ITERATOR_COUNT;
}
return keys.kyberPreKeys.length;
}
getServiceIdByKind(serviceIdKind) {
switch (serviceIdKind) {
case types_1.ServiceIdKind.ACI:
return this.aci;
case types_1.ServiceIdKind.PNI:
return this.pni;
}
}
getServiceIdBinaryByKind(serviceIdKind) {
switch (serviceIdKind) {
case types_1.ServiceIdKind.ACI:
return this.aciBinary;
case types_1.ServiceIdKind.PNI:
return this.pniBinary;
}
}
getServiceIdKind(serviceId) {
if (serviceId === this.aci) {
return types_1.ServiceIdKind.ACI;
}
if (serviceId === this.pni) {
return types_1.ServiceIdKind.PNI;
}
throw new Error(`Unknown serviceId: ${serviceId}`);
}
getServiceIdBinaryKind(serviceIdBinary) {
if ((0, node_crypto_1.timingSafeEqual)(serviceIdBinary, this.aciBinary)) {
return types_1.ServiceIdKind.ACI;
}
if ((0, node_crypto_1.timingSafeEqual)(serviceIdBinary, this.pniBinary)) {
return types_1.ServiceIdKind.PNI;
}
throw new Error('Unknown serviceId');
}
getAddressByKind(serviceIdKind) {
switch (serviceIdKind) {
case types_1.ServiceIdKind.ACI:
return this.address;
case types_1.ServiceIdKind.PNI:
return this.pniAddress;
}
}
}
exports.Device = Device;