Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/storage-plugin-keychain-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rozenite/storage-plugin': minor
---

Add `createKeychainStorageAdapter` to inspect and edit react-native-keychain (iOS Keychain and Android Keystore) entries in the Storage panel.

Storages can now be marked read-only with `capabilities.readOnly`. The panel disables adding, editing, deleting, purging and importing for them, and the device rejects those changes.
5 changes: 5 additions & 0 deletions .changeset/storage-plugin-purge-refresh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rozenite/storage-plugin': patch
---

Refresh the Storage panel after a purge that fails partway, so it no longer shows entries that were already removed.
1 change: 1 addition & 0 deletions apps/playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"react-native-gesture-handler": "~2.32.0",
"react-native-get-random-values": "^1.11.0",
"react-native-harness": "^1.0.0-alpha.24",
"react-native-keychain": "^10.0.0",
"react-native-mmkv": "^4.3.2",
"react-native-nitro-fetch": "^1.5.1",
"react-native-nitro-modules": "^0.36.1",
Expand Down
31 changes: 28 additions & 3 deletions apps/playground/src/app/screens/StoragePluginScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { Alert, FlatList, Text } from 'react-native';
import * as Keychain from 'react-native-keychain';
import * as SecureStore from 'expo-secure-store';
import { MMKV } from 'react-native-mmkv';
import {
Expand All @@ -23,7 +24,7 @@ import {
} from '../storage-plugin-adapters';
import { useTheme } from '../theme/useTheme';

type AdapterTab = 'mmkv' | 'async' | 'secure';
type AdapterTab = 'mmkv' | 'async' | 'secure' | 'keychain';
type AsyncStorageMode = 'v2-default' | 'v3-auth' | 'v3-cache';
type EntryType = 'string' | 'number' | 'boolean' | 'buffer';
type AsyncStorageLike = {
Expand Down Expand Up @@ -125,6 +126,22 @@ export const StoragePluginScreen = () => {
return;
}

if (tab === 'keychain') {
const services = await Keychain.getAllGenericPasswordServices();
const credentials = await Promise.all(
services.map(async (service) => ({
service,
credentials: await Keychain.getGenericPassword({ service }),
})),
);
setEntries(
credentials.flatMap(({ service, credentials: item }) =>
item ? [{ key: service, type: 'string' as const, value: item.password }] : [],
),
);
return;
}

const keys = getKnownSecureStoreKeys();
const values = await Promise.all(
keys.map(async (entryKey) => ({
Expand Down Expand Up @@ -178,6 +195,8 @@ export const StoragePluginScreen = () => {
}
} else if (tab === 'async') {
await selectedAsyncStorage.setItem(key, value);
} else if (tab === 'keychain') {
await Keychain.setGenericPassword('playground', value, { service: key });
} else {
rememberSecureStoreKey(key);
await SecureStore.setItemAsync(key, value);
Expand Down Expand Up @@ -207,6 +226,8 @@ export const StoragePluginScreen = () => {
}
} else if (tab === 'async') {
await selectedAsyncStorage.removeItem(key);
} else if (tab === 'keychain') {
await Keychain.resetGenericPassword({ service: key });
} else {
forgetSecureStoreKey(key);
await SecureStore.deleteItemAsync(key);
Expand All @@ -224,7 +245,10 @@ export const StoragePluginScreen = () => {

return (
<Screen scroll={false}>
<PluginHeader title="Storage" subtitle="MMKV, AsyncStorage, and SecureStore adapters." />
<PluginHeader
title="Storage"
subtitle="MMKV, AsyncStorage, SecureStore, and Keychain adapters."
/>

<SegmentedTabs
accessibilityLabel="Storage adapter"
Expand All @@ -234,6 +258,7 @@ export const StoragePluginScreen = () => {
{ value: 'mmkv', label: 'MMKV' },
{ value: 'async', label: 'Async' },
{ value: 'secure', label: 'Secure' },
{ value: 'keychain', label: 'Keychain' },
]}
/>

Expand Down Expand Up @@ -297,7 +322,7 @@ export const StoragePluginScreen = () => {
label="Value"
helperText={
!supportsTypedValues
? 'AsyncStorage and SecureStore are string-only in this demo.'
? 'AsyncStorage, SecureStore, and Keychain are string-only in this demo.'
: undefined
}
>
Expand Down
5 changes: 5 additions & 0 deletions apps/playground/src/app/storage-plugin-adapters.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import AsyncStorage, { createAsyncStorage } from '@react-native-async-storage/async-storage';
import * as Keychain from 'react-native-keychain';
import * as SecureStore from 'expo-secure-store';
import {
createAsyncStorageAdapter,
createExpoSecureStorageAdapter,
createKeychainStorageAdapter,
createMMKVStorageAdapter,
} from '@rozenite/storage-plugin';
import { mmkvStorages } from './mmkv-storages';
Expand Down Expand Up @@ -64,4 +66,7 @@ export const storagePluginAdapters = [
storageId: 'secure-default',
storageName: 'Default SecureStore',
}),
createKeychainStorageAdapter({
keychain: Keychain,
}),
];
8 changes: 6 additions & 2 deletions packages/storage-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### A Rozenite plugin for inspecting multiple storage backends in React Native DevTools.

The Storage Plugin provides a single inspector for sync and async storages, including MMKV, AsyncStorage and Expo SecureStore via adapters.
The Storage Plugin provides a single inspector for sync and async storages, including MMKV, AsyncStorage, Expo SecureStore and react-native-keychain via adapters.

## Installation

Expand All @@ -13,7 +13,7 @@ npm install @rozenite/storage-plugin
Optional peers depending on adapters you use:

```bash
npm install react-native-mmkv @react-native-async-storage/async-storage expo-secure-store
npm install react-native-mmkv @react-native-async-storage/async-storage expo-secure-store react-native-keychain
```

## Usage
Expand All @@ -23,6 +23,7 @@ import {
createAsyncStorageAdapter,
createMMKVStorageAdapter,
createExpoSecureStorageAdapter,
createKeychainStorageAdapter,
useRozeniteStoragePlugin,
} from '@rozenite/storage-plugin';

Expand All @@ -41,6 +42,9 @@ const storages = [
storage: SecureStore,
keys: ['token', 'session'],
}),
createKeychainStorageAdapter({
keychain: Keychain,
}),
];

useRozeniteStoragePlugin({ storages });
Expand Down
4 changes: 4 additions & 0 deletions packages/storage-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"expo-secure-store": "*",
"react": "*",
"react-native": "*",
"react-native-keychain": "*",
"react-native-mmkv": "*"
},
"peerDependenciesMeta": {
Expand All @@ -85,6 +86,9 @@
"expo-secure-store": {
"optional": true
},
"react-native-keychain": {
"optional": true
},
"react-native-mmkv": {
"optional": true
}
Expand Down
14 changes: 14 additions & 0 deletions packages/storage-plugin/react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ export type {
CreateAsyncStorageAdapterOptions,
CreateExpoAsyncStorageAdapterOptions,
CreateExpoSecureStorageAdapterOptions,
CreateKeychainStorageAdapterOptions,
CreateMMKVStorageAdapterOptions,
KeychainLike,
SecureStorageLike,
} from './src/react-native/adapters';

Expand All @@ -22,6 +24,7 @@ export type {
export let createAsyncStorageAdapter: typeof import('./src/react-native/adapters').createAsyncStorageAdapter;
export let createExpoAsyncStorageAdapter: typeof import('./src/react-native/adapters').createExpoAsyncStorageAdapter;
export let createExpoSecureStorageAdapter: typeof import('./src/react-native/adapters').createExpoSecureStorageAdapter;
export let createKeychainStorageAdapter: typeof import('./src/react-native/adapters').createKeychainStorageAdapter;
export let createMMKVStorageAdapter: typeof import('./src/react-native/adapters').createMMKVStorageAdapter;
export let useRozeniteStoragePlugin: typeof import('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;

Expand All @@ -46,6 +49,13 @@ const createNoopStorageAdapter = (
storages: [],
});

const createNoopKeychainStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
createNoopStorageAdapter(
options,
'react-native-keychain',
'React Native Keychain',
)) as typeof createKeychainStorageAdapter;

if (!isDev || isServer) {
createAsyncStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
createNoopStorageAdapter(
Expand All @@ -60,6 +70,7 @@ if (!isDev || isServer) {
'expo-secure-store',
'Expo SecureStore',
)) as typeof createExpoSecureStorageAdapter;
createKeychainStorageAdapter = createNoopKeychainStorageAdapter;
createMMKVStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
createNoopStorageAdapter(options, 'mmkv', 'MMKV')) as typeof createMMKVStorageAdapter;
useRozeniteStoragePlugin = () => null;
Expand All @@ -70,6 +81,7 @@ if (!isDev || isServer) {
createAsyncStorageAdapter = asyncStorageModule.createAsyncStorageAdapter;
createExpoAsyncStorageAdapter = asyncStorageModule.createExpoAsyncStorageAdapter;
createExpoSecureStorageAdapter = secureStorageModule.createExpoSecureStorageAdapter;
createKeychainStorageAdapter = createNoopKeychainStorageAdapter;
createMMKVStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
createNoopStorageAdapter(options, 'mmkv', 'MMKV')) as typeof createMMKVStorageAdapter;
useRozeniteStoragePlugin =
Expand All @@ -80,6 +92,8 @@ if (!isDev || isServer) {
require('./src/react-native/adapters').createExpoAsyncStorageAdapter;
createExpoSecureStorageAdapter =
require('./src/react-native/adapters').createExpoSecureStorageAdapter;
createKeychainStorageAdapter =
require('./src/react-native/adapters').createKeychainStorageAdapter;
createMMKVStorageAdapter = require('./src/react-native/adapters').createMMKVStorageAdapter;
useRozeniteStoragePlugin =
require('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;
Expand Down
Loading
Loading