Introduce Service Id Types
Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
parent
414c0a58d3
commit
366b875fd2
269 changed files with 5832 additions and 5550 deletions
|
@ -34,14 +34,12 @@ import {
|
|||
HTTPError,
|
||||
} from './Errors';
|
||||
import type { CallbackResultType, CustomError } from './Types.d';
|
||||
import { isValidNumber } from '../types/PhoneNumber';
|
||||
import { Address } from '../types/Address';
|
||||
import * as Errors from '../types/errors';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import { UUID, isValidUuid } from '../types/UUID';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import { Sessions, IdentityKeys } from '../LibSignalStores';
|
||||
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
|
||||
import { getKeysForIdentifier } from './getKeysForIdentifier';
|
||||
import { getKeysForServiceId } from './getKeysForServiceId';
|
||||
import { SignalService as Proto } from '../protobuf';
|
||||
import * as log from '../logging/log';
|
||||
|
||||
|
@ -51,7 +49,7 @@ export const enum SenderCertificateMode {
|
|||
}
|
||||
|
||||
export type SendLogCallbackType = (options: {
|
||||
identifier: string;
|
||||
serviceId: ServiceIdString;
|
||||
deviceIds: Array<number>;
|
||||
}) => Promise<void>;
|
||||
|
||||
|
@ -109,7 +107,7 @@ export default class OutgoingMessage {
|
|||
|
||||
timestamp: number;
|
||||
|
||||
identifiers: ReadonlyArray<string>;
|
||||
serviceIds: ReadonlyArray<ServiceIdString>;
|
||||
|
||||
message: Proto.Content | PlaintextContent;
|
||||
|
||||
|
@ -117,15 +115,15 @@ export default class OutgoingMessage {
|
|||
|
||||
plaintext?: Uint8Array;
|
||||
|
||||
identifiersCompleted: number;
|
||||
serviceIdsCompleted: number;
|
||||
|
||||
errors: Array<CustomError>;
|
||||
|
||||
successfulIdentifiers: Array<string>;
|
||||
successfulServiceIds: Array<ServiceIdString>;
|
||||
|
||||
failoverIdentifiers: Array<string>;
|
||||
failoverServiceIds: Array<ServiceIdString>;
|
||||
|
||||
unidentifiedDeliveries: Array<string>;
|
||||
unidentifiedDeliveries: Array<ServiceIdString>;
|
||||
|
||||
sendMetadata?: SendMetadataType;
|
||||
|
||||
|
@ -147,7 +145,7 @@ export default class OutgoingMessage {
|
|||
callback,
|
||||
contentHint,
|
||||
groupId,
|
||||
identifiers,
|
||||
serviceIds,
|
||||
message,
|
||||
options,
|
||||
sendLogCallback,
|
||||
|
@ -159,7 +157,7 @@ export default class OutgoingMessage {
|
|||
callback: (result: CallbackResultType) => void;
|
||||
contentHint: number;
|
||||
groupId: string | undefined;
|
||||
identifiers: ReadonlyArray<string>;
|
||||
serviceIds: ReadonlyArray<ServiceIdString>;
|
||||
message: Proto.Content | Proto.DataMessage | PlaintextContent;
|
||||
options?: OutgoingMessageOptionsType;
|
||||
sendLogCallback?: SendLogCallbackType;
|
||||
|
@ -178,17 +176,17 @@ export default class OutgoingMessage {
|
|||
|
||||
this.server = server;
|
||||
this.timestamp = timestamp;
|
||||
this.identifiers = identifiers;
|
||||
this.serviceIds = serviceIds;
|
||||
this.contentHint = contentHint;
|
||||
this.groupId = groupId;
|
||||
this.callback = callback;
|
||||
this.story = story;
|
||||
this.urgent = urgent;
|
||||
|
||||
this.identifiersCompleted = 0;
|
||||
this.serviceIdsCompleted = 0;
|
||||
this.errors = [];
|
||||
this.successfulIdentifiers = [];
|
||||
this.failoverIdentifiers = [];
|
||||
this.successfulServiceIds = [];
|
||||
this.failoverServiceIds = [];
|
||||
this.unidentifiedDeliveries = [];
|
||||
this.recipients = {};
|
||||
this.sendLogCallback = sendLogCallback;
|
||||
|
@ -198,8 +196,8 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
numberCompleted(): void {
|
||||
this.identifiersCompleted += 1;
|
||||
if (this.identifiersCompleted >= this.identifiers.length) {
|
||||
this.serviceIdsCompleted += 1;
|
||||
if (this.serviceIdsCompleted >= this.serviceIds.length) {
|
||||
const proto = this.message;
|
||||
const contentProto = this.getContentProtoBytes();
|
||||
const { timestamp, contentHint, recipients, urgent } = this;
|
||||
|
@ -221,8 +219,8 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
this.callback({
|
||||
successfulIdentifiers: this.successfulIdentifiers,
|
||||
failoverIdentifiers: this.failoverIdentifiers,
|
||||
successfulServiceIds: this.successfulServiceIds,
|
||||
failoverServiceIds: this.failoverServiceIds,
|
||||
errors: this.errors,
|
||||
unidentifiedDeliveries: this.unidentifiedDeliveries,
|
||||
|
||||
|
@ -239,7 +237,7 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
registerError(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
reason: string,
|
||||
providedError?: Error
|
||||
): void {
|
||||
|
@ -247,9 +245,9 @@ export default class OutgoingMessage {
|
|||
|
||||
if (!error || (error instanceof HTTPError && error.code !== 404)) {
|
||||
if (error && error.code === 428) {
|
||||
error = new SendMessageChallengeError(identifier, error);
|
||||
error = new SendMessageChallengeError(serviceId, error);
|
||||
} else {
|
||||
error = new OutgoingMessageError(identifier, null, null, error);
|
||||
error = new OutgoingMessageError(serviceId, null, null, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,51 +258,51 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
reloadDevicesAndSend(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
recurse?: boolean
|
||||
): () => Promise<void> {
|
||||
return async () => {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||
ourUuid,
|
||||
identifier,
|
||||
ourServiceId: ourAci,
|
||||
serviceId,
|
||||
});
|
||||
if (deviceIds.length === 0) {
|
||||
this.registerError(
|
||||
identifier,
|
||||
serviceId,
|
||||
'reloadDevicesAndSend: Got empty device list when loading device keys',
|
||||
undefined
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return this.doSendMessage(identifier, deviceIds, recurse);
|
||||
return this.doSendMessage(serviceId, deviceIds, recurse);
|
||||
};
|
||||
}
|
||||
|
||||
async getKeysForIdentifier(
|
||||
identifier: string,
|
||||
async getKeysForServiceId(
|
||||
serviceId: ServiceIdString,
|
||||
updateDevices?: Array<number>
|
||||
): Promise<void | Array<void | null>> {
|
||||
): Promise<void> {
|
||||
const { sendMetadata } = this;
|
||||
const info =
|
||||
sendMetadata && sendMetadata[identifier]
|
||||
? sendMetadata[identifier]
|
||||
sendMetadata && sendMetadata[serviceId]
|
||||
? sendMetadata[serviceId]
|
||||
: { accessKey: undefined };
|
||||
const { accessKey } = info;
|
||||
|
||||
const { accessKeyFailed } = await getKeysForIdentifier(
|
||||
identifier,
|
||||
const { accessKeyFailed } = await getKeysForServiceId(
|
||||
serviceId,
|
||||
this.server,
|
||||
updateDevices,
|
||||
accessKey
|
||||
);
|
||||
if (accessKeyFailed && !this.failoverIdentifiers.includes(identifier)) {
|
||||
this.failoverIdentifiers.push(identifier);
|
||||
if (accessKeyFailed && !this.failoverServiceIds.includes(serviceId)) {
|
||||
this.failoverServiceIds.push(serviceId);
|
||||
}
|
||||
}
|
||||
|
||||
async transmitMessage(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
jsonData: ReadonlyArray<MessageType>,
|
||||
timestamp: number,
|
||||
{ accessKey }: { accessKey?: string } = {}
|
||||
|
@ -312,19 +310,14 @@ export default class OutgoingMessage {
|
|||
let promise;
|
||||
|
||||
if (accessKey) {
|
||||
promise = this.server.sendMessagesUnauth(
|
||||
identifier,
|
||||
jsonData,
|
||||
timestamp,
|
||||
{
|
||||
accessKey,
|
||||
online: this.online,
|
||||
story: this.story,
|
||||
urgent: this.urgent,
|
||||
}
|
||||
);
|
||||
promise = this.server.sendMessagesUnauth(serviceId, jsonData, timestamp, {
|
||||
accessKey,
|
||||
online: this.online,
|
||||
story: this.story,
|
||||
urgent: this.urgent,
|
||||
});
|
||||
} else {
|
||||
promise = this.server.sendMessages(identifier, jsonData, timestamp, {
|
||||
promise = this.server.sendMessages(serviceId, jsonData, timestamp, {
|
||||
online: this.online,
|
||||
story: this.story,
|
||||
urgent: this.urgent,
|
||||
|
@ -338,12 +331,12 @@ export default class OutgoingMessage {
|
|||
// 428 should throw SendMessageChallengeError
|
||||
// all other network errors can be retried later.
|
||||
if (e.code === 404) {
|
||||
throw new UnregisteredUserError(identifier, e);
|
||||
throw new UnregisteredUserError(serviceId, e);
|
||||
}
|
||||
if (e.code === 428) {
|
||||
throw new SendMessageChallengeError(identifier, e);
|
||||
throw new SendMessageChallengeError(serviceId, e);
|
||||
}
|
||||
throw new SendMessageNetworkError(identifier, jsonData, e);
|
||||
throw new SendMessageNetworkError(serviceId, jsonData, e);
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
|
@ -394,12 +387,12 @@ export default class OutgoingMessage {
|
|||
}
|
||||
|
||||
async doSendMessage(
|
||||
identifier: string,
|
||||
serviceId: ServiceIdString,
|
||||
deviceIds: Array<number>,
|
||||
recurse?: boolean
|
||||
): Promise<void> {
|
||||
const { sendMetadata } = this;
|
||||
const { accessKey, senderCertificate } = sendMetadata?.[identifier] || {};
|
||||
const { accessKey, senderCertificate } = sendMetadata?.[serviceId] || {};
|
||||
|
||||
if (accessKey && !senderCertificate) {
|
||||
log.warn(
|
||||
|
@ -411,12 +404,9 @@ export default class OutgoingMessage {
|
|||
|
||||
// We don't send to ourselves unless sealedSender is enabled
|
||||
const ourNumber = window.textsecure.storage.user.getNumber();
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const ourDeviceId = window.textsecure.storage.user.getDeviceId();
|
||||
if (
|
||||
(identifier === ourNumber || identifier === ourUuid.toString()) &&
|
||||
!sealedSender
|
||||
) {
|
||||
if ((serviceId === ourNumber || serviceId === ourAci) && !sealedSender) {
|
||||
deviceIds = reject(
|
||||
deviceIds,
|
||||
deviceId =>
|
||||
|
@ -427,15 +417,14 @@ export default class OutgoingMessage {
|
|||
);
|
||||
}
|
||||
|
||||
const sessionStore = new Sessions({ ourUuid });
|
||||
const identityKeyStore = new IdentityKeys({ ourUuid });
|
||||
const sessionStore = new Sessions({ ourServiceId: ourAci });
|
||||
const identityKeyStore = new IdentityKeys({ ourServiceId: ourAci });
|
||||
|
||||
return Promise.all(
|
||||
deviceIds.map(async destinationDeviceId => {
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
const address = new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(theirUuid, destinationDeviceId)
|
||||
ourAci,
|
||||
new Address(serviceId, destinationDeviceId)
|
||||
);
|
||||
|
||||
return window.textsecure.storage.protocol.enqueueSessionJob<MessageType>(
|
||||
|
@ -443,7 +432,7 @@ export default class OutgoingMessage {
|
|||
`doSendMessage(${address.toString()})`,
|
||||
async () => {
|
||||
const protocolAddress = ProtocolAddress.new(
|
||||
theirUuid.toString(),
|
||||
serviceId,
|
||||
destinationDeviceId
|
||||
);
|
||||
|
||||
|
@ -517,21 +506,21 @@ export default class OutgoingMessage {
|
|||
)
|
||||
.then(async (jsonData: Array<MessageType>) => {
|
||||
if (sealedSender) {
|
||||
return this.transmitMessage(identifier, jsonData, this.timestamp, {
|
||||
return this.transmitMessage(serviceId, jsonData, this.timestamp, {
|
||||
accessKey,
|
||||
}).then(
|
||||
() => {
|
||||
this.recipients[identifier] = deviceIds;
|
||||
this.unidentifiedDeliveries.push(identifier);
|
||||
this.successfulIdentifiers.push(identifier);
|
||||
this.recipients[serviceId] = deviceIds;
|
||||
this.unidentifiedDeliveries.push(serviceId);
|
||||
this.successfulServiceIds.push(serviceId);
|
||||
this.numberCompleted();
|
||||
|
||||
if (this.sendLogCallback) {
|
||||
void this.sendLogCallback({
|
||||
identifier,
|
||||
serviceId,
|
||||
deviceIds,
|
||||
});
|
||||
} else if (this.successfulIdentifiers.length > 1) {
|
||||
} else if (this.successfulServiceIds.length > 1) {
|
||||
log.warn(
|
||||
`OutgoingMessage.doSendMessage: no sendLogCallback provided for message ${this.timestamp}, but multiple recipients`
|
||||
);
|
||||
|
@ -543,18 +532,18 @@ export default class OutgoingMessage {
|
|||
(error.code === 401 || error.code === 403)
|
||||
) {
|
||||
log.warn(
|
||||
`OutgoingMessage.doSendMessage: Failing over to unsealed send for identifier ${identifier}`
|
||||
`OutgoingMessage.doSendMessage: Failing over to unsealed send for serviceId ${serviceId}`
|
||||
);
|
||||
if (this.failoverIdentifiers.indexOf(identifier) === -1) {
|
||||
this.failoverIdentifiers.push(identifier);
|
||||
if (this.failoverServiceIds.indexOf(serviceId) === -1) {
|
||||
this.failoverServiceIds.push(serviceId);
|
||||
}
|
||||
|
||||
// This ensures that we don't hit this codepath the next time through
|
||||
if (sendMetadata) {
|
||||
delete sendMetadata[identifier];
|
||||
delete sendMetadata[serviceId];
|
||||
}
|
||||
|
||||
return this.doSendMessage(identifier, deviceIds, recurse);
|
||||
return this.doSendMessage(serviceId, deviceIds, recurse);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -562,18 +551,18 @@ export default class OutgoingMessage {
|
|||
);
|
||||
}
|
||||
|
||||
return this.transmitMessage(identifier, jsonData, this.timestamp).then(
|
||||
return this.transmitMessage(serviceId, jsonData, this.timestamp).then(
|
||||
() => {
|
||||
this.successfulIdentifiers.push(identifier);
|
||||
this.recipients[identifier] = deviceIds;
|
||||
this.successfulServiceIds.push(serviceId);
|
||||
this.recipients[serviceId] = deviceIds;
|
||||
this.numberCompleted();
|
||||
|
||||
if (this.sendLogCallback) {
|
||||
void this.sendLogCallback({
|
||||
identifier,
|
||||
serviceId,
|
||||
deviceIds,
|
||||
});
|
||||
} else if (this.successfulIdentifiers.length > 1) {
|
||||
} else if (this.successfulServiceIds.length > 1) {
|
||||
log.warn(
|
||||
`OutgoingMessage.doSendMessage: no sendLogCallback provided for message ${this.timestamp}, but multiple recipients`
|
||||
);
|
||||
|
@ -588,7 +577,7 @@ export default class OutgoingMessage {
|
|||
) {
|
||||
if (!recurse) {
|
||||
this.registerError(
|
||||
identifier,
|
||||
serviceId,
|
||||
'Hit retry limit attempting to reload device list',
|
||||
error
|
||||
);
|
||||
|
@ -602,18 +591,15 @@ export default class OutgoingMessage {
|
|||
};
|
||||
let p: Promise<any> = Promise.resolve();
|
||||
if (error.code === 409) {
|
||||
p = this.removeDeviceIdsForIdentifier(
|
||||
identifier,
|
||||
p = this.removeDeviceIdsForServiceId(
|
||||
serviceId,
|
||||
response.extraDevices || []
|
||||
);
|
||||
} else {
|
||||
p = Promise.all(
|
||||
(response.staleDevices || []).map(async (deviceId: number) => {
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
new QualifiedAddress(
|
||||
ourUuid,
|
||||
new Address(UUID.checkedLookup(identifier), deviceId)
|
||||
)
|
||||
new QualifiedAddress(ourAci, new Address(serviceId, deviceId))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
@ -624,10 +610,10 @@ export default class OutgoingMessage {
|
|||
error.code === 410
|
||||
? response.staleDevices
|
||||
: response.missingDevices;
|
||||
return this.getKeysForIdentifier(identifier, resetDevices).then(
|
||||
return this.getKeysForServiceId(serviceId, resetDevices).then(
|
||||
// We continue to retry as long as the error code was 409; the assumption is
|
||||
// that we'll request new device info and the next request will succeed.
|
||||
this.reloadDevicesAndSend(identifier, error.code === 409)
|
||||
this.reloadDevicesAndSend(serviceId, error.code === 409)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -637,32 +623,30 @@ export default class OutgoingMessage {
|
|||
error instanceof LibSignalErrorBase &&
|
||||
error.code === ErrorCode.UntrustedIdentity
|
||||
) {
|
||||
newError = new OutgoingIdentityKeyError(identifier, error);
|
||||
newError = new OutgoingIdentityKeyError(serviceId, error);
|
||||
log.error(
|
||||
'Got "key changed" error from encrypt - no identityKey for application layer',
|
||||
identifier,
|
||||
serviceId,
|
||||
deviceIds
|
||||
);
|
||||
|
||||
log.info('closing all sessions for', identifier);
|
||||
window.textsecure.storage.protocol
|
||||
.archiveAllSessions(UUID.checkedLookup(identifier))
|
||||
.then(
|
||||
() => {
|
||||
throw error;
|
||||
},
|
||||
innerError => {
|
||||
log.error(
|
||||
'doSendMessage: Error closing sessions: ' +
|
||||
`${Errors.toLogFormat(innerError)}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
log.info('closing all sessions for', serviceId);
|
||||
window.textsecure.storage.protocol.archiveAllSessions(serviceId).then(
|
||||
() => {
|
||||
throw error;
|
||||
},
|
||||
innerError => {
|
||||
log.error(
|
||||
'doSendMessage: Error closing sessions: ' +
|
||||
`${Errors.toLogFormat(innerError)}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.registerError(
|
||||
identifier,
|
||||
serviceId,
|
||||
'Failed to create or send message',
|
||||
newError
|
||||
);
|
||||
|
@ -671,87 +655,43 @@ export default class OutgoingMessage {
|
|||
});
|
||||
}
|
||||
|
||||
async removeDeviceIdsForIdentifier(
|
||||
identifier: string,
|
||||
async removeDeviceIdsForServiceId(
|
||||
serviceId: ServiceIdString,
|
||||
deviceIdsToRemove: Array<number>
|
||||
): Promise<void> {
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const theirUuid = UUID.checkedLookup(identifier);
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
|
||||
await Promise.all(
|
||||
deviceIdsToRemove.map(async deviceId => {
|
||||
await window.textsecure.storage.protocol.archiveSession(
|
||||
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
|
||||
new QualifiedAddress(ourAci, new Address(serviceId, deviceId))
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async sendToIdentifier(providedIdentifier: string): Promise<void> {
|
||||
let identifier = providedIdentifier;
|
||||
async sendToServiceId(serviceId: ServiceIdString): Promise<void> {
|
||||
try {
|
||||
if (isValidUuid(identifier)) {
|
||||
// We're good!
|
||||
} else if (isValidNumber(identifier)) {
|
||||
if (!window.textsecure.server) {
|
||||
throw new Error(
|
||||
'sendToIdentifier: window.textsecure.server is not available!'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await updateConversationsWithUuidLookup({
|
||||
conversationController: window.ConversationController,
|
||||
conversations: [
|
||||
window.ConversationController.getOrCreate(identifier, 'private'),
|
||||
],
|
||||
server: window.textsecure.server,
|
||||
});
|
||||
|
||||
const uuid =
|
||||
window.ConversationController.get(identifier)?.get('uuid');
|
||||
if (!uuid) {
|
||||
throw new UnregisteredUserError(
|
||||
identifier,
|
||||
new HTTPError('User is not registered', {
|
||||
code: -1,
|
||||
headers: {},
|
||||
})
|
||||
);
|
||||
}
|
||||
identifier = uuid;
|
||||
} catch (error) {
|
||||
log.error(
|
||||
`sendToIdentifier: Failed to fetch UUID for identifier ${identifier}`,
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`sendToIdentifier: identifier ${identifier} was neither a UUID or E164`
|
||||
);
|
||||
}
|
||||
|
||||
const ourUuid = window.textsecure.storage.user.getCheckedUuid();
|
||||
const ourAci = window.textsecure.storage.user.getCheckedAci();
|
||||
const deviceIds = await window.textsecure.storage.protocol.getDeviceIds({
|
||||
ourUuid,
|
||||
identifier,
|
||||
ourServiceId: ourAci,
|
||||
serviceId,
|
||||
});
|
||||
if (deviceIds.length === 0) {
|
||||
await this.getKeysForIdentifier(identifier);
|
||||
await this.getKeysForServiceId(serviceId);
|
||||
}
|
||||
await this.reloadDevicesAndSend(identifier, true)();
|
||||
await this.reloadDevicesAndSend(serviceId, true)();
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof LibSignalErrorBase &&
|
||||
error.code === ErrorCode.UntrustedIdentity
|
||||
) {
|
||||
const newError = new OutgoingIdentityKeyError(identifier, error);
|
||||
this.registerError(identifier, 'Untrusted identity', newError);
|
||||
const newError = new OutgoingIdentityKeyError(serviceId, error);
|
||||
this.registerError(serviceId, 'Untrusted identity', newError);
|
||||
} else {
|
||||
this.registerError(
|
||||
identifier,
|
||||
`Failed to retrieve new device keys for identifier ${identifier}`,
|
||||
serviceId,
|
||||
`Failed to retrieve new device keys for serviceId ${serviceId}`,
|
||||
error
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue