Adopt libsignal getDevices and setDeviceName APIs

Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
automated-signal
2026-08-05 17:43:01 +00:00
committed by GitHub
co-authored by trevor-signal
parent ddaba5d290
commit 9ba6b515bd
3 changed files with 35 additions and 44 deletions
+12 -8
View File
@@ -9,6 +9,7 @@ import {
BackupKey,
} from '@signalapp/libsignal-client/dist/AccountKeys.js';
import type { RegisterAccountResponse } from '@signalapp/libsignal-client/dist/net';
import { Base64 } from '@signalapp/types';
import EventTarget from './EventTarget.std.ts';
import {
@@ -295,7 +296,7 @@ export default class AccountManager extends EventTarget {
encryptDeviceName(
name: string,
identityKey: KeyPairType
): string | undefined {
): Base64 | undefined {
if (!name) {
return undefined;
}
@@ -306,17 +307,17 @@ export default class AccountManager extends EventTarget {
syntheticIv: encrypted.syntheticIv,
ciphertext: encrypted.ciphertext,
});
return Bytes.toBase64(bytes);
return Base64.fromBytes(bytes);
}
async decryptDeviceName(base64: string): Promise<string> {
async decryptDeviceName(base64: Base64): Promise<string> {
const ourAci = itemStorage.user.getCheckedAci();
const identityKey = signalProtocolStore.getIdentityKeyPair(ourAci);
if (!identityKey) {
throw new Error('decryptDeviceName: No identity key pair!');
}
const bytes = Bytes.fromBase64(base64);
const bytes = Base64.toBytes(base64);
const proto = Proto.DeviceName.decode(bytes);
strictAssert(
proto.ephemeralPublic,
@@ -341,7 +342,7 @@ export default class AccountManager extends EventTarget {
async _encryptDeviceCreatedAt(
createdAt: number,
deviceId: number
): Promise<string> {
): Promise<Base64> {
const ourAci = itemStorage.user.getCheckedAci();
const identityKey = signalProtocolStore.getIdentityKeyPair(ourAci);
const registrationId =
@@ -356,11 +357,11 @@ export default class AccountManager extends EventTarget {
identityKey.publicKey
);
return Bytes.toBase64(createdAtCiphertextBytes);
return Base64.fromBytes(createdAtCiphertextBytes);
}
async decryptDeviceCreatedAt(
createdAtCiphertextBase64: string,
createdAtCiphertextBase64: Base64,
deviceId: number
): Promise<number> {
const ourAci = itemStorage.user.getCheckedAci();
@@ -405,7 +406,10 @@ export default class AccountManager extends EventTarget {
const base64 = this.encryptDeviceName(deviceName || '', identityKeyPair);
if (base64) {
await updateDeviceName(base64);
await updateDeviceName({
deviceId: itemStorage.user.getCheckedDeviceId(),
encryptedName: Base64.toBytes(base64),
});
await itemStorage.user.setDeviceNameEncrypted();
}
}
+15 -29
View File
@@ -22,6 +22,7 @@ import {
import { AccountAttributes } from '@signalapp/libsignal-client/dist/net.js';
import type {
BackupAuth,
LinkedDevice,
ProvisioningConnection,
ProvisioningConnectionListener,
RegisterAccountResponse,
@@ -819,7 +820,6 @@ const CHAT_CALLS = {
subscriptions: 'v1/subscription',
subscriptionConfiguration: 'v1/subscription/configuration',
transferArchive: 'v1/devices/transfer_archive',
updateDeviceName: 'v1/accounts/name',
username: 'v1/accounts/username_hash',
reserveUsername: 'v1/accounts/username_hash/reserve',
confirmUsername: 'v1/accounts/username_hash/confirm',
@@ -1004,19 +1004,6 @@ export type GetAccountForUsernameOptionsType = Readonly<{
export type GetAccountForUsernameResultType = AciString | null;
const getDevicesResultZod = z.object({
devices: z.array(
z.object({
id: z.number(),
name: z.string().nullish(), // primary devices may not have a name
lastSeen: z.number().nullish(),
createdAtCiphertext: z.string(),
})
),
});
export type GetDevicesResultType = z.infer<typeof getDevicesResultZod>;
export type GetIceServersResultType = Readonly<{
relays?: ReadonlyArray<IceServerGroupType>;
}>;
@@ -3094,24 +3081,23 @@ export async function disableRegistrationLock(): Promise<void> {
});
}
export async function getDevices(): Promise<GetDevicesResultType> {
return _ajax({
host: 'chatService',
call: 'devices',
httpType: 'GET',
responseType: 'json',
zodSchema: getDevicesResultZod,
export async function getDevices(): Promise<ReadonlyArray<LinkedDevice>> {
return _retry(async () => {
const chat = await socketManager.getAuthenticatedApi();
return chat.getDevices();
});
}
export async function updateDeviceName(deviceName: string): Promise<void> {
await _ajax({
host: 'chatService',
call: 'updateDeviceName',
httpType: 'PUT',
jsonData: {
deviceName,
},
export async function updateDeviceName({
deviceId,
encryptedName,
}: {
deviceId: number;
encryptedName: Uint8Array<ArrayBuffer>;
}): Promise<void> {
await _retry(async () => {
const chat = await socketManager.getAuthenticatedApi();
await chat.setDeviceName({ deviceId, encryptedName });
});
}
+8 -7
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import PQueue from 'p-queue';
import { Base64 } from '@signalapp/types';
import type { DeviceNameChangeSyncEvent } from '../textsecure/messageReceiverEvents.std.ts';
import { getDevices } from '../textsecure/WebAPI.preload.ts';
import { MINUTE } from './durations/index.std.ts';
@@ -57,7 +58,7 @@ export async function maybeQueueDeviceInfoFetch(): Promise<void> {
}
async function fetchAndUpdateDeviceInfo() {
const { devices } = await getDevices();
const devices = await getDevices();
const localDeviceId = parseIntOrThrow(
itemStorage.user.getDeviceId(),
'fetchAndUpdateDeviceInfo: localDeviceId'
@@ -70,15 +71,16 @@ async function fetchAndUpdateDeviceInfo() {
localDeviceId
);
const newNameEncrypted = ourDevice.name;
if (!newNameEncrypted) {
if (ourDevice.encryptedName.length === 0) {
log.error('fetchAndUpdateDeviceInfo: device had empty name');
return;
}
let newName: string;
try {
newName = await accountManager.decryptDeviceName(newNameEncrypted);
newName = await accountManager.decryptDeviceName(
Base64.fromBytes(ourDevice.encryptedName)
);
} catch (e) {
const deviceNameWasEncrypted = itemStorage.user.getDeviceNameEncrypted();
log.error(
@@ -101,7 +103,7 @@ async function fetchAndUpdateDeviceInfo() {
}
async function maybeUpdateDeviceCreatedAt(
createdAtCiphertext: string,
createdAtCiphertext: Uint8Array<ArrayBuffer>,
deviceId: number
): Promise<void> {
const existingCreatedAt = itemStorage.user.getDeviceCreatedAt();
@@ -109,11 +111,10 @@ async function maybeUpdateDeviceCreatedAt(
return;
}
const createdAtEncrypted = createdAtCiphertext;
let createdAt: number | undefined;
try {
createdAt = await accountManager.decryptDeviceCreatedAt(
createdAtEncrypted,
Base64.fromBytes(createdAtCiphertext),
deviceId
);
} catch (e) {