Skip to content

Commit f57482e

Browse files
committed
React Native SDK: protocol conformance, prebuilt walls, SHA-512 without WebCrypto
0 parents  commit f57482e

20 files changed

Lines changed: 10011 additions & 0 deletions

.github/workflows/ci.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-node@v4
14+
with: { node-version: 22 }
15+
- run: npm install
16+
- run: npm run typecheck
17+
# The golden vectors are the contract between every Ripstop SDK. Red here
18+
# means this SDK disagrees with the protocol, not the other way round.
19+
- run: npm test
20+
- run: npm run build

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
dist/
3+
*.log
4+
.DS_Store

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
## 0.1.0
4+
5+
First release.
6+
7+
- Force update, soft update, kill switch and maintenance mode, decided by the
8+
protocol's evaluation order and verified against the golden vectors.
9+
- Remote config values in the same signed payload — no extra request.
10+
- Ed25519 verification of the exact response bytes, with SHA-512 wired for
11+
runtimes without `crypto.subtle` — which is all of React Native.
12+
- Signed cache, re-verified on read. AsyncStorage adapter, no dependency on it.
13+
- `RipstopProvider` with prebuilt walls, or `useRipstop()` and draw your own.

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Ripstop
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# @ripstop/react-native
2+
3+
**Force update, kill switch, maintenance mode and remote config for React Native.**
4+
Decided at the edge, verified on device.
5+
6+
[![npm](https://img.shields.io/npm/v/@ripstop/react-native.svg)](https://www.npmjs.com/package/@ripstop/react-native)
7+
[![CI](https://github.com/ripstop-dev/ripstop-react-native/actions/workflows/ci.yaml/badge.svg)](https://github.com/ripstop-dev/ripstop-react-native/actions/workflows/ci.yaml)
8+
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9+
10+
## Install
11+
12+
```bash
13+
npm install @ripstop/react-native
14+
```
15+
16+
No native modules, no pod install, no config plugin. It is JavaScript.
17+
18+
## Quickstart
19+
20+
```tsx
21+
import { RipstopProvider } from '@ripstop/react-native';
22+
import AsyncStorage from '@react-native-async-storage/async-storage';
23+
import { asyncStorageAdapter } from '@ripstop/react-native';
24+
25+
export default function App() {
26+
return (
27+
<RipstopProvider
28+
apiKey="rs_pub_your_key"
29+
appVersion={DeviceInfo.getVersion()}
30+
storage={asyncStorageAdapter(AsyncStorage)}
31+
>
32+
<MyApp />
33+
</RipstopProvider>
34+
);
35+
}
36+
```
37+
38+
That's the whole integration. The provider renders your app until a decision
39+
says otherwise, then shows the right wall.
40+
41+
## Or draw your own
42+
43+
```tsx
44+
const { decision, values, snooze, loading } = useRipstop();
45+
46+
if (decision.type === 'force') return <MyUpdateScreen {...decision} />;
47+
```
48+
49+
Pass `walls={false}` to the provider and nothing is rendered for you.
50+
51+
## Storage
52+
53+
The cache is in-memory unless you give it somewhere to live. AsyncStorage is a
54+
separate package and the SDK will not add it to your app on your behalf — pass
55+
your existing instance through `asyncStorageAdapter` and the cache survives
56+
restarts.
57+
58+
## What it does when things break
59+
60+
| Situation | What your app does |
61+
| --- | --- |
62+
| No network | Uses the last **signed** payload |
63+
| No network, no cache | `none` — your app runs, unrestricted |
64+
| Server returns 500, or times out | Cache, then normal |
65+
| Signature doesn't verify | Discarded. A forged payload can never kill your app |
66+
| Cache tampered with | Re-verified on read, so it grants nothing |
67+
| Kill switch on, then network lost | The kill **stays**, until a fresh signed payload clears it |
68+
69+
## About `crypto.subtle`
70+
71+
React Native doesn't have it. `@noble/ed25519` backs its SHA-512 with WebCrypto
72+
by default, so a naive integration verifies fine in every test and throws on the
73+
first real device. This package wires SHA-512 explicitly from `@noble/hashes`,
74+
and has a test that deletes `crypto.subtle` before importing anything — because
75+
a test that runs on Node's fallback would pass whether or not the fix was there.
76+
77+
## Conformance
78+
79+
Every Ripstop SDK runs the same `vectors.json` — version ordering, evaluation
80+
order, snooze accounting, the fail-open state machine. `npm test` runs it here.
81+
82+
Full docs: **[ripstop.dev/docs/react-native](https://ripstop.dev/docs/react-native)**
83+
84+
## License
85+
86+
MIT

eslint.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import js from '@eslint/js';
2+
import tseslint from 'typescript-eslint';
3+
4+
export default tseslint.config(
5+
{ ignores: ['dist/**', 'node_modules/**'] },
6+
js.configs.recommended,
7+
...tseslint.configs.recommended,
8+
{ rules: { '@typescript-eslint/no-explicit-any': 'error' } },
9+
);

0 commit comments

Comments
 (0)