@fireota/react-native 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +235 -119
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,119 +1,235 @@
1
- # @fireota/react-native
2
-
3
- Over-the-air JavaScript updates for React Native, served from **your own Firebase project** by [FireOTA](https://www.npmjs.com/package/@fireota/cli).
4
-
5
- - SHA-256 verified downloads, straight from your Firebase Storage bucket
6
- - Staged rollouts, pause and rollback from the FireOTA dashboard
7
- - Automatic rollback when a new bundle crashes before it starts
8
- - Old updates are discarded when a new store build is installed
9
- - No third-party native dependencies
10
-
11
- > **Beta (0.x).** Verified on Android with React Native 0.87. iOS support is implemented but not yet device-tested.
12
-
13
- ## Install
14
-
15
- ```sh
16
- npm install @fireota/react-native
17
- npm install --save-dev @fireota/cli
18
- cd ios && pod install
19
- ```
20
-
21
- Requires a bare React Native app, 0.73 or newer (Expo: run `npx expo prebuild`).
22
-
23
- ## Android
24
-
25
- In `MainApplication.kt` (React Native 0.76+ template):
26
-
27
- ```kotlin
28
- import dev.fireota.reactnative.FireOTA
29
-
30
- override val reactHost: ReactHost by lazy {
31
- getDefaultReactHost(
32
- context = applicationContext,
33
- packageList = PackageList(this).packages,
34
- jsBundleFilePath = if (BuildConfig.DEBUG) null else FireOTA.getJSBundleFile(applicationContext),
35
- )
36
- }
37
- ```
38
-
39
- Older templates that override `reactNativeHost`:
40
-
41
- ```kotlin
42
- override fun getJSBundleFile(): String? =
43
- if (BuildConfig.DEBUG) super.getJSBundleFile()
44
- else FireOTA.getJSBundleFile(applicationContext) ?: super.getJSBundleFile()
45
- ```
46
-
47
- ## iOS
48
-
49
- ```objc
50
- // AppDelegate.mm
51
- #import <FireOTA.h>
52
-
53
- - (NSURL *)bundleURL
54
- {
55
- #if DEBUG
56
- return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
57
- #else
58
- return [FireOTA bundleURL] ?: [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
59
- #endif
60
- }
61
- ```
62
-
63
- Swift: add `#import <FireOTA.h>` to the bridging header and return `FireOTA.bundleURL() ?? Bundle.main.url(forResource: "main", withExtension: "jsbundle")` from `bundleURL()` in release builds.
64
-
65
- ## Configure
66
-
67
- Call once, early and unconditionally (e.g. `index.js`). Reaching this call confirms a new bundle starts; a bundle that never reaches it is rolled back on the next launch.
68
-
69
- ```js
70
- import FireOTA from "@fireota/react-native";
71
-
72
- if (!__DEV__) {
73
- FireOTA.configure({
74
- appId: "YOUR_APP_ID",
75
- serverUrl: "https://ota.example.com",
76
- runtimeVersion: "1.3.0", // must equal this store build; bump when native code changes
77
- channel: "production",
78
- });
79
- }
80
- ```
81
-
82
- By default updates download silently and apply on the next cold start; mandatory updates restart immediately. To show your own prompt, set `checkOnAppStart: false` and use the API:
83
-
84
- ```js
85
- const result = await FireOTA.checkForUpdate();
86
- if (result.updateAvailable) {
87
- // show your UI with result.release.version / result.release.notes, then:
88
- await FireOTA.downloadUpdate(result.release);
89
- await FireOTA.installUpdate(result.release, "immediate");
90
- }
91
- ```
92
-
93
- ## API
94
-
95
- | Method | Description |
96
- | --- | --- |
97
- | `configure(options)` | Initialize once at startup (required). |
98
- | `sync()` | Check, download and install, honouring install modes. |
99
- | `checkForUpdate()` | `{ updateAvailable, reason?, release? }` |
100
- | `downloadUpdate(release)` | Download and verify; the running bundle is untouched. |
101
- | `installUpdate(release, mode)` | `"nextRestart"` or `"immediate"`. |
102
- | `reload()` | Restart now to apply a pending update. |
103
- | `clearPendingUpdate()` | Drop a downloaded update that is not applied yet. |
104
- | `getCurrentRelease()` | `{ releaseId, pendingReleaseId, installationId }` |
105
- | `getUpdateStatus()` | Current status string. |
106
-
107
- Options: `appId`, `serverUrl`, `runtimeVersion` (required), `channel`, `checkOnAppStart`, `installMode`, `mandatoryInstallMode`, `onStatusChange(status, detail)`.
108
-
109
- ## Publishing updates
110
-
111
- Use [`@fireota/cli`](https://www.npmjs.com/package/@fireota/cli):
112
-
113
- ```sh
114
- npx fireota publish --app YOUR_APP_ID --platform android --version 1.3.1 --runtime 1.3.0 --channel production --rollout 10
115
- ```
116
-
117
- ## License
118
-
119
- MIT
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/Sujay-Dynamisch/FireOTA/main/public/logo-mark.png" alt="FireOTA" width="96" />
3
+ </p>
4
+
5
+ <h1 align="center">FireOTA</h1>
6
+
7
+ <p align="center">
8
+ Over-the-air updates for React Native — served from <strong>your own Firebase project</strong>.<br />
9
+ Staged rollouts, checksum verification, automatic rollback, audit logs and a CI-ready CLI.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/@fireota/react-native"><img alt="@fireota/react-native" src="https://img.shields.io/npm/v/@fireota/react-native?label=%40fireota%2Freact-native" /></a>
14
+ <a href="https://www.npmjs.com/package/@fireota/cli"><img alt="@fireota/cli" src="https://img.shields.io/npm/v/@fireota/cli?label=%40fireota%2Fcli" /></a>
15
+ <img alt="license" src="https://img.shields.io/badge/license-MIT-blue" />
16
+ </p>
17
+
18
+ ---
19
+
20
+ ## What it does
21
+
22
+ Ship JavaScript fixes and features to your React Native app **without a store review**:
23
+
24
+ 1. `npx fireota publish` bundles your JS (Hermes bytecode + images) and uploads it to **your** Firebase Storage bucket.
25
+ 2. FireOTA records the release — version, runtime, SHA-256, channel, rollout — and writes every action to an audit log.
26
+ 3. The SDK in your app checks for a compatible release on launch, downloads it **directly from your bucket**, verifies the checksum, and applies it on the next start.
27
+ 4. If the new bundle crashes before starting, the device rolls back to the previous bundle automatically and reports it.
28
+
29
+ You own the artifacts and pay Google directly for storage and bandwidth. FireOTA never proxies downloads.
30
+
31
+ ## Features
32
+
33
+ | | |
34
+ | --- | --- |
35
+ | **Releases** | Strict state machine (draft → ready → published → paused / rolled back / archived), atomic publish and rollback |
36
+ | **Rollouts** | Deterministic per-device buckets, 0–100 %, per-platform channel targets (Android and iOS independent) |
37
+ | **Safety** | Server-side SHA-256, exact runtime matching, native-version constraints, automatic pause on high failure rates |
38
+ | **Teams** | Organizations, roles (owner, admin, release manager, developer, viewer), Supabase Row Level Security |
39
+ | **Integrations** | Scoped API keys, CI/CD API, HMAC-signed webhooks with retries, notifications, analytics |
40
+ | **Firebase** | Google OAuth (PKCE) per customer project, encrypted refresh tokens, live Storage write verification |
41
+
42
+ ## Repository layout
43
+
44
+ ```text
45
+ src/ Web dashboard + API (TanStack Start, React, Tailwind)
46
+ routes/ Pages and HTTP routes (/api/public/v1/*, /health, /docs)
47
+ lib/backend/ Release engine, OTA resolver, Firebase adapter, RBAC, webhooks
48
+ lib/api/ Server functions used by the dashboard
49
+ components/docs/ Documentation site content (/docs)
50
+ supabase/migrations/ Database schema, RLS policies and transactional functions
51
+ packages/react-native/ @fireota/react-native — the app SDK (Kotlin + Objective-C)
52
+ packages/cli/ @fireota/cli — bundle, upload and publish from a terminal or CI
53
+ tests/ Integration, end-to-end and package-format tests
54
+ ```
55
+
56
+ ## Quick start (local)
57
+
58
+ **Prerequisites:** Node.js 20+, a [Supabase](https://supabase.com) project, a Google Cloud OAuth client.
59
+
60
+ ```sh
61
+ git clone https://github.com/Sujay-Dynamisch/FireOTA.git
62
+ cd FireOTA
63
+ npm install
64
+ ```
65
+
66
+ ### 1. Environment
67
+
68
+ Create `.env` in the project root (never commit it):
69
+
70
+ ```sh
71
+ # Supabase -> Project Settings -> API
72
+ SUPABASE_URL="https://<project-ref>.supabase.co"
73
+ SUPABASE_PUBLISHABLE_KEY="sb_publishable_..."
74
+ VITE_SUPABASE_URL="https://<project-ref>.supabase.co"
75
+ VITE_SUPABASE_PUBLISHABLE_KEY="sb_publishable_..."
76
+ SUPABASE_PROJECT_ID="<project-ref>"
77
+ VITE_SUPABASE_PROJECT_ID="<project-ref>"
78
+
79
+ # Server-only secrets
80
+ SUPABASE_SERVICE_ROLE_KEY="..." # Supabase -> API Keys -> Secret key (bypasses RLS)
81
+ FIREOTA_ENCRYPTION_KEY="..." # random; encrypts Google tokens and webhook secrets — never change it
82
+ FIREOTA_CRON_SECRET="..." # bearer token for the maintenance endpoint
83
+
84
+ # Google Cloud -> APIs & Services -> Credentials -> OAuth client (Web application)
85
+ GOOGLE_CLIENT_ID="..."
86
+ GOOGLE_CLIENT_SECRET="..."
87
+ GOOGLE_REDIRECT_URI="http://localhost:8080/api/public/v1/firebase/callback"
88
+ ```
89
+
90
+ Generate random secrets with:
91
+
92
+ ```sh
93
+ node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
94
+ ```
95
+
96
+ ### 2. Database
97
+
98
+ Apply every file in `supabase/migrations/` in order — with the Supabase CLI (`supabase link` then `supabase db push`) or by running them in the SQL editor.
99
+
100
+ ### 3. Auth and Google OAuth
101
+
102
+ - **Supabase → Authentication → URL Configuration:** Site URL `http://localhost:8080`; add redirect URLs `http://localhost:8080/dashboard`, `/onboarding`, `/reset-password`.
103
+ - **Supabase → Authentication → Providers → Google:** enable it with your OAuth client for "Continue with Google".
104
+ - **Google Cloud → OAuth client → Authorised redirect URIs:** add `http://localhost:8080/api/public/v1/firebase/callback` and `https://<project-ref>.supabase.co/auth/v1/callback`.
105
+
106
+ ### 4. Run
107
+
108
+ ```sh
109
+ npm run dev # http://localhost:8080
110
+ ```
111
+
112
+ Open `/signup`, create your workspace, connect a Firebase project (Cloud Storage must be set up) and register your app.
113
+
114
+ ## Deploy to Vercel
115
+
116
+ The repository includes [`vercel.json`](vercel.json). Nitro detects Vercel and builds a Node.js function plus static assets.
117
+
118
+ 1. Import the repository in Vercel (no framework preset needed).
119
+ 2. Add every variable from the `.env` section under **Project → Settings → Environment Variables**, with production URLs:
120
+ - `GOOGLE_REDIRECT_URI=https://<your-domain>/api/public/v1/firebase/callback`
121
+ - `CRON_SECRET=<random>` — Vercel sends it to the maintenance cron automatically.
122
+ 3. Deploy, then update the production URLs in Supabase Auth and in the Google OAuth client.
123
+ 4. Check `https://<your-domain>/health/ready` returns `{"status":"ok"}`.
124
+
125
+ The cron in `vercel.json` runs the maintenance job (webhook retries, OAuth state cleanup) daily, which works on the Hobby plan. On Pro, change the schedule to `*/5 * * * *` for faster webhook retries.
126
+
127
+ > **Upload size on Vercel:** Vercel Functions accept request bodies up to **4.5 MB**. Larger update packages must be published against a deployment without that limit (or a direct-to-Storage upload, on the roadmap).
128
+
129
+ ## Install
130
+
131
+ ```sh
132
+ npm install @fireota/react-native
133
+ npm install --save-dev @fireota/cli
134
+ cd ios && pod install
135
+ ```
136
+
137
+ Requires a bare React Native app, 0.73 or newer (Expo: run `npx expo prebuild`).
138
+
139
+ ## Android
140
+
141
+ In `MainApplication.kt` (React Native 0.76+ template):
142
+
143
+ ```kotlin
144
+ import dev.fireota.reactnative.FireOTA
145
+
146
+ override val reactHost: ReactHost by lazy {
147
+ getDefaultReactHost(
148
+ context = applicationContext,
149
+ packageList = PackageList(this).packages,
150
+ jsBundleFilePath = if (BuildConfig.DEBUG) null else FireOTA.getJSBundleFile(applicationContext),
151
+ )
152
+ }
153
+ ```
154
+
155
+ Older templates that override `reactNativeHost`:
156
+
157
+ ```kotlin
158
+ override fun getJSBundleFile(): String? =
159
+ if (BuildConfig.DEBUG) super.getJSBundleFile()
160
+ else FireOTA.getJSBundleFile(applicationContext) ?: super.getJSBundleFile()
161
+ ```
162
+
163
+ ## iOS
164
+
165
+ ```objc
166
+ // AppDelegate.mm
167
+ #import <FireOTA.h>
168
+
169
+ - (NSURL *)bundleURL
170
+ {
171
+ #if DEBUG
172
+ return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
173
+ #else
174
+ return [FireOTA bundleURL] ?: [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
175
+ #endif
176
+ }
177
+ ```
178
+
179
+ Swift: add `#import <FireOTA.h>` to the bridging header and return `FireOTA.bundleURL() ?? Bundle.main.url(forResource: "main", withExtension: "jsbundle")` from `bundleURL()` in release builds.
180
+
181
+ ## Configure
182
+
183
+ Call once, early and unconditionally (e.g. `index.js`). Reaching this call confirms a new bundle starts; a bundle that never reaches it is rolled back on the next launch.
184
+
185
+ ```js
186
+ import FireOTA from "@fireota/react-native";
187
+
188
+ if (!__DEV__) {
189
+ FireOTA.configure({
190
+ appId: "YOUR_APP_ID",
191
+ serverUrl: "https://ota.example.com",
192
+ runtimeVersion: "1.3.0", // must equal this store build; bump when native code changes
193
+ channel: "production",
194
+ });
195
+ }
196
+ ```
197
+
198
+ By default updates download silently and apply on the next cold start; mandatory updates restart immediately. To show your own prompt, set `checkOnAppStart: false` and use the API:
199
+
200
+ ```js
201
+ const result = await FireOTA.checkForUpdate();
202
+ if (result.updateAvailable) {
203
+ // show your UI with result.release.version / result.release.notes, then:
204
+ await FireOTA.downloadUpdate(result.release);
205
+ await FireOTA.installUpdate(result.release, "immediate");
206
+ }
207
+ ```
208
+
209
+ ## API
210
+
211
+ | Method | Description |
212
+ | --- | --- |
213
+ | `configure(options)` | Initialize once at startup (required). |
214
+ | `sync()` | Check, download and install, honouring install modes. |
215
+ | `checkForUpdate()` | `{ updateAvailable, reason?, release? }` |
216
+ | `downloadUpdate(release)` | Download and verify; the running bundle is untouched. |
217
+ | `installUpdate(release, mode)` | `"nextRestart"` or `"immediate"`. |
218
+ | `reload()` | Restart now to apply a pending update. |
219
+ | `clearPendingUpdate()` | Drop a downloaded update that is not applied yet. |
220
+ | `getCurrentRelease()` | `{ releaseId, pendingReleaseId, installationId }` |
221
+ | `getUpdateStatus()` | Current status string. |
222
+
223
+ Options: `appId`, `serverUrl`, `runtimeVersion` (required), `channel`, `checkOnAppStart`, `installMode`, `mandatoryInstallMode`, `onStatusChange(status, detail)`.
224
+
225
+ ## Publishing updates
226
+
227
+ Use [`@fireota/cli`](https://www.npmjs.com/package/@fireota/cli):
228
+
229
+ ```sh
230
+ npx fireota publish --app YOUR_APP_ID --platform android --version 1.3.1 --runtime 1.3.0 --channel production --rollout 10
231
+ ```
232
+
233
+ ## License
234
+
235
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fireota/react-native",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Over-the-air JavaScript updates for React Native apps, served from your own Firebase project by FireOTA.",
5
5
  "license": "MIT",
6
6
  "author": "FireOTA",