mattermost-mobile/app/managers/network_manager.ts
Rahim Rahman 72b97f60f5
revert: floating banner & low connectivity (#9254)
* Revert "feat(MM-65625): low connectivity feature toggle in advance settings (#9191)"

This reverts commit c872e2e2b1.

* Revert "feat(MM-65145): Network connectivity/performance observer (#9173)"

This reverts commit 597e03d7d8.

* Revert "feat(MM-65625): floating banner (#9162)"

This reverts commit 5887c3ab46.

* Revert "feat(MM-65625): enhance navigation overlay management (#9165)"

This reverts commit f1554f0341.

* revert advanced.test.tsx changes
2025-11-04 07:15:51 -07:00

182 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import Emm from '@mattermost/react-native-emm';
import {
type APIClientErrorEvent,
type APIClientErrorEventHandler,
getOrCreateAPIClient,
RetryTypes,
type APIClientConfiguration,
} from '@mattermost/react-native-network-client';
import {nativeApplicationVersion, nativeBuildVersion} from 'expo-application';
import {modelName, osName, osVersion} from 'expo-device';
import {defineMessages} from 'react-intl';
import {Alert, DeviceEventEmitter} from 'react-native';
import urlParse from 'url-parse';
import LocalConfig from '@assets/config.json';
import {Client} from '@client/rest';
import * as ClientConstants from '@client/rest/constants';
import ClientError from '@client/rest/error';
import {CERTIFICATE_ERRORS} from '@constants/network';
import ManagedApp from '@init/managed_app';
import {toMilliseconds} from '@utils/datetime';
import {getIntlShape} from '@utils/general';
import {logDebug, logError} from '@utils/log';
import {getCSRFFromCookie} from '@utils/security';
const CLIENT_CERTIFICATE_IMPORT_ERROR_CODES = [-103, -104, -105, -108];
const CLIENT_CERTIFICATE_MISSING_ERROR_CODE = -200;
const SERVER_CERTIFICATE_INVALID = -299;
const SERVER_TRUST_EVALUATION_FAILED = -298;
let showingServerTrustAlert = false;
const messages = defineMessages({
invalidSslTitle: {
id: 'server.invalid.certificate.title',
defaultMessage: 'Invalid SSL certificate',
},
invalidSslDescription: {
id: 'server.invalid.certificate.description',
defaultMessage: 'The certificate for this server is invalid.\nYou might be connecting to a server that is pretending to be “{hostname}” which could put your confidential information at risk.',
},
invalidPinningTitle: {
id: 'server.invalid.pinning.title',
defaultMessage: 'Invalid pinned SSL certificate',
},
});
class NetworkManagerSingleton {
private clients: Record<string, Client> = {};
private intl = getIntlShape();
private DEFAULT_CONFIG: APIClientConfiguration = {
headers: {
'X-Requested-With': 'XMLHttpRequest',
...LocalConfig.CustomRequestHeaders,
},
sessionConfiguration: {
allowsCellularAccess: true,
waitsForConnectivity: false,
httpMaximumConnectionsPerHost: 100,
cancelRequestsOnUnauthorized: true,
collectMetrics: false,
},
retryPolicyConfiguration: {
type: RetryTypes.EXPONENTIAL_RETRY,
retryLimit: 3,
exponentialBackoffBase: 2,
exponentialBackoffScale: 0.5,
},
requestAdapterConfiguration: {
bearerAuthTokenResponseHeader: 'token',
},
};
public init = async (serverCredentials: ServerCredential[]) => {
for await (const {serverUrl, token, preauthSecret} of serverCredentials) {
try {
await this.createClient(serverUrl, token, preauthSecret);
} catch (error) {
logError('NetworkManager init error', error);
}
}
};
public invalidateClient = (serverUrl: string) => {
this.clients[serverUrl]?.invalidate();
delete this.clients[serverUrl];
};
public getClient = (serverUrl: string) => {
const client = this.clients[serverUrl];
if (!client) {
throw new Error(`${serverUrl} client not found`);
}
return client;
};
public createClient = async (serverUrl: string, bearerToken?: string, preauthSecret?: string) => {
const config = await this.buildConfig(preauthSecret);
try {
const {client} = await getOrCreateAPIClient(serverUrl, config, this.clientErrorEventHandler);
const csrfToken = await getCSRFFromCookie(serverUrl);
// Pass preauthSecret explicitly to constructor to match ClientBase behavior
this.clients[serverUrl] = new Client(client, serverUrl, bearerToken, csrfToken, preauthSecret);
} catch (error) {
throw new ClientError(serverUrl, {
message: 'Cant find this server. Check spelling and URL format.',
intl: {
id: 'apps.error.network.no_server',
defaultMessage: 'Cant find this server. Check spelling and URL format.',
},
url: serverUrl,
details: error,
});
}
return this.clients[serverUrl];
};
private buildConfig = async (preauthSecret?: string) => {
const userAgent = `Mattermost Mobile/${nativeApplicationVersion}+${nativeBuildVersion} (${osName}; ${osVersion}; ${modelName})`;
const managedConfig = ManagedApp.enabled ? Emm.getManagedConfig<ManagedConfig>() : undefined;
const headers: Record<string, string> = {
[ClientConstants.HEADER_USER_AGENT]: userAgent,
...(preauthSecret ? {[ClientConstants.HEADER_X_MATTERMOST_PREAUTH_SECRET]: preauthSecret} : {}),
...this.DEFAULT_CONFIG.headers,
};
const config = {
...this.DEFAULT_CONFIG,
sessionConfiguration: {
...this.DEFAULT_CONFIG.sessionConfiguration,
timeoutIntervalForRequest: managedConfig?.timeout ? parseInt(managedConfig.timeout, 10) : this.DEFAULT_CONFIG.sessionConfiguration?.timeoutIntervalForRequest,
timeoutIntervalForResource: managedConfig?.timeoutVPN ? parseInt(managedConfig.timeoutVPN, 10) : this.DEFAULT_CONFIG.sessionConfiguration?.timeoutIntervalForResource,
waitsForConnectivity: managedConfig?.useVPN === 'true',
collectMetrics: LocalConfig.CollectNetworkMetrics,
},
headers,
};
return config;
};
private clientErrorEventHandler: APIClientErrorEventHandler = (event: APIClientErrorEvent) => {
if (CLIENT_CERTIFICATE_IMPORT_ERROR_CODES.includes(event.errorCode)) {
DeviceEventEmitter.emit(CERTIFICATE_ERRORS.CLIENT_CERTIFICATE_IMPORT_ERROR, event.serverUrl);
} else if (CLIENT_CERTIFICATE_MISSING_ERROR_CODE === event.errorCode) {
DeviceEventEmitter.emit(CERTIFICATE_ERRORS.CLIENT_CERTIFICATE_MISSING, event.serverUrl);
} else if (SERVER_CERTIFICATE_INVALID === event.errorCode) {
logDebug('Invalid SSL certificate:', event.errorDescription);
const parsed = urlParse(event.serverUrl);
Alert.alert(
this.intl.formatMessage(messages.invalidSslTitle),
this.intl.formatMessage(messages.invalidSslDescription, {hostname: parsed.hostname}),
);
} else if (SERVER_TRUST_EVALUATION_FAILED === event.errorCode && !showingServerTrustAlert) {
logDebug('Invalid SSL Pinning:', event.errorDescription);
showingServerTrustAlert = true;
Alert.alert(
this.intl.formatMessage(messages.invalidPinningTitle),
event.errorDescription,
[{
text: this.intl.formatMessage({id: 'server_upgrade.dismiss', defaultMessage: 'Dismiss'}),
onPress: () => {
setTimeout(() => {
showingServerTrustAlert = false;
}, toMilliseconds({hours: 23}));
},
}],
);
}
};
}
const NetworkManager = new NetworkManagerSingleton();
export default NetworkManager;