mattermost-mobile/app/screens/report_a_problem/app_logs.tsx
Elias Nahum 44eb76bed7
feat(iOS): Add Microsoft Intune MAM integration with multi-server support (#9312)
* refactor: implement custom ExpoImage wrapper for cache control

Add ExpoImage component with automatic cacheKey/cachePath management and replace all expo-image imports across the app

* refactor(ios): convert Gekidou to CocoaPods

Migrate from Swift Package Manager to CocoaPods, add Keychain write operations, refactor notification handler to remove react-native-notifications headers, and upgrade Swift to 5.0

* npm audit

* update fastlane

* feat(ci): integrate Intune MAM for enterprise builds with strict OSS protection

Add Intune submodule, CI actions, Fastlane configuration, developer scripts, pre-commit hooks, and validation workflows to enable internal MAM builds while protecting OSS repository

* fix tests by mocking @mattermost/intune

* feat: implement Intune MAM integration with comprehensive security enforcement

Add IntuneManager, refactor SecurityManager/SessionManager for MAM policies, implement native OIDC auth flow, add biometric enforcement, conditional launch blocking, and file protection controls

* fix alerts when no server database is present

* Unify cache strategy

* fix emit config changed after it was stored in the db

* Handle Mid-Session Enrollment Detection

* fix ADALLogOverrideDisabled missing in Fastfile

* fix flow for initial enrollment

* fix and add unit tests

* enable Intune configuration for PR and beta builds, CLIENT_ID should be changed before actual release

* Update intune submodule with addressed feedback

* fix validate-intune-clean workflow

* feat(intune): add comprehensive error handling and SAML+Entra support

Add production-ready error handling for native Entra authentication with
user-friendly i18n messages, comprehensive test coverage, and support for
Entra login when server requires SAML.

* update i18n

* update intune submodule

* update build-pr token

* fix race condition between server auth and intune enrollment

* fix CI workflow to build with intune

* use deploy key for intune submodule

* set the config directly in the submodule .git

* debug injection

* try setting GIT_SSH_COMMAND

* remove action debug

* fix server url input

* match pod cache with intune hash

* Fastfile and envs

* have workflows check for intune/.git

* have ci cache intune frameworks as well

* update Fastlane to set no-cache to artifacts uploaded

* fix s3 upload

* fix pblist template

* Attempt to remove the cache control for PR uploads to s3

* use hash from commit for S3 path

* Implement crash-resilient selective wipe with automatic retry and add removeInternetPassword to Gekidou Keychain

* Fix surface errors from intune login

* fix postinstall scripts

* use cacheKey for draft md images

* remove unnecessary double await during test

* Have isMinimumLicenseTier accept valid license sku tier as target

* Add missing Auth error messages

* remove the last period for intune errors in i18n

* do not call unenroll with wipe on manual logout

* Fix tests and Intune error messages

* do not filter any SSO type regardless of which is used for Intune

* fix 412 to not retry

* fix tests, app logs sharing and share_extension avatar cache

* apply setScreenCapturePolicy on license change

Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com>

* re-apply screen capture on enrollment

Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com>

* use userData from intunr login and prevent getMe

Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com>

* Check for Biometrics and Jailbreak as we used to

---------

Co-authored-by: Eva Sarafianou <eva.sarafianou@mattermost.com>
2025-12-10 13:07:28 +02:00

119 lines
3.7 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import TurboLogger from '@mattermost/react-native-turbo-log';
import RNUtils from '@mattermost/rnutils';
import React, {useCallback, useEffect, useState} from 'react';
import {useIntl} from 'react-intl';
import {View, Text, ActivityIndicator, Platform} from 'react-native';
import Share from 'react-native-share';
import Button from '@components/button';
import {useServerUrl} from '@context/server';
import {useTheme} from '@context/theme';
import SecurityManager from '@managers/security_manager';
import {deleteFile, pathWithPrefix} from '@utils/file';
import {logDebug} from '@utils/log';
import {makeStyleSheetFromTheme} from '@utils/theme';
import LogFileItem from './log_file_item';
import {getCommonStyleSheet} from './styles';
const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
...getCommonStyleSheet(theme),
buttonContainer: {
alignItems: 'flex-start',
},
container: {
gap: 16,
},
}));
const AppLogs = () => {
const theme = useTheme();
const styles = getStyleSheet(theme);
const intl = useIntl();
const serverUrl = useServerUrl();
const [logFiles, setLogFiles] = useState<string[]>([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
const fetchLogs = async () => {
try {
const paths = await TurboLogger.getLogPaths();
setLogFiles(paths);
} finally {
setIsLoading(false);
}
};
fetchLogs();
}, []);
const handleDownload = useCallback(async () => {
let zipFilePath = '';
try {
zipFilePath = await RNUtils.createZipFile(logFiles);
if (Platform.OS === 'android') {
await RNUtils.saveFile(zipFilePath);
} else {
await Share.open({
url: pathWithPrefix('file://', zipFilePath),
// If Allowing to save to Files app, save it there, otherwise show share dialog
saveToFiles: SecurityManager.canSaveToLocation(serverUrl, 'FilesApp'),
});
}
} catch (error) {
logDebug('Failed to create save file', error);
}
if (zipFilePath) {
try {
await deleteFile(zipFilePath);
} catch (error) {
logDebug('Failed to delete zip file', error);
}
}
}, [logFiles, serverUrl]);
const hasLogs = logFiles.length > 0;
return (
<View style={styles.container}>
<Text style={styles.sectionTitle}>
{intl.formatMessage({
id: 'screen.report_problem.logs.title',
defaultMessage: 'APP LOGS:',
})}
</Text>
<View>
{isLoading ? (
<ActivityIndicator
testID='logs-loading'
color={theme.buttonBg}
size='large'
/>
) : (hasLogs && (
<LogFileItem/>
))}
</View>
<View style={styles.buttonContainer}>
<Button
onPress={handleDownload}
text={intl.formatMessage({
id: 'screen.report_problem.logs.download',
defaultMessage: 'Download App Logs',
})}
emphasis='tertiary'
theme={theme}
iconName='download-outline'
disabled={!hasLogs}
/>
</View>
</View>
);
};
export default AppLogs;