Skip to content

Repository files navigation

Ripstop

Ripstop React Native SDK

CI License: MIT

Remote config, update walls and maintenance mode for React Native. Decided at the edge, verified on device.

Installation

@ripstop/react-native is not on npm yet. Until it is, install straight from GitHub. The package builds itself on install:

npm install github:ripstop-dev/ripstop-react-native

No native modules, no pod install, no config plugin. It is JavaScript.

Quick start

import AsyncStorage from '@react-native-async-storage/async-storage';
import DeviceInfo from 'react-native-device-info';
import { RipstopProvider, asyncStorageAdapter } from '@ripstop/react-native';

export default function App() {
  return (
    <RipstopProvider
      apiKey="rs_pub_your_key"
      appVersion={DeviceInfo.getVersion()}
      storage={asyncStorageAdapter(AsyncStorage)}
    >
      <MyApp />
    </RipstopProvider>
  );
}

That's the whole integration. The provider renders your app until a decision says otherwise, then shows the right wall.

Why you still pass appVersion

The other Ripstop SDKs read the installed version themselves, Swift from CFBundleShortVersionString and Flutter from package_info_plus. React Native cannot. Nothing in the framework exposes the host app's version: Platform.OS and Platform.Version describe the operating system, and Platform.constants.reactNativeVersion describes React Native. Reading the bundle means a native module, and this package has none. See the installation note above; that promise is worth more than saving you one argument.

So pass it, from whichever of these you already have:

import DeviceInfo from 'react-native-device-info';   // DeviceInfo.getVersion()
import Constants from 'expo-constants';              // Constants.expoConfig?.version

Pass the version only: 1.4.0, not 1.4.0 (312). Build numbers are not part of a semantic version, and the rules are compared as semantic versions.

Or draw your own

const { decision, values, snooze, loading } = useRipstop();

if (decision.type === 'force') return <MyUpdateScreen {...decision} />;

Pass walls={false} to the provider and nothing is rendered for you.

Storage

The cache is in-memory unless you give it somewhere to live. AsyncStorage is a separate package and the SDK will not add it to your app on your behalf. Pass your existing instance through asyncStorageAdapter and the cache survives restarts.

What it does when things break

Situation What your app does
No network Uses the last signed payload
No network, no cache none, so your app runs unrestricted
Server returns 500, or times out Cache, then normal
Signature doesn't verify Discarded. A forged payload can never wall your app
Cache tampered with Re-verified on read, so it grants nothing
Maintenance on, then network lost The wall stays, until a fresh signed payload clears it

About crypto.subtle

React Native doesn't have it. @noble/ed25519 backs its SHA-512 with WebCrypto by default, so a naive integration verifies fine in every test and throws on the first real device. This package wires SHA-512 explicitly from @noble/hashes, and has a test that deletes crypto.subtle before importing anything, because a test that runs on Node's fallback would pass whether or not the fix was there.

Conformance

Every Ripstop SDK runs the same vectors.json: version ordering, evaluation order, snooze accounting, the fail-open state machine. npm test runs it here.

Full docs: ripstop.dev/docs/react-native

License

MIT

About

Ripstop SDK for React Native. Required updates, update nudges, maintenance mode and remote config.

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages