feat(mobile): crypto + secret-store + session-storage adapters

This commit is contained in:
byGalax
2026-05-13 23:55:07 +02:00
parent a275703ba6
commit 7ac93a95e6
3 changed files with 55 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
import * as s from 'react-native-libsodium';
import type { CryptoBackend } from '@chat-app/shared/crypto';
// react-native-libsodium re-exports libsodium-wrappers' API shape, so
// this adapter is the synchronous twin of the desktop one
// (`apps/desktop/src/lib/cryptoBackend.ts`). No WASM warm-up gate is
// needed — the native module is ready as soon as the module loads.
export function createLibsodiumBackend(): CryptoBackend {
return {
name: 'react-native-libsodium',
nonceLength: s.crypto_box_NONCEBYTES,
publicKeyLength: s.crypto_box_PUBLICKEYBYTES,
privateKeyLength: s.crypto_box_SECRETKEYBYTES,
secretboxKeyLength: s.crypto_secretbox_KEYBYTES,
secretboxNonceLength: s.crypto_secretbox_NONCEBYTES,
randomBytes: (n) => s.randombytes_buf(n),
generateKeyPair: () => {
const kp = s.crypto_box_keypair();
return { publicKey: kp.publicKey, privateKey: kp.privateKey };
},
box: (plaintext, nonce, recipientPublicKey, senderPrivateKey) =>
s.crypto_box_easy(plaintext, nonce, recipientPublicKey, senderPrivateKey),
boxOpen: (ciphertext, nonce, senderPublicKey, recipientPrivateKey) =>
s.crypto_box_open_easy(ciphertext, nonce, senderPublicKey, recipientPrivateKey),
secretbox: (plaintext, nonce, key) => s.crypto_secretbox_easy(plaintext, nonce, key),
secretboxOpen: (ciphertext, nonce, key) => s.crypto_secretbox_open_easy(ciphertext, nonce, key),
};
}
+20
View File
@@ -0,0 +1,20 @@
import { Buffer } from 'buffer';
import * as SecureStore from 'expo-secure-store';
import type { SecretStore } from '@chat-app/shared/auth';
// SecretStore contract uses Uint8Array values; SecureStore only takes
// strings, so we base64 at the boundary. iOS Keychain max value size
// is generous (a few MB); private keys are 32 bytes so we are well
// within limits.
export const secretStore: SecretStore = {
async getSecret(key) {
const v = await SecureStore.getItemAsync(key);
return v ? new Uint8Array(Buffer.from(v, 'base64')) : null;
},
async setSecret(key, value) {
await SecureStore.setItemAsync(key, Buffer.from(value).toString('base64'));
},
async removeSecret(key) {
await SecureStore.deleteItemAsync(key);
},
};
+7
View File
@@ -0,0 +1,7 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
// supabase-js v2 accepts any object with async getItem / setItem /
// removeItem returning Promise<string | null> / Promise<void>. RN's
// AsyncStorage matches that shape for shape; we just re-export it
// under a name that signals intent at the call site.
export const sessionStorage = AsyncStorage;