npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@foxtrotravi/backup-cloud-react-native

v2.0.0

Published

Production-grade cloud backup SDK for Expo/React Native wallet apps — Google Drive & iCloud providers

Readme

@foxtrotravi/backup-cloud-react-native

Production-grade cloud backup SDK for Expo / React Native wallet apps.
Stores an encrypted master key in Google Drive (appDataFolder) or iCloud via a clean provider abstraction.


Installation

npm install @foxtrotravi/backup-cloud-react-native
# iCloud support also requires:
npm install react-native-cloud-storage

Expo note: react-native-cloud-storage requires native modules. Use a custom dev build — it does NOT work with Expo Go.


Requirements

| Platform | Cloud Target | Requirement | |---|---|---| | iOS / macOS | iCloud | iCloud Drive enabled in Settings; user signed in | | Android / iOS | Google Drive | OAuth2 access token with drive.appdata scope |

This SDK performs NO OAuth flows. You must supply a valid token.


Quick Start

Google Drive

import {
  CloudBackup,
  GoogleDriveProvider,
  CloudAuthError,
  CloudUnavailableError,
  CloudStorageError,
  CloudValidationError,
} from '@foxtrotravi/backup-cloud-react-native';

// Token must be obtained by the caller (e.g., via expo-auth-session)
const provider = new GoogleDriveProvider({ accessToken: '<your_token>' });
const cloud = new CloudBackup(provider);

// Upload (metadata must include version; savedAt is auto-filled if omitted)
const result = await cloud.uploadEncryptedKey(encryptedKey, { version: 1 });

// Download — returns the full CloudEncryptionKeyFile or null
const backup = await cloud.downloadEncryptedKey(); // CloudEncryptionKeyFile | null
if (backup) {
  console.log(backup.encryptionKey, backup.version);
}

// Delete
await cloud.deleteBackup();

// Availability check
const available = await cloud.isAvailable(); // boolean

// Existence check (without downloading)
const hasBackup = await cloud.exists(); // boolean

iCloud

import {
  CloudBackup,
  ICloudProvider,
} from '@foxtrotravi/backup-cloud-react-native';

const provider = new ICloudProvider(); // default path: wallet_backup_key.json
const cloud = new CloudBackup(provider);

await cloud.uploadEncryptedKey(encryptedKey, { version: 1 });

Error Handling

import {
  CloudValidationError,
  CloudAuthError,
  CloudUnavailableError,
  CloudStorageError,
} from '@foxtrotravi/backup-cloud-react-native';

try {
  await cloud.uploadEncryptedKey(encryptedKey, { version: 1 });
} catch (err) {
  if (err instanceof CloudValidationError) {
    // Empty or invalid key passed by caller
  } else if (err instanceof CloudAuthError) {
    // Token expired / user not signed in to iCloud → refresh and retry
  } else if (err instanceof CloudUnavailableError) {
    // No network, iCloud disabled, Drive service down
  } else if (err instanceof CloudStorageError) {
    // Quota exceeded, I/O error, malformed response
  }
}

Each error carries a machine-readable code discriminant:

| Class | code | |---|---| | CloudValidationError | CLOUD_VALIDATION_ERROR | | CloudAuthError | CLOUD_AUTH_ERROR | | CloudUnavailableError | CLOUD_UNAVAILABLE | | CloudStorageError | CLOUD_STORAGE_ERROR |


API Reference

CloudBackup

new CloudBackup(provider: CloudProvider)

| Method | Signature | Description | |---|---|---| | uploadEncryptedKey | (key, metadata) => Promise<CloudEncryptionKeyFile \| null> | Validate + upload. Returns the written payload. Throws CloudValidationError on empty key. | | downloadEncryptedKey | () => Promise<CloudEncryptionKeyFile \| null> | Download full backup payload or null if no backup exists. | | deleteBackup | () => Promise<void> | Delete backup (idempotent). | | isAvailable | () => Promise<boolean> | Lightweight probe — never throws. | | exists | () => Promise<boolean> | Check if backup file exists without downloading — never throws. |


GoogleDriveProvider

new GoogleDriveProvider(config: GoogleDriveConfig)

interface GoogleDriveConfig {
  accessToken: string;    // OAuth2 token — drive.appdata scope required
  filePath?: string;      // default: "wallet_backup_key.json"
  cloudEmail?: string;    // stored inside the backup file for traceability
}
  • File stored in appDataFolder as wallet_backup_key.json (configurable via filePath)
  • Uses react-native-cloud-storage with CloudStorageScope.AppData
  • Verifies file existence after every write

ICloudProvider

new ICloudProvider(config?: ICloudConfig)

interface ICloudConfig {
  filePath?: string;      // default: "wallet_backup_key.json"
  cloudEmail?: string;    // stored inside the backup file for traceability
}
  • Requires react-native-cloud-storage peer dependency
  • Uses CloudStorageScope.AppData (app-specific hidden folder)
  • Checks iCloud availability before every operation
  • Verifies file existence after every write
  • Handles: iCloud not available, user not signed in, quota errors

Implementing a Custom Provider

import type { CloudProvider, CloudEncryptionKeyFile } from '@foxtrotravi/backup-cloud-react-native';

class MyCustomProvider implements CloudProvider {
  async upload(key: string, metadata: Record<string, unknown>): Promise<CloudEncryptionKeyFile | null> { /* ... */ }
  async download(): Promise<CloudEncryptionKeyFile | null> { /* ... */ }
  async delete(): Promise<void> { /* ... */ }
  async isAvailable(): Promise<boolean> { /* ... */ }
  async exists(): Promise<boolean> { /* ... */ }
}

const cloud = new CloudBackup(new MyCustomProvider());

Stored File Format

Both providers write the same JSON payload (CloudEncryptionKeyFile):

{
  "encryptionKey": "<encrypted_wallet_master_key>",
  "savedAt": "2026-02-25T00:00:00.000Z",
  "platform": "ios",
  "version": 1,
  "cloudEmail": "[email protected]"
}

| Field | Type | Description | |---|---|---| | encryptionKey | string | The encrypted wallet master key | | savedAt | string | ISO-8601 UTC timestamp when the backup was saved | | platform | "ios" \| "android" | Platform that created this backup | | version | number | Backup version number | | cloudEmail | string | Cloud user email that owns this backup |


Security Notes

  • Never logs the encrypted key or access token
  • No AsyncStorage — entirely in-request-lifecycle
  • No singletons — providers are stateless
  • No OAuth flows implemented — the caller owns credential management
  • Error messages strip sensitive values

Build

npm run build    # Outputs dist/ (CJS + ESM + .d.ts)
npm test         # Jest (90%+ coverage required)
npm run typecheck # tsc --noEmit

Publishing

First publish:

npm login
npm run build
npm publish --access public

Publishing updates:

# 1. Make your code changes
# 2. Bump version (choose one based on semver):
npm version patch   # 1.0.0 → 1.0.1 (bug fixes)
npm version minor   # 1.0.0 → 1.1.0 (new features, backward compatible)
npm version major   # 1.0.0 → 2.0.0 (breaking changes)

# 3. Build and publish
npm run build
npm publish --access public

Consumers using ^1.0.0 will receive patch/minor updates automatically on npm install.


Architecture

src/
  types.ts                    # CloudProvider interface + config types
  errors.ts                   # CloudUnavailableError, CloudAuthError,
  │                           #   CloudStorageError, CloudValidationError
  cloudBackup.ts              # Public CloudBackup wrapper
  providers/
    googleDriveProvider.ts    # react-native-cloud-storage (AppData scope)
    iCloudProvider.ts         # react-native-cloud-storage
  index.ts                    # Public barrel (named exports only)
  __tests__/
    errors.test.ts
    cloudBackup.test.ts
    googleDriveProvider.test.ts
    iCloudProvider.test.ts
  __mocks__/
    react-native-cloud-storage.ts
examples/
  usage.ts

Integration with @foxtrotravi/backup-backend

import { BackendBackupClient } from '@foxtrotravi/backup-backend';
import type { SeedItem, EntropyItem } from '@foxtrotravi/backup-backend';
import {
  CloudBackup,
  GoogleDriveProvider,
} from '@foxtrotravi/backup-cloud-react-native';

const backend = new BackendBackupClient({ baseUrl: 'https://api.mywallet.com' });
const cloud = new CloudBackup(new GoogleDriveProvider({ accessToken }));

// Upload (seed, entropy, and cloud key)
await backend.uploadSeed({ seed: encryptedSeed, authToken, metadata: { device: 'ios' } });
await backend.uploadEntropy({ entropy: encryptedEntropy, authToken });
await cloud.uploadEncryptedKey(encryptedMasterKey, { version: 1 });

// Retrieve — backend returns full arrays of SeedItem[] / EntropyItem[]
const seeds: SeedItem[] = await backend.getSeed(authToken);         // [] if no backup
const entropies: EntropyItem[] = await backend.getEntropy(authToken); // [] if no backup
const backup = await cloud.downloadEncryptedKey();                  // CloudEncryptionKeyFile | null

License

MIT