Set Accept-Language at the connection level for chat connections

This commit is contained in:
Jordan Rose 2025-07-09 10:57:01 -07:00 committed by GitHub
parent 3063262730
commit b440aec88c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 41 additions and 54 deletions

View file

@ -120,7 +120,7 @@
"@react-aria/utils": "3.25.3", "@react-aria/utils": "3.25.3",
"@react-spring/web": "9.7.5", "@react-spring/web": "9.7.5",
"@react-types/shared": "3.27.0", "@react-types/shared": "3.27.0",
"@signalapp/libsignal-client": "0.76.0", "@signalapp/libsignal-client": "0.76.3",
"@signalapp/quill-cjs": "2.1.2", "@signalapp/quill-cjs": "2.1.2",
"@signalapp/ringrtc": "2.54.1", "@signalapp/ringrtc": "2.54.1",
"@signalapp/sqlcipher": "2.1.0", "@signalapp/sqlcipher": "2.1.0",

10
pnpm-lock.yaml generated
View file

@ -129,8 +129,8 @@ importers:
specifier: 3.27.0 specifier: 3.27.0
version: 3.27.0(react@18.3.1) version: 3.27.0(react@18.3.1)
'@signalapp/libsignal-client': '@signalapp/libsignal-client':
specifier: 0.76.0 specifier: 0.76.3
version: 0.76.0 version: 0.76.3
'@signalapp/quill-cjs': '@signalapp/quill-cjs':
specifier: 2.1.2 specifier: 2.1.2
version: 2.1.2 version: 2.1.2
@ -2767,8 +2767,8 @@ packages:
'@signalapp/libsignal-client@0.60.2': '@signalapp/libsignal-client@0.60.2':
resolution: {integrity: sha512-tU4kNP/yCwkFntb2ahXOSQJtzdy+YifAB2yv5hw0qyKSidRHLn6bYiz4Zo2tjxLDRoBLAUxCRsQramStiqNZdA==} resolution: {integrity: sha512-tU4kNP/yCwkFntb2ahXOSQJtzdy+YifAB2yv5hw0qyKSidRHLn6bYiz4Zo2tjxLDRoBLAUxCRsQramStiqNZdA==}
'@signalapp/libsignal-client@0.76.0': '@signalapp/libsignal-client@0.76.3':
resolution: {integrity: sha512-wQZFC79GAUeee8pf+aDK5Gii0HbQoCAv/oTn1Ht7d5mFq2pw/L0jRcv3j9DgVYodzCOlnanfto3apfA6eN/Whw==} resolution: {integrity: sha512-Ht8XtdsSvgiCb8ftUYE9DaLcWy0vltrj9cQ2sfy+DGUayE1k2njicNhB2RKOfQV2Wb/1Cl0WxVZP/NlXRo2+jA==}
'@signalapp/mock-server@13.1.0': '@signalapp/mock-server@13.1.0':
resolution: {integrity: sha512-CuDNLNEBMzwIs5jr7Lx9F4YFoRD62s7WgPGtm3qpaggixSQtabjMC7AKSR0xvaHcZpYZtBU5jcGK8Roguo9nuw==} resolution: {integrity: sha512-CuDNLNEBMzwIs5jr7Lx9F4YFoRD62s7WgPGtm3qpaggixSQtabjMC7AKSR0xvaHcZpYZtBU5jcGK8Roguo9nuw==}
@ -12460,7 +12460,7 @@ snapshots:
type-fest: 4.26.1 type-fest: 4.26.1
uuid: 8.3.2 uuid: 8.3.2
'@signalapp/libsignal-client@0.76.0': '@signalapp/libsignal-client@0.76.3':
dependencies: dependencies:
node-gyp-build: 4.8.4 node-gyp-build: 4.8.4
type-fest: 4.26.1 type-fest: 4.26.1

View file

@ -48,7 +48,6 @@ import { isValidTapToView } from '../util/isValidTapToView';
import { getNotificationTextForMessage } from '../util/getNotificationTextForMessage'; import { getNotificationTextForMessage } from '../util/getNotificationTextForMessage';
import { getMessageAuthorText } from '../util/getMessageAuthorText'; import { getMessageAuthorText } from '../util/getMessageAuthorText';
import { GiftBadgeStates } from '../components/conversation/Message'; import { GiftBadgeStates } from '../components/conversation/Message';
import { getUserLanguages } from '../util/userLanguages';
import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer'; import { parseBoostBadgeListFromServer } from '../badges/parseBadgesFromServer';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
import { import {
@ -761,16 +760,11 @@ export async function handleDataMessage(
typeof updatesUrl === 'string', typeof updatesUrl === 'string',
'getProfile: expected updatesUrl to be a defined string' 'getProfile: expected updatesUrl to be a defined string'
); );
const userLanguages = getUserLanguages(
window.SignalContext.getPreferredSystemLocales(),
window.SignalContext.getResolvedMessagesLocale()
);
const { messaging } = window.textsecure; const { messaging } = window.textsecure;
if (!messaging) { if (!messaging) {
throw new Error(`${idLog}: messaging is not available`); throw new Error(`${idLog}: messaging is not available`);
} }
const response = const response = await messaging.server.getSubscriptionConfiguration();
await messaging.server.getSubscriptionConfiguration(userLanguages);
const boostBadgesByLevel = parseBoostBadgeListFromServer( const boostBadgesByLevel = parseBoostBadgeListFromServer(
response, response,
updatesUrl updatesUrl

View file

@ -27,7 +27,6 @@ import {
handleProfileKeyCredential, handleProfileKeyCredential,
} from '../util/zkgroup'; } from '../util/zkgroup';
import { isMe } from '../util/whatTypeOfConversation'; import { isMe } from '../util/whatTypeOfConversation';
import { getUserLanguages } from '../util/userLanguages';
import { parseBadgesFromServer } from '../badges/parseBadgesFromServer'; import { parseBadgesFromServer } from '../badges/parseBadgesFromServer';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { drop } from '../util/drop'; import { drop } from '../util/drop';
@ -236,11 +235,6 @@ export const profileService = new ProfileService();
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
namespace ProfileFetchOptions { namespace ProfileFetchOptions {
type Base = ReadonlyDeep<{
request: {
userLanguages: ReadonlyArray<string>;
};
}>;
type WithVersioned = ReadonlyDeep<{ type WithVersioned = ReadonlyDeep<{
profileKey: string; profileKey: string;
profileCredentialRequestContext: ProfileKeyCredentialRequestContext | null; profileCredentialRequestContext: ProfileKeyCredentialRequestContext | null;
@ -275,16 +269,16 @@ namespace ProfileFetchOptions {
export type Unauth = export type Unauth =
// versioned (unauth) // versioned (unauth)
| (Base & WithVersioned & WithUnauthAccessKey) | (WithVersioned & WithUnauthAccessKey)
// unversioned (unauth) // unversioned (unauth)
| (Base & WithUnversioned & WithUnauthAccessKey) | (WithUnversioned & WithUnauthAccessKey)
| (Base & WithUnversioned & WithUnauthGroupSendToken); | (WithUnversioned & WithUnauthGroupSendToken);
export type Auth = export type Auth =
// unversioned (auth) -- Using lastProfile // unversioned (auth) -- Using lastProfile
| (Base & WithVersioned & WithAuth) | (WithVersioned & WithAuth)
// unversioned (auth) // unversioned (auth)
| (Base & WithUnversioned & WithAuth); | (WithUnversioned & WithAuth);
} }
export type ProfileFetchUnauthRequestOptions = export type ProfileFetchUnauthRequestOptions =
@ -308,11 +302,6 @@ async function buildProfileFetchOptions({
}): Promise<ProfileFetchOptions.Auth | ProfileFetchOptions.Unauth> { }): Promise<ProfileFetchOptions.Auth | ProfileFetchOptions.Unauth> {
const logId = `buildGetProfileOptions(${conversation.idForLogging()})`; const logId = `buildGetProfileOptions(${conversation.idForLogging()})`;
const userLanguages = getUserLanguages(
window.SignalContext.getPreferredSystemLocales(),
window.SignalContext.getResolvedMessagesLocale()
);
const profileKey = conversation.get('profileKey'); const profileKey = conversation.get('profileKey');
const profileKeyVersion = conversation.deriveProfileKeyVersion(); const profileKeyVersion = conversation.deriveProfileKeyVersion();
const accessKey = conversation.get('accessKey'); const accessKey = conversation.get('accessKey');
@ -330,7 +319,6 @@ async function buildProfileFetchOptions({
profileKey, profileKey,
profileCredentialRequestContext: null, profileCredentialRequestContext: null,
request: { request: {
userLanguages,
accessKey, accessKey,
groupSendToken: null, groupSendToken: null,
profileKeyVersion, profileKeyVersion,
@ -350,7 +338,6 @@ async function buildProfileFetchOptions({
profileKey, profileKey,
profileCredentialRequestContext: result.context, profileCredentialRequestContext: result.context,
request: { request: {
userLanguages,
accessKey, accessKey,
groupSendToken: null, groupSendToken: null,
profileKeyVersion, profileKeyVersion,
@ -374,7 +361,6 @@ async function buildProfileFetchOptions({
profileKey: lastProfile.profileKey, profileKey: lastProfile.profileKey,
profileCredentialRequestContext: null, profileCredentialRequestContext: null,
request: { request: {
userLanguages,
accessKey: null, accessKey: null,
groupSendToken: null, groupSendToken: null,
profileKeyVersion: lastProfile.profileKeyVersion, profileKeyVersion: lastProfile.profileKeyVersion,
@ -403,7 +389,6 @@ async function buildProfileFetchOptions({
profileKey: null, profileKey: null,
profileCredentialRequestContext: null, profileCredentialRequestContext: null,
request: { request: {
userLanguages,
accessKey: null, accessKey: null,
groupSendToken, groupSendToken,
profileKeyVersion: null, profileKeyVersion: null,
@ -418,7 +403,6 @@ async function buildProfileFetchOptions({
profileKey: null, profileKey: null,
profileCredentialRequestContext: null, profileCredentialRequestContext: null,
request: { request: {
userLanguages,
accessKey: null, accessKey: null,
groupSendToken: null, groupSendToken: null,
profileKeyVersion: null, profileKeyVersion: null,

View file

@ -55,6 +55,10 @@ import {
parseServerAlertsFromHeader, parseServerAlertsFromHeader,
type ServerAlert, type ServerAlert,
} from '../util/handleServerAlerts'; } from '../util/handleServerAlerts';
import {
formatAcceptLanguageHeader,
getUserLanguages,
} from '../util/userLanguages';
const log = createLogger('SocketManager'); const log = createLogger('SocketManager');
@ -216,6 +220,11 @@ export class SocketManager extends EventListener {
'desktop.experimentalTransport.enableAuth' 'desktop.experimentalTransport.enableAuth'
) && this.#transportOption() === TransportOption.Libsignal; ) && this.#transportOption() === TransportOption.Libsignal;
const userLanguages = getUserLanguages(
window.SignalContext.getPreferredSystemLocales(),
window.SignalContext.getResolvedMessagesLocale()
);
const process = useLibsignalTransport const process = useLibsignalTransport
? connectAuthenticatedLibsignal({ ? connectAuthenticatedLibsignal({
libsignalNet: this.libsignalNet, libsignalNet: this.libsignalNet,
@ -228,6 +237,7 @@ export class SocketManager extends EventListener {
this.emit('serverAlerts', alerts); this.emit('serverAlerts', alerts);
}, },
receiveStories: !this.#hasStoriesDisabled, receiveStories: !this.#hasStoriesDisabled,
userLanguages,
keepalive: { path: '/v1/keepalive' }, keepalive: { path: '/v1/keepalive' },
}) })
: this.#connectResource({ : this.#connectResource({
@ -243,6 +253,7 @@ export class SocketManager extends EventListener {
extraHeaders: { extraHeaders: {
Authorization: getBasicAuth({ username, password }), Authorization: getBasicAuth({ username, password }),
'X-Signal-Receive-Stories': String(!this.#hasStoriesDisabled), 'X-Signal-Receive-Stories': String(!this.#hasStoriesDisabled),
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
}, },
onUpgradeResponse: (response: IncomingMessage) => { onUpgradeResponse: (response: IncomingMessage) => {
this.#handleAuthenticatedUpgradeResponseHeaders(response.headers); this.#handleAuthenticatedUpgradeResponseHeaders(response.headers);
@ -722,12 +733,18 @@ export class SocketManager extends EventListener {
status: SocketStatus.CONNECTING, status: SocketStatus.CONNECTING,
}); });
const userLanguages = getUserLanguages(
window.SignalContext.getPreferredSystemLocales(),
window.SignalContext.getResolvedMessagesLocale()
);
let process: AbortableProcess<IWebSocketResource>; let process: AbortableProcess<IWebSocketResource>;
if (transportOption === TransportOption.Libsignal) { if (transportOption === TransportOption.Libsignal) {
process = connectUnauthenticatedLibsignal({ process = connectUnauthenticatedLibsignal({
libsignalNet: this.libsignalNet, libsignalNet: this.libsignalNet,
name: UNAUTHENTICATED_CHANNEL_NAME, name: UNAUTHENTICATED_CHANNEL_NAME,
userLanguages,
keepalive: { path: '/v1/keepalive' }, keepalive: { path: '/v1/keepalive' },
}); });
} else { } else {
@ -740,6 +757,9 @@ export class SocketManager extends EventListener {
keepalive: { path: '/v1/keepalive' }, keepalive: { path: '/v1/keepalive' },
transportOption, transportOption,
}, },
extraHeaders: {
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
},
}); });
} }

View file

@ -29,7 +29,6 @@ import type { ExplodePromiseResultType } from '../util/explodePromise';
import { explodePromise } from '../util/explodePromise'; import { explodePromise } from '../util/explodePromise';
import { getUserAgent } from '../util/getUserAgent'; import { getUserAgent } from '../util/getUserAgent';
import { getTimeoutStream } from '../util/getStreamWithTimeout'; import { getTimeoutStream } from '../util/getStreamWithTimeout';
import { formatAcceptLanguageHeader } from '../util/userLanguages';
import { toWebSafeBase64, fromWebSafeBase64 } from '../util/webSafeBase64'; import { toWebSafeBase64, fromWebSafeBase64 } from '../util/webSafeBase64';
import { getBasicAuth } from '../util/getBasicAuth'; import { getBasicAuth } from '../util/getBasicAuth';
import { createHTTPSAgent } from '../util/createHTTPSAgent'; import { createHTTPSAgent } from '../util/createHTTPSAgent';
@ -1630,9 +1629,7 @@ export type WebAPIType = {
options: ProfileFetchUnauthRequestOptions options: ProfileFetchUnauthRequestOptions
) => Promise<ProfileType>; ) => Promise<ProfileType>;
getBadgeImageFile: (imageUrl: string) => Promise<Uint8Array>; getBadgeImageFile: (imageUrl: string) => Promise<Uint8Array>;
getSubscriptionConfiguration: ( getSubscriptionConfiguration: () => Promise<unknown>;
userLanguages: ReadonlyArray<string>
) => Promise<unknown>;
getSubscription: ( getSubscription: (
subscriberId: Uint8Array subscriberId: Uint8Array
) => Promise<SubscriptionResponseType>; ) => Promise<SubscriptionResponseType>;
@ -2743,17 +2740,13 @@ export function initialize({
serviceId: ServiceIdString, serviceId: ServiceIdString,
options: ProfileFetchAuthRequestOptions options: ProfileFetchAuthRequestOptions
) { ) {
const { profileKeyVersion, profileKeyCredentialRequest, userLanguages } = const { profileKeyVersion, profileKeyCredentialRequest } = options;
options;
return (await _ajax({ return (await _ajax({
host: 'chatService', host: 'chatService',
call: 'profile', call: 'profile',
httpType: 'GET', httpType: 'GET',
urlParameters: getProfileUrl(serviceId, options), urlParameters: getProfileUrl(serviceId, options),
headers: {
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
},
responseType: 'json', responseType: 'json',
redactUrl: _createRedactor( redactUrl: _createRedactor(
serviceId, serviceId,
@ -2855,7 +2848,6 @@ export function initialize({
groupSendToken, groupSendToken,
profileKeyVersion, profileKeyVersion,
profileKeyCredentialRequest, profileKeyCredentialRequest,
userLanguages,
} = options; } = options;
if (profileKeyVersion != null || profileKeyCredentialRequest != null) { if (profileKeyVersion != null || profileKeyCredentialRequest != null) {
@ -2872,9 +2864,6 @@ export function initialize({
call: 'profile', call: 'profile',
httpType: 'GET', httpType: 'GET',
urlParameters: getProfileUrl(serviceId, options), urlParameters: getProfileUrl(serviceId, options),
headers: {
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
},
responseType: 'json', responseType: 'json',
unauthenticated: true, unauthenticated: true,
accessKey: accessKey ?? undefined, accessKey: accessKey ?? undefined,
@ -2939,16 +2928,11 @@ export function initialize({
); );
} }
async function getSubscriptionConfiguration( async function getSubscriptionConfiguration(): Promise<unknown> {
userLanguages: ReadonlyArray<string>
): Promise<unknown> {
return _ajax({ return _ajax({
host: 'chatService', host: 'chatService',
call: 'subscriptionConfiguration', call: 'subscriptionConfiguration',
httpType: 'GET', httpType: 'GET',
headers: {
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
},
responseType: 'json', responseType: 'json',
// TODO DESKTOP-8719 // TODO DESKTOP-8719
zodSchema: z.unknown(), zodSchema: z.unknown(),

View file

@ -315,10 +315,12 @@ const UNEXPECTED_DISCONNECT_CODE = 3001;
export function connectUnauthenticatedLibsignal({ export function connectUnauthenticatedLibsignal({
libsignalNet, libsignalNet,
name, name,
userLanguages,
keepalive, keepalive,
}: { }: {
libsignalNet: Net.Net; libsignalNet: Net.Net;
name: string; name: string;
userLanguages: ReadonlyArray<string>;
keepalive: KeepAliveOptionsType; keepalive: KeepAliveOptionsType;
}): AbortableProcess<LibsignalWebSocketResource> { }): AbortableProcess<LibsignalWebSocketResource> {
const logId = `LibsignalWebSocketResource(${name})`; const logId = `LibsignalWebSocketResource(${name})`;
@ -338,6 +340,7 @@ export function connectUnauthenticatedLibsignal({
abortSignal => abortSignal =>
libsignalNet.connectUnauthenticatedChat(listener, { libsignalNet.connectUnauthenticatedChat(listener, {
abortSignal, abortSignal,
languages: [...userLanguages],
}), }),
listener, listener,
logId, logId,
@ -351,6 +354,7 @@ export function connectAuthenticatedLibsignal({
credentials, credentials,
handler, handler,
receiveStories, receiveStories,
userLanguages,
keepalive, keepalive,
onReceivedAlerts, onReceivedAlerts,
}: { }: {
@ -360,6 +364,7 @@ export function connectAuthenticatedLibsignal({
handler: (request: IncomingWebSocketRequest) => void; handler: (request: IncomingWebSocketRequest) => void;
onReceivedAlerts: (alerts: Array<ServerAlert>) => void; onReceivedAlerts: (alerts: Array<ServerAlert>) => void;
receiveStories: boolean; receiveStories: boolean;
userLanguages: ReadonlyArray<string>;
keepalive: KeepAliveOptionsType; keepalive: KeepAliveOptionsType;
}): AbortableProcess<LibsignalWebSocketResource> { }): AbortableProcess<LibsignalWebSocketResource> {
const logId = `LibsignalWebSocketResource(${name})`; const logId = `LibsignalWebSocketResource(${name})`;
@ -411,7 +416,7 @@ export function connectAuthenticatedLibsignal({
credentials.password, credentials.password,
receiveStories, receiveStories,
listener, listener,
{ abortSignal } { abortSignal, languages: [...userLanguages] }
), ),
listener, listener,
logId, logId,