O que é AsyncStorage?

Você pode pensar em AsyncStorage como um armazenamento local para React Native. Isso é porque é! Conforme descrito no site do React Native: “AsyncStorage é um sistema de armazenamento de valor-chave não criptografado, assíncrono, persistente e global para o aplicativo.”

É um bocado. Mas, simplesmente, ele permite que você salve dados localmente no dispositivo do usuário. Digamos que você queira relembrar uma configuração de tema de usuário ou permitir que continuem de onde pararam depois de reiniciar o telefone ou o aplicativo, a capacidade de persistir os dados offline torna o AsyncStorage seu melhor amigo!

Por que você não deve usar AsyncStorage para dados confidenciais

Se, por outro lado, você precisa armazenar dados confidenciais-ou seja, um token JWT-AsyncStorage é o melhor amigo que sempre coloca você em apuros. Lembre-se de que os dados que você salva com AsyncStorage não são criptografados, portanto, qualquer pessoa com acesso pode lê-los, o que não é bom para dados confidenciais.

Alternativas de armazenamento de dados criptografados

Para obter informações confidenciais, precisamos de uma maneira criptografada e segura de armazenar dados localmente. Felizmente, temos opções:

Todos os três são ótimas opções de uso, mas neste artigo, vamos cobrir Expo SecureStore .

Expo é um SDK maravilhoso com várias bibliotecas fabulosas, embora você precise configurar unimódulos para usar o Expo com um aplicativo Bare React. Mas, uma vez feito isso, o mundo é sua ostra.

Criando um projeto React Native

Se quiser dar uma olhada no código final, você pode encontrá-lo em meu GitHub :

Para inicializar seu projeto, cole o seguinte no Terminal:

 npx react-native init yourAppNameHere--template react-native-template-typescript

Isso cria um novo projeto React Native com um modelo TypeScript. Mas não acredite apenas na minha palavra, vamos construir os aplicativos e ver por si mesmo.

 yarn run ios

Depois de algum tempo, você verá seu novo aplicativo em seu simulador iOS. Agora, para Android:

 yarn run android
/pre>
Para usar os pacotes Expo em um projeto React Native vazio, primeiro precisamos instalar e configurar react-native-unimodules. Portanto, em seu Terminal, digite: 
 yarn add react-native-unimodules expo-secure-store && cd ios && pod install && cd..

O comando acima instalará as bibliotecas, navegará até a pasta iOS, instalará os CocoaPods dos seus projetos e, em seguida, navegará de volta à pasta do projeto.

💡 Dica: adicione um script de pós-instalação ao arquivo package.json para evitar instalações manuais de pod:

"scripts": { ... "postinstall":"cd ios && pod install" },

Desenvolvimento de armazenamento local criptografado no iOS

Se você estiver adicionando unimódulos a um projeto existente e não estiver familiarizado com o desenvolvimento nativo do iOS, preste atenção extra para onde você exclui e adiciona novas linhas de código.

Clique aqui para a comparação completa das alterações.

AppDelegate.h

Primeiro, vamos mudar o código deste:

 #import 
#import  @interface AppDelegate: UIResponder  @property (nonatomic, strong) UIWindow * window; @end 

Para isso:

 #import 
#import  #import  @interface AppDelegate: UMAppDelegateWrapper  @property (nonatomic, strong) UIWindow * window; @end 

AppDelegate.m

Agora, altere seu código de:

 #import"AppDelegate.h" #import 
#import 
#import  #ifdef FB_SONARKIT_ENABLED
#import 
#import 
#import 
#import 
#import 
#import  static void InitializeFlipper (aplicativo UIApplication *) {
FlipperClient * client=[FlipperClient sharedClient];
SKDescriptorMapper * layoutDescriptorMapper=[[SKDescriptorMapper alloc] initWithDefaults];
[cliente addPlugin: [[FlipperKitLayoutPlugin aloc] initWithRootNode: aplicativo comDescriptorMapper: layoutDescriptorMapper]];
[cliente addPlugin: [[FKUserDefaultsPlugin aloc] initWithSuiteName: nil]];
[cliente addPlugin: [FlipperKitReactPlugin novo]];
[cliente addPlugin: [[FlipperKitNetworkPlugin aloc] initWithNetworkAdapter: [SKIOSNetworkAdapter novo]]];
[início do cliente];
}
#fim se @implementation AppDelegate -(BOOL) aplicativo: (UIApplication *) aplicativo didFinishLaunchingWithOptions: (NSDictionary *) launchOptions
{
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper (aplicativo);
#fim se RCTBridge * bridge=[[RCTBridge alloc] initWithDelegate: self launchOptions: launchOptions];
RCTRootView * rootView=[[RCTRootView alloc] initWithBridge: bridge
moduleName: @"secureStoreExample"
initialProperties: nil]; rootView.backgroundColor=[[UIColor alloc] initWithRed: 1.0f verde: 1.0f azul: 1.0f alfa: 1]; self.window=[[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
UIViewController * rootViewController=[UIViewController novo];
rootViewController.view=rootView;
self.window.rootViewController=rootViewController;
[self.window makeKeyAndVisible];
retornar SIM;
} -(NSURL *) sourceURLForBridge: (RCTBridge *) ponte
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot: @"index"fallbackResource: nil];
#outro
return [[NSBundle mainBundle] URLForResource: @"main"withExtension: @"jsbundle"];
#fim se
} @end 

Para isso:

 #import"AppDelegate.h" #import 
#import 
#import  #import 
#import 
#import  #ifdef FB_SONARKIT_ENABLED
#import 
#import 
#import 
#import 
#import 
#import  static void InitializeFlipper (aplicativo UIApplication *) { FlipperClient * client=[FlipperClient sharedClient]; SKDescriptorMapper * layoutDescriptorMapper=[[SKDescriptorMapper alloc] initWithDefaults]; [cliente addPlugin: [[FlipperKitLayoutPlugin aloc] initWithRootNode: aplicativo comDescriptorMapper: layoutDescriptorMapper]]; [cliente addPlugin: [[FKUserDefaultsPlugin alloc] initWithSuiteName: nil]]; [cliente addPlugin: [FlipperKitReactPlugin novo]]; [cliente addPlugin: [[FlipperKitNetworkPlugin aloc] initWithNetworkAdapter: [SKIOSNetworkAdapter novo]]]; [início do cliente];
}
#fim se @interface AppDelegate ()  @property (não atômico, forte) UMModuleRegistryAdapter * moduleRegistryAdapter; @fim @implementation AppDelegate -(BOOL) aplicativo: (UIApplication *) aplicativo didFinishLaunchingWithOptions: (NSDictionary *) launchOptions
{
#ifdef FB_SONARKIT_ENABLED InitializeFlipper (aplicativo);
#fim se self.moduleRegistryAdapter=[[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider: [[UMModuleRegistryProvider alloc] init]]; RCTBridge * bridge=[[RCTBridge alloc] initWithDelegate: self launchOptions: launchOptions]; RCTRootView * rootView=[[RCTRootView alloc] initWithBridge: bridge moduleName: @"secureStoreExample" initialProperties: nil]; rootView.backgroundColor=[[UIColor alloc] initWithRed: 1.0f verde: 1.0f azul: 1.0f alfa: 1]; self.window=[[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds]; UIViewController * rootViewController=[UIViewController novo]; rootViewController.view=rootView; self.window.rootViewController=rootViewController; [self.window makeKeyAndVisible]; [superaplicativo: aplicativo didFinishLaunchingWithOptions: launchOptions]; retornar SIM;
} -(NSArray > *) extraModulesForBridge: (RCTBridge *) ponte
{ NSArray > * extraModules=[_moduleRegistryAdapter extraModulesForBridge: bridge]; //Se você gostaria de exportar alguns RCTBridgeModules personalizados que não são módulos Expo, adicione-os aqui! return extraModules;
} -(NSURL *) sourceURLForBridge: (RCTBridge *) ponte
{
#if DEBUG return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot: @"index"fallbackResource: nil];
#outro return [[NSBundle mainBundle] URLForResource: @"main"withExtension: @"jsbundle"];
#fim se
} @fim

Podfile

A seguir, vamos pegar este código:

 require_relative'../node_modules/react-native/scripts/react_native_pods'
require_relative'../node_modules/@react-native-community/cli-platform-ios/native_modules' plataforma: ios,'10.0' target'secureStoreExample'do config=use_native_modules! use_react_native! (: path=> config ["reactNativePath"]) target'secureStoreExampleTests'fazer herdar!:completo # Pods para teste fim # Ativa o Flipper. # # Observe que se você tiver use_frameworks! habilitado, o Flipper não funcionará e # você deve desabilitar as próximas linhas. use_flipper! post_install do | installer | flipper_post_install (instalador) fim
fim target'secureStoreExample-tvOS'do # Pods para secureStoreExample-tvOS target'secureStoreExample-tvOSTests'do herdar!: search_paths # Pods para teste fim
fim

E mude para:

 require_relative'../node_modules/react-native/scripts/react_native_pods'
require_relative'../node_modules/@react-native-community/cli-platform-ios/native_modules'
require_relative'../node_modules/react-native-unimodules/cocoapods.rb' plataforma: ios,'10.0' target'secureStoreExample'do config=use_native_modules! use_unimodules! use_react_native! (: path=> config ["reactNativePath"]) target'secureStoreExampleTests'fazer herdar!:completo # Pods para teste fim # Ativa o Flipper. # # Observe que se você tiver use_frameworks! habilitado, o Flipper não funcionará e # você deve desabilitar as próximas linhas. use_flipper! post_install do | installer | flipper_post_install (instalador) fim
fim target'secureStoreExample-tvOS'do # Pods para secureStoreExample-tvOS target'secureStoreExample-tvOSTests'do herdar!: search_paths # Pods para teste fim
fim 

Agora que fizemos as alterações necessárias na pasta do iOS, precisamos instalar nosso CocoaPods novamente:

 cd ios && pod install

No momento em que este artigo foi escrito, a versão mais recente da Expo SecureStore não era compatível com CocoaPods. Se você tiver esse problema, instale a versão anterior. No meu caso:

 yarn add [email protected] && cd ios && pod install && cd..

Android

Clique aqui para a comparação completa das alterações.

android/build.gradle

Vamos atualizar o código a partir do seguinte.

//Arquivo de construção de nível superior onde você pode adicionar opções de configuração comuns a todos os subprojetos/módulos. buildscript { ext { buildToolsVersion="29.0.2" minSdkVersion=16 compileSdkVersion=29 targetSdkVersion=29 } repositórios { Google() jcenter () } dependencies { classpath ("com.android.tools.build:gradle:3.5.3") //NOTA: Não coloque as dependências do seu aplicativo aqui; eles pertencem //nos arquivos individuais do módulo build.gradle }
} allprojects { repositórios { mavenLocal () maven { //Todo o React Native (JS, fontes Obj-C, binários Android) é instalado a partir do npm url ("$ rootDir/../node_modules/react-native/android") } maven { //Android JSC é instalado a partir do npm url ("$ rootDir/../node_modules/jsc-android/dist") } Google() jcenter () maven {url'https://www.jitpack.io'} }
}

E mude para:

//Arquivo de construção de nível superior onde você pode adicionar opções de configuração comuns a todos os subprojetos/módulos. buildscript { ext { buildToolsVersion="29.0.2" minSdkVersion=21 compileSdkVersion=29 targetSdkVersion=29 } repositórios { Google() jcenter () } dependencies { classpath ("com.android.tools.build:gradle:3.5.3") //NOTA: Não coloque as dependências do aplicativo aqui; eles pertencem //nos arquivos individuais do módulo build.gradle }
} allprojects { repositórios { mavenLocal () maven { //Todo o React Native (JS, fontes Obj-C, binários Android) é instalado a partir do npm url ("$ rootDir/../node_modules/react-native/android") } maven { //Android JSC é instalado a partir do npm url ("$ rootDir/../node_modules/jsc-android/dist") } Google() jcenter () maven {url'https://www.jitpack.io'} }
} 

android/app/build.gradle

Disto:

 aplicar plugin:"com.android.application" import com.android.build.OutputFile ....
//aproximadamente linha 183
dependencies { implementação fileTree (dir:"libs", incluir: ["*.jar"]) //noinspection GradleDynamicVersion implementação"com.facebook.react: react-native: +"//De node_modules implementação"androidx.swiperefreshlayout: swiperefreshlayout: 1.0.0" debugImplementation ("com.facebook.flipper: flipper: $ {FLIPPER_VERSION}") { excluir grupo:'com.facebook.fbjni' } debugImplementation ("com.facebook.flipper: flipper-network-plugin: $ {FLIPPER_VERSION}") { excluir grupo:'com.facebook.flipper' excluir grupo:'com.squareup.okhttp3', módulo:'okhttp' } debugImplementation ("com.facebook.flipper: flipper-fresco-plugin: $ {FLIPPER_VERSION}") { excluir grupo:'com.facebook.flipper' } if (enableHermes) { def hermesPath="../../node_modules/hermes-engine/android/"; Arquivos debugImplementation (hermesPath +"hermes-debug.aar") arquivos releaseImplementation (hermesPath +"hermes-release.aar") } outro { implementação jscFlavor }
}

Para isso:

 aplicar plugin:"com.android.application"
inscreva-se em:'../../node_modules/react-native-unimodules/gradle.groovy' import com.android.build.OutputFile ...
//aproximadamente linha 184:
dependencies { implementação fileTree (dir:"libs", inclua: ["*.jar"]) //noinspection GradleDynamicVersion implementação"com.facebook.react: react-native: +"//De node_modules implementação"androidx.swiperefreshlayout: swiperefreshlayout: 1.0.0"
//Adicione esta linha aqui addUnimodulesDependencies ()
addUnimodulesDependencies () debugImplementation ("com.facebook.flipper: flipper: $ {FLIPPER_VERSION}") { excluir grupo:'com.facebook.fbjni' } debugImplementation ("com.facebook.flipper: flipper-network-plugin: $ {FLIPPER_VERSION}") { excluir grupo:'com.facebook.flipper' excluir grupo:'com.squareup.okhttp3', módulo:'okhttp' } debugImplementation ("com.facebook.flipper: flipper-fresco-plugin: $ {FLIPPER_VERSION}") { excluir grupo:'com.facebook.flipper' } if (enableHermes) { def hermesPath="../../node_modules/hermes-engine/android/"; Arquivos debugImplementation (hermesPath +"hermes-debug.aar") arquivos releaseImplementation (hermesPath +"hermes-release.aar") } outro { implementação jscFlavor }
} 

android/settings.gradle

Agora, vamos de:

 rootProject.name='secureStoreExample'
aplique a partir de: file ("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle (configurações)
incluir': app'

Para isso:

 rootProject.name='secureStoreExample'
inscreva-se em:'../node_modules/react-native-unimodules/gradle.groovy'; includeUnimodulesProjects ()
aplique a partir de: file ("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle (configurações)
incluir': app'

android/app/src/main/java/com/yourappname/MainApplication.java

Altere o seu código de:

 package com.securestoreexample; import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List; public class MainApplication extends Application implementa ReactApplication { final privado ReactNativeHost mReactNativeHost= novo ReactNativeHost (this) { @Sobrepor public boolean getUseDeveloperSupport () { return BuildConfig.DEBUG; } @Sobrepor Protected List  getPackages () { @SuppressWarnings ("UnnecessaryLocalVariable") Listar pacotes =new PackageList (this).getPackages (); //Pacotes que não podem ser vinculados automaticamente ainda podem ser adicionados manualmente aqui, por exemplo: //packages.add (new MyReactNativePackage ()); devolver pacotes; } @Sobrepor protected String getJSMainModuleName () { retornar"índice"; } };
...

Para:

 package com.securestoreexample; import com.secureStoreExample.generated.BasePackageList; import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.Arrays; import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
import org.unimodules.core.interfaces.SingletonModule;
public class MainApplication extends Application implementa ReactApplication { final privado ReactModuleRegistryProvider mModuleRegistryProvider=new ReactModuleRegistryProvider (new BasePackageList (). getPackageList (), null); final privado ReactNativeHost mReactNativeHost= novo ReactNativeHost (this) { @Sobrepor public boolean getUseDeveloperSupport () { return BuildConfig.DEBUG; } @Sobrepor Protected List  getPackages () { @SuppressWarnings ("UnnecessaryLocalVariable") Listar pacotes =new PackageList (this).getPackages (); //Pacotes que ainda não podem ser vinculados automaticamente podem ser adicionados manualmente aqui, por exemplo: //packages.add (new MyReactNativePackage ()); //Adicionar unimódulos Listar  unimodules=Arrays.  asList ( novo ModuleRegistryAdapter (mModuleRegistryProvider) ); packages.addAll (unimodules); devolver pacotes; } @Sobrepor protected String getJSMainModuleName () { retornar"índice"; } };
...

Expo SecureStore API

SecureStore tem três métodos principais de API: definir, obter e excluir. Vamos limpar nosso arquivo App.tsx e explorá-los. Para começar, substitua o conteúdo de App.tsx por:

 import React, {useState} de'react';
import {SafeAreaView, StyleSheet, StatusBar, Button, Text} de'react-native'; const App=()=> { Retorna ( <>   

Isso nos dá três botões no meio da tela. Eles não fazem nada no momento, então vamos consertar isso! Assim como o AsyncStorage, o SecureStore usa pares de valores-chave e, como estamos usando o TypeScript, podemos utilizar enums:

 import React, {useState} de'react';
importar {SafeAreaView, StyleSheet, StatusBar, Button, Text} de'react-native'; export enum SecureStoreEnum { TOKEN='token',
} const App=()=> { Retorna ( <>   

Usamos este enum (chave) para emparelhar o valor que vamos armazenar em nosso dispositivo.

A seguir, vamos configurar um gancho useState para armazenar nossa variável de estado-valor recuperado de nosso dispositivo-mais a variável simulada que iremos armazenar.

 import React, {useState} de'react';
importar {SafeAreaView, StyleSheet, StatusBar, Button, Text} de'react-native'; export enum SecureStoreEnum { TOKEN='token',
} const App=()=> { const [token, setToken]=useState  (''); const fakeToken='Um token falso & # x1f36a;'; Retorna ( <>   

Você pode visualizar o código nesta essência .

SecureStore.setItemAsync (chave, valor);

Ok, está tudo configurado, agora estamos realmente cozinhando a gás! É hora de brincar com SecureStore. 😃

Como SecureStore é assíncrono, precisamos chamá-lo em uma função assíncrona. Adicione o seguinte ao App.tsx:

 import React, {useState} de'react';
import { SafeAreaView, StyleSheet, Barra de status, Botão, Texto, Alerta,
} de'react-native';
import * como SecureStore de'expo-secure-store'; export enum SecureStoreEnum { TOKEN='token',
} const App=()=> { const [token, setToken]=useState  (''); const fakeToken='Um token falso & # x1f36a;'; const handleSetToken=async ()=> { SecureStore.setItemAsync (SecureStoreEnum.TOKEN, fakeToken).then; setToken (fakeToken); }; Retorna ( <>   

setItemAsync usa o enum que criamos anteriormente como a chave para o valor que estamos salvando, Um token falso & # x1f36a; . Em seguida, aguarda a conclusão da função. Assim que nossa função assíncrona for concluída, salvamos a mesma variável em nosso gancho useState com setToken .

Agora, se você clicar em definir token, verá nosso token falso aparecer abaixo dos três botões.

SecureStore.getItemAsync (key);

A seguir, forneceremos ao botão “obter token” alguma funcionalidade para que possamos recuperar o valor que armazenamos em nosso dispositivo. Copie e cole em App.tsx:

 import React, {useState} de'react';
import { SafeAreaView, StyleSheet, Barra de status, Botão, Texto, Alerta,
} de'react-native';
import * como SecureStore de'expo-secure-store'; export enum SecureStoreEnum { TOKEN='token',
} const App=()=> { const [token, setToken]=useState  (''); const fakeToken='Um token falso & # x1f36a;'; const handleSetToken=async ()=> { esperar SecureStore.setItemAsync (SecureStoreEnum.TOKEN, fakeToken); setToken (fakeToken); }; const handleGetToken=async ()=> { const tokenFromPersistentState=await SecureStore.getItemAsync ( SecureStoreEnum.TOKEN, ); if (tokenFromPersistentState) { Alert.alert ( "Este token está armazenado no seu dispositivo, não é legal !:", tokenFromPersistentState, ); } }; Retorna ( <>   

Usamos uma instrução if na linha 29 para garantir que a caixa de alerta só será aberta se um valor for recuperado.

Agora, para a mágica: feche seu aplicativo ou reinicie seu dispositivo e clique em obter token. Ta-da! Seu token está de volta.

SecureStore.deleteItemAsync (chave);

Se este fosse um aplicativo do mundo real, precisaríamos ser capazes de excluir o token do usuário quando eles se desconectarem. Última parte do código, prometo 😇.

 import React, {useState} de'react';
import { SafeAreaView, StyleSheet, Barra de status, Botão, Texto, Alerta,
} de'react-native';
import * como SecureStore de'expo-secure-store'; export enum SecureStoreEnum { TOKEN='token',
} const App=()=> { const [token, setToken]=useState  (''); const fakeToken='Um token falso & # x1f36a;'; const handleSetToken=async ()=> { esperar SecureStore.setItemAsync (SecureStoreEnum.TOKEN, fakeToken); setToken (fakeToken); }; const handleGetToken=async ()=> { const tokenFromPersistentState=await SecureStore.getItemAsync ( SecureStoreEnum.TOKEN, ); if (tokenFromPersistentState) { Alert.alert ( "Este token está armazenado no seu dispositivo, não é legal !:", tokenFromPersistentState, ); } }; const handleDeleteToken=async ()=> { esperar SecureStore.deleteItemAsync (SecureStoreEnum.TOKEN); setToken (''); }; Retorna ( <>   

Aqui, handleDeleteToken na linha 37 exclui o token e, em seguida, define o estado do token como uma string vazia (estado inicial).

Agora seus dados estão seguros e criptografados!

A postagem React Native e Expo SecureStore: criptografar dados locais apareceu primeiro no LogRocket Blog .

Source link