Merge pull request #5750 from BlueWallet/androidpermissions

FIX: Permission issues on newer Android APIs
This commit is contained in:
GLaDOS 2023-10-21 09:54:42 +01:00 committed by GitHub
commit c4b337fcf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 83 deletions

View file

@ -8,6 +8,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" /> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application <application
android:name=".MainApplication" android:name=".MainApplication"
android:label="@string/app_name" android:label="@string/app_name"

View file

@ -4,6 +4,7 @@ import Frisbee from 'frisbee';
import { getApplicationName, getVersion, getSystemName, getSystemVersion, hasGmsSync, hasHmsSync } from 'react-native-device-info'; import { getApplicationName, getVersion, getSystemName, getSystemVersion, hasGmsSync, hasHmsSync } from 'react-native-device-info';
import AsyncStorage from '@react-native-async-storage/async-storage'; import AsyncStorage from '@react-native-async-storage/async-storage';
import loc from '../loc'; import loc from '../loc';
import { requestNotifications } from 'react-native-permissions';
const PushNotification = require('react-native-push-notification'); const PushNotification = require('react-native-push-notification');
const constants = require('./constants'); const constants = require('./constants');
@ -38,75 +39,80 @@ function Notifications(props) {
*/ */
const configureNotifications = async function () { const configureNotifications = async function () {
return new Promise(function (resolve) { return new Promise(function (resolve) {
PushNotification.configure({ requestNotifications(['alert', 'sound', 'badge']).then(({ status, _ }) => {
// (optional) Called when Token is generated (iOS and Android) if (status === 'granted') {
onRegister: async function (token) { PushNotification.configure({
console.log('TOKEN:', token); // (optional) Called when Token is generated (iOS and Android)
alreadyConfigured = true; onRegister: async function (token) {
await _setPushToken(token); console.log('TOKEN:', token);
resolve(true); alreadyConfigured = true;
}, await _setPushToken(token);
resolve(true);
},
// (required) Called when a remote is received or opened, or local notification is opened // (required) Called when a remote is received or opened, or local notification is opened
onNotification: async function (notification) { onNotification: async function (notification) {
// since we do not know whether we: // since we do not know whether we:
// 1) received notification while app is in background (and storage is not decrypted so wallets are not loaded) // 1) received notification while app is in background (and storage is not decrypted so wallets are not loaded)
// 2) opening this notification right now but storage is still unencrypted // 2) opening this notification right now but storage is still unencrypted
// 3) any of the above but the storage is decrypted, and app wallets are loaded // 3) any of the above but the storage is decrypted, and app wallets are loaded
// //
// ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage) // ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage)
const payload = Object.assign({}, notification, notification.data); const payload = Object.assign({}, notification, notification.data);
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data); if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
delete payload.data; delete payload.data;
// ^^^ weird, but sometimes payload data is not in `data` but in root level // ^^^ weird, but sometimes payload data is not in `data` but in root level
console.log('got push notification', payload); console.log('got push notification', payload);
await Notifications.addNotification(payload); await Notifications.addNotification(payload);
// (required) Called when a remote is received or opened, or local notification is opened // (required) Called when a remote is received or opened, or local notification is opened
notification.finish(PushNotificationIOS.FetchResult.NoData); notification.finish(PushNotificationIOS.FetchResult.NoData);
// if user is staring at the app when he receives the notification we process it instantly // if user is staring at the app when he receives the notification we process it instantly
// so app refetches related wallet // so app refetches related wallet
if (payload.foreground) props.onProcessNotifications(); if (payload.foreground) props.onProcessNotifications();
}, },
// (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android) // (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)
onAction: function (notification) { onAction: function (notification) {
console.log('ACTION:', notification.action); console.log('ACTION:', notification.action);
console.log('NOTIFICATION:', notification); console.log('NOTIFICATION:', notification);
// process the action // process the action
}, },
// (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS) // (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS)
onRegistrationError: function (err) { onRegistrationError: function (err) {
console.error(err.message, err); console.error(err.message, err);
resolve(false); resolve(false);
}, },
// IOS ONLY (optional): default: all - Permissions to register. // IOS ONLY (optional): default: all - Permissions to register.
permissions: { permissions: {
alert: true, alert: true,
badge: true, badge: true,
sound: true, sound: true,
}, },
// Should the initial notification be popped automatically // Should the initial notification be popped automatically
// default: true // default: true
popInitialNotification: true, popInitialNotification: true,
/** /**
* (optional) default: true * (optional) default: true
* - Specified if permissions (ios) and token (android and ios) will requested or not, * - Specified if permissions (ios) and token (android and ios) will requested or not,
* - if not, you must call PushNotificationsHandler.requestPermissions() later * - if not, you must call PushNotificationsHandler.requestPermissions() later
* - if you are not using remote notification or do not have Firebase installed, use this: * - if you are not using remote notification or do not have Firebase installed, use this:
* requestPermissions: Platform.OS === 'ios' * requestPermissions: Platform.OS === 'ios'
*/ */
requestPermissions: true, requestPermissions: true,
});
}
}); });
}); });
// …
}; };
Notifications.cleanUserOptOutFlag = async function () { Notifications.cleanUserOptOutFlag = async function () {
@ -227,8 +233,7 @@ function Notifications(props) {
if (!pushToken || !pushToken.token || !pushToken.os) return; if (!pushToken || !pushToken.token || !pushToken.os) return;
const api = new Frisbee({ baseURI }); const api = new Frisbee({ baseURI });
const postCall = await api.post(
return await api.post(
'/unsubscribe', '/unsubscribe',
Object.assign({}, _getHeaders(), { Object.assign({}, _getHeaders(), {
body: { body: {
@ -240,6 +245,8 @@ function Notifications(props) {
}, },
}), }),
); );
Notifications.abandonPermissions();
return postCall;
}; };
Notifications.isNotificationsEnabled = async function () { Notifications.isNotificationsEnabled = async function () {

View file

@ -47,7 +47,7 @@
<key>WidgetsExtension.xcscheme_^#shared#^_</key> <key>WidgetsExtension.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>144</integer> <integer>99</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View file

@ -16,7 +16,7 @@ require_relative '../node_modules/@react-native-community/cli-platform-ios/nativ
workspace 'BlueWallet' workspace 'BlueWallet'
platform :ios, '13.0' platform :ios, '13.0'
prepare_react_native_project! prepare_react_native_project!
setup_permissions(['Camera']) setup_permissions(['Camera', 'Notifications'])
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
# #

View file

@ -780,7 +780,7 @@ SPEC CHECKSUMS:
RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa RNHandoff: d3b0754cca3a6bcd9b25f544f733f7f033ccf5fa
RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c RNKeychain: a65256b6ca6ba6976132cc4124b238a5b13b3d9c
RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4 RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4
RNPermissions: eae8b97d8ab0587f082966ee608f47c97b2a349b RNPermissions: e9e703e08dfe50cf4d2ca45852eb45c4ac98f7b7
RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4 RNPrivacySnapshot: 71919dde3c6a29dd332115409c2aec564afee8f4
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a RNRate: ef3bcff84f39bb1d1e41c5593d3eea4aab2bd73a
@ -793,6 +793,6 @@ SPEC CHECKSUMS:
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236 RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9 Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9
PODFILE CHECKSUM: 23bb5c319ccbedd7e109c44fe4273afce6efb48a PODFILE CHECKSUM: 27db07925dc3a89e9ecced7e377a50dba9deb58a
COCOAPODS: 1.11.3 COCOAPODS: 1.13.0

View file

@ -1,6 +1,6 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TextInput, FlatList, Linking, TouchableOpacity, StyleSheet, Text, View, Platform, PermissionsAndroid, Alert } from 'react-native'; import { TextInput, FlatList, Linking, TouchableOpacity, StyleSheet, Text, View, Platform, Alert } from 'react-native';
import Clipboard from '@react-native-clipboard/clipboard'; import Clipboard from '@react-native-clipboard/clipboard';
import { Icon } from 'react-native-elements'; import { Icon } from 'react-native-elements';
import Share from 'react-native-share'; import Share from 'react-native-share';
@ -15,6 +15,7 @@ import { DynamicQRCode } from '../../components/DynamicQRCode';
import { isDesktop } from '../../blue_modules/environment'; import { isDesktop } from '../../blue_modules/environment';
import { useNavigation, useRoute, useTheme } from '@react-navigation/native'; import { useNavigation, useRoute, useTheme } from '@react-navigation/native';
import alert from '../../components/Alert'; import alert from '../../components/Alert';
import { PERMISSIONS, RESULTS, request } from 'react-native-permissions';
const bitcoin = require('bitcoinjs-lib'); const bitcoin = require('bitcoinjs-lib');
const currency = require('../../blue_modules/currency'); const currency = require('../../blue_modules/currency');
@ -68,15 +69,8 @@ const SendCreate = () => {
RNFS.unlink(filePath); RNFS.unlink(filePath);
}); });
} else if (Platform.OS === 'android') { } else if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { const granted = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE);
title: loc.send.permission_storage_title, if (granted === RESULTS.GRANTED) {
message: loc.send.permission_storage_message,
buttonNeutral: loc.send.permission_storage_later,
buttonNegative: loc._.cancel,
buttonPositive: loc._.ok,
});
if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) {
console.log('Storage Permission: Granted'); console.log('Storage Permission: Granted');
const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`; const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`;
try { try {

View file

@ -14,7 +14,6 @@ import {
StyleSheet, StyleSheet,
StatusBar, StatusBar,
ScrollView, ScrollView,
PermissionsAndroid,
InteractionManager, InteractionManager,
ActivityIndicator, ActivityIndicator,
I18nManager, I18nManager,
@ -45,6 +44,7 @@ import { AbstractHDElectrumWallet } from '../../class/wallets/abstract-hd-electr
import alert from '../../components/Alert'; import alert from '../../components/Alert';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { writeFileAndExport } from '../../blue_modules/fs'; import { writeFileAndExport } from '../../blue_modules/fs';
import { PERMISSIONS, RESULTS, request } from 'react-native-permissions';
const prompt = require('../../helpers/prompt'); const prompt = require('../../helpers/prompt');
@ -364,15 +364,8 @@ const WalletDetails = () => {
RNFS.unlink(filePath); RNFS.unlink(filePath);
}); });
} else if (Platform.OS === 'android') { } else if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE, { const granted = await request(PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE);
title: loc.send.permission_storage_title, if (granted === RESULTS.GRANTED) {
message: loc.send.permission_storage_message,
buttonNeutral: loc.send.permission_storage_later,
buttonNegative: loc._.cancel,
buttonPositive: loc._.ok,
});
if (granted === PermissionsAndroid.RESULTS.GRANTED || Platform.Version >= 33) {
console.log('Storage Permission: Granted'); console.log('Storage Permission: Granted');
const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`; const filePath = RNFS.DownloadDirectoryPath + `/${fileName}`;
try { try {

View file

@ -50,6 +50,8 @@ jest.mock('@react-native-community/push-notification-ios', () => {
return {}; return {};
}); });
jest.mock('react-native-permissions', () => require('react-native-permissions/mock'));
jest.mock('react-native-device-info', () => { jest.mock('react-native-device-info', () => {
return { return {
getUniqueId: jest.fn().mockReturnValue('uniqueId'), getUniqueId: jest.fn().mockReturnValue('uniqueId'),