mattermost-mobile-upstream/app/utils/server/index.ts
Elias Nahum b6fc85385e
Migrate from RNN to Expo router (#9402)
* Expo-Router partial migration

* Full migration to ExpoRouter

* i18n

* add tests

* Merge branch 'main' into expo-router

* align package-lock.json

* i18n

* feedback review

* feedback review

* android 16kb patch

* Refactored emoji picker keyboard using a state machine

* set android launch_screen in drawables instead of layout

* no conditional hooks

* remove duplicate call in test

* address feedback

* fix tests and address more feedback

* small corrections

* small corrections

* small corrections

* fix draft test

* fix draft test 2

* remove navigateToScreen in draff test

* fix nit in draft test

* remove doc file

* Feedback review

* fix snapshots

* improve test coverage

* fix i18n

* Upgrade t o RN 0.83, other dependencies and new architecture (#9737)

* library hardware-keyboard android new arch support fix

* library rnshare android new arch support fix

* library rnutils android new arch support fix

* library secure-pdf-viewer add new arch support

* Enable new arch on iOS and Android

* update emm and network client with new-arch

* update intune to use new-arch

* performance improvements

* fix tests

* fixes post input on new-arch

* fix android emoji picker

* remove doc

* run tests in parallel

* upgrade to React Native 0.83.9, Expo 55, React 19, and New Architecture

Bumps core dependencies to their latest versions:
- React Native 0.77.3 → 0.83.9 (New Architecture enabled)
- React 18.3.1 → 19.2.5
- Expo SDK 52 → 55
- TypeScript 5.8.3 → 6.0.3
- Node.js 22.16.0 → 24.15.0
- react-native-reanimated 3.17.3 → 4.3.0
- Various other dependency upgrades

Migrates iOS AppDelegate from Objective-C to Swift, updates Android
build tooling and Gradle wrapper, refreshes all patches and font
assets, and adapts app code, tests, and CI workflows throughout for
API changes introduced by these upgrades.

* remove unecessary dismissKeyboard in notifications

* feedback review

* update intune to use new-arch

* update @mattermost deps

* missing i18n string

* additional fixes

* update fastlane
2026-05-13 17:44:55 +08:00

235 lines
8.7 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Alert, type AlertButton} from 'react-native';
import {Screens, Sso, SupportedServer, Launch} from '@constants';
import {navigateToScreen} from '@screens/navigation';
import {getErrorMessage} from '@utils/errors';
import {isMinimumServerVersion} from '@utils/helpers';
import {tryOpenURL} from '@utils/url';
import type ServersModel from '@typings/database/models/app/servers';
import type {DeepLinkWithData} from '@typings/launch';
import type {IntlShape} from 'react-intl';
export function isSupportedServer(currentVersion: string) {
return isMinimumServerVersion(currentVersion, SupportedServer.MAJOR_VERSION, SupportedServer.MIN_VERSION, SupportedServer.PATCH_VERSION);
}
export function unsupportedServer(serverDisplayName: string, isSystemAdmin: boolean, intl: IntlShape, onPress?: () => void) {
if (isSystemAdmin) {
unsupportedServerAdminAlert(serverDisplayName, intl, onPress);
}
}
export function semverFromServerVersion(value: string) {
if (!value || typeof value !== 'string') {
return undefined;
}
const split = value.split('.');
const major = parseInt(split[0], 10);
const minor = parseInt(split[1] || '0', 10);
const patch = parseInt(split[2] || '0', 10);
return `${major}.${minor}.${patch}`;
}
export async function addNewServer(theme: Theme, serverUrl?: string, displayName?: string, deepLinkProps?: DeepLinkWithData) {
const props = {
displayName,
launchType: deepLinkProps ? Launch.AddServerFromDeepLink : Launch.AddServer,
serverUrl,
theme,
extra: deepLinkProps,
isModal: true,
isStackRoot: true,
};
navigateToScreen(Screens.SERVER, props);
}
export function loginOptions(config: ClientConfig, license: ClientLicense) {
const isLicensed = license.IsLicensed === 'true';
const samlEnabled = config.EnableSaml === 'true' && isLicensed && license.SAML === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true';
const isMinServerVersionForCloudOAuthChanges = isMinimumServerVersion(config.Version, 7, 6);
let googleEnabled = false;
let o365Enabled = false;
let openIdEnabled = false;
if (isMinServerVersionForCloudOAuthChanges) {
googleEnabled = config.EnableSignUpWithGoogle === 'true';
o365Enabled = config.EnableSignUpWithOffice365 === 'true';
openIdEnabled = config.EnableSignUpWithOpenId === 'true';
} else {
googleEnabled = config.EnableSignUpWithGoogle === 'true' && isLicensed;
o365Enabled = config.EnableSignUpWithOffice365 === 'true' && isLicensed && license.Office365OAuth === 'true';
openIdEnabled = config.EnableSignUpWithOpenId === 'true' && isLicensed;
}
const ldapEnabled = isLicensed && config.EnableLdap === 'true' && license.LDAP === 'true';
const hasLoginForm = config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true' || ldapEnabled;
const ssoOptions: SsoWithOptions = {
[Sso.SAML]: {enabled: samlEnabled, text: config.SamlLoginButtonText},
[Sso.GITLAB]: {enabled: gitlabEnabled},
[Sso.GOOGLE]: {enabled: googleEnabled},
[Sso.OFFICE365]: {enabled: o365Enabled},
[Sso.OPENID]: {enabled: openIdEnabled, text: config.OpenIdButtonText},
};
const enabledSSOs = Object.keys(ssoOptions).filter((key) => ssoOptions[key]);
const numberSSOs = enabledSSOs.length;
return {
enabledSSOs,
hasLoginForm,
numberSSOs,
ssoOptions,
};
}
export async function loginToServer(theme: Theme, serverUrl: string, displayName: string, config: ClientConfig, license: ClientLicense) {
const {enabledSSOs, hasLoginForm, numberSSOs, ssoOptions} = loginOptions(config, license);
const props = {
config,
hasLoginForm,
launchType: Launch.AddServer,
license,
serverDisplayName: displayName,
serverUrl,
ssoOptions,
theme,
isModal: true,
isStackRoot: true,
};
const redirectSSO = !hasLoginForm && numberSSOs === 1;
const screen = redirectSSO ? Screens.SSO : Screens.LOGIN;
if (redirectSSO) {
// @ts-expect-error ssoType not in definition
props.ssoType = enabledSSOs[0];
}
navigateToScreen(screen, props);
}
export async function editServer(theme: Theme, serverId: string) {
const props = {
serverId,
theme,
};
navigateToScreen(Screens.EDIT_SERVER, props);
}
export async function alertServerLogout(displayName: string, onPress: () => void, intl: IntlShape) {
Alert.alert(
intl.formatMessage({
id: 'server.logout.alert_title',
defaultMessage: 'Are you sure you want to log out of {displayName}?',
}, {displayName}),
intl.formatMessage({
id: 'server.logout.alert_description',
defaultMessage: 'All associated data will be removed',
}),
[{
style: 'cancel',
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
}, {
style: 'destructive',
text: intl.formatMessage({id: 'servers.logout', defaultMessage: 'Log out'}),
onPress,
}],
);
}
export async function alertServerRemove(displayName: string, onPress: () => void, intl: IntlShape) {
Alert.alert(
intl.formatMessage({
id: 'server.remove.alert_title',
defaultMessage: 'Are you sure you want to remove {displayName}?',
}, {displayName}),
intl.formatMessage({
id: 'server.remove.alert_description',
defaultMessage: 'This will remove it from your list of servers. All associated data will be removed',
}),
[{
style: 'cancel',
text: intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'}),
}, {
style: 'destructive',
text: intl.formatMessage({id: 'servers.remove', defaultMessage: 'Remove'}),
onPress,
}],
);
}
export function alertServerError(intl: IntlShape, error: unknown) {
const message = getErrorMessage(error, intl);
Alert.alert(
intl.formatMessage({
id: 'server.websocket.unreachable',
defaultMessage: 'Server is unreachable.',
}),
message,
);
}
export function alertServerAlreadyConnected(intl: IntlShape) {
Alert.alert(
'',
intl.formatMessage({
id: 'mobile.server_identifier.exists',
defaultMessage: 'You are already connected to this server.',
}),
);
}
export const sortServersByDisplayName = (servers: ServersModel[], intl: IntlShape) => {
function serverName(s: ServersModel) {
if (s.displayName === s.url) {
return intl.formatMessage({id: 'servers.default', defaultMessage: 'Default Server'});
}
return s.displayName;
}
return servers.sort((a, b) => {
return serverName(a).localeCompare(serverName(b));
});
};
function unsupportedServerAdminAlert(serverDisplayName: string, intl: IntlShape, onPress?: () => void) {
const title = intl.formatMessage({id: 'mobile.server_upgrade.title', defaultMessage: 'Server upgrade required'});
const message = intl.formatMessage({
id: 'server_upgrade.alert_description',
defaultMessage: 'Your server, {serverDisplayName}, is running an unsupported server version. Users will be exposed to compatibility issues that cause crashes or severe bugs breaking core functionality of the app. Upgrading to server version {supportedServerVersion} or later is required.',
}, {serverDisplayName, supportedServerVersion: SupportedServer.FULL_VERSION});
const cancel: AlertButton = {
text: intl.formatMessage({id: 'server_upgrade.dismiss', defaultMessage: 'Dismiss'}),
style: 'default',
onPress,
};
const learnMore: AlertButton = {
text: intl.formatMessage({id: 'server_upgrade.learn_more', defaultMessage: 'Learn More'}),
style: 'cancel',
onPress: () => {
const url = 'https://docs.mattermost.com/administration/release-lifecycle.html';
const onError = () => {
Alert.alert(
intl.formatMessage({id: 'mobile.link.error.title', defaultMessage: 'Error'}),
intl.formatMessage({id: 'mobile.link.error.text', defaultMessage: 'Unable to open the link.'}),
);
};
tryOpenURL(url, onError);
},
};
const buttons: AlertButton[] = [cancel, learnMore];
const options = {cancelable: false};
Alert.alert(title, message, buttons, options);
}