-
Notifications
You must be signed in to change notification settings - Fork 7
feat: add on-demand ota updates, ota background worker #302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -147,6 +147,7 @@ dependencies { | |
|
|
||
| // Airborne SDK dependency | ||
| api "in.juspay:airborne:2.2.7-xota.02" | ||
|
|
||
| } | ||
|
|
||
| react { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| package `in`.juspay.airborneplugin | ||
|
|
||
| import android.os.Handler | ||
| import android.os.Looper | ||
| import com.facebook.react.bridge.ReactApplicationContext | ||
| import com.facebook.react.bridge.Promise | ||
|
|
||
|
|
@@ -68,4 +70,72 @@ class AirborneModuleImpl(private val reactContext: ReactApplicationContext) { | |
| promise.reject("AIRBORNE_ERROR", "Failed to get bundle path: ${e.message}", e) | ||
| } | ||
| } | ||
|
|
||
| fun checkForUpdate(namespace: String, promise: Promise) { | ||
| Thread { | ||
| try { | ||
| val airborne = Airborne.airborneObjectMap[namespace] | ||
| if (airborne == null) { | ||
| promise.reject("AIRBORNE_ERROR", "Airborne not initialized for namespace: $namespace") | ||
| return@Thread | ||
| } | ||
| val result = airborne.checkForUpdate() | ||
| promise.resolve(result) | ||
| } catch (e: Exception) { | ||
| promise.reject("AIRBORNE_ERROR", "Failed to check for update: ${e.message}", e) | ||
| } | ||
| }.start() | ||
| } | ||
|
|
||
| fun downloadUpdate(namespace: String, promise: Promise) { | ||
| try { | ||
| val airborne = Airborne.airborneObjectMap[namespace] | ||
| if (airborne == null) { | ||
| promise.reject("AIRBORNE_ERROR", "Airborne not initialized for namespace: $namespace") | ||
| return | ||
| } | ||
| airborne.downloadUpdate { success -> | ||
| Handler(Looper.getMainLooper()).post { | ||
| promise.resolve(success) | ||
| } | ||
| } | ||
| } catch (e: Exception) { | ||
| promise.reject("AIRBORNE_ERROR", "Failed to download update: ${e.message}", e) | ||
| } | ||
| } | ||
|
|
||
| fun startBackgroundDownload(namespace: String, promise: Promise) { | ||
| try { | ||
| Airborne.triggerBackgroundDownload(reactContext.applicationContext, namespace) | ||
| promise.resolve(true) | ||
| } catch (e: Exception) { | ||
| promise.reject("AIRBORNE_ERROR", "Failed to start background download: ${e.message}", e) | ||
| } | ||
| } | ||
|
Comment on lines
+107
to
+114
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mirror the namespace initialization guard in
🛠️ Suggested guard fun startBackgroundDownload(namespace: String, promise: Promise) {
try {
+ if (in.juspay.airborne.ota.OTADownloadWorker.managerMap[namespace] == null) {
+ promise.reject("AIRBORNE_ERROR", "Airborne not initialized for namespace: $namespace")
+ return
+ }
Airborne.triggerBackgroundDownload(reactContext.applicationContext, namespace)
promise.resolve(true)
} catch (e: Exception) {🤖 Prompt for AI Agents |
||
|
|
||
| fun reloadApp(namespace: String, promise: Promise) { | ||
| try { | ||
| promise.resolve(null) | ||
|
|
||
| Handler(Looper.getMainLooper()).postDelayed({ | ||
| try { | ||
| val context = reactContext.applicationContext | ||
| val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) | ||
| if (intent != null) { | ||
| intent.addFlags( | ||
| android.content.Intent.FLAG_ACTIVITY_NEW_TASK or | ||
| android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK | ||
| ) | ||
| context.startActivity(intent) | ||
| reactContext.currentActivity?.finishAffinity() | ||
| android.os.Process.killProcess(android.os.Process.myPid()) | ||
| } | ||
| } catch (e: Exception) { | ||
| android.util.Log.e("AirborneModuleImpl", "Failed to reload app", e) | ||
| } | ||
| }, 200) | ||
|
Comment on lines
+116
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate the relaunch path before resolving the promise. The method resolves immediately and only then looks up the launch intent / starts the activity inside the delayed runnable, so JS gets a successful result even when restart is impossible and the failure is only logged. Either precompute that path and reject early, or make this a fire-and-forget API instead of a resolved promise. 🤖 Prompt for AI Agents |
||
| } catch (e: Exception) { | ||
| promise.reject("AIRBORNE_ERROR", "Failed to reload app: ${e.message}", e) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,10 @@ export interface Spec extends TurboModule { | |
| readReleaseConfig(nameSpace: string): Promise<string>; | ||
| getFileContent(nameSpace: string, filePath: string): Promise<string>; | ||
| getBundlePath(nameSpace: string): Promise<string>; | ||
| checkForUpdate(nameSpace: string): Promise<string>; | ||
| downloadUpdate(nameSpace: string): Promise<boolean>; | ||
| startBackgroundDownload(nameSpace: string): Promise<boolean>; | ||
| reloadApp(nameSpace: string): Promise<void>; | ||
|
Comment on lines
+22
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== iOS Airborne bridge files =="
fd -i 'Airborne*' airborne-react-native/ios || true
echo
echo "== Search for newly added APIs on iOS =="
rg -n -C2 'checkForUpdate|downloadUpdate|startBackgroundDownload|reloadApp' airborne-react-native/ios || true
echo
echo "== Search for ObjC/Swift exported RN methods =="
rg -n -C2 'RCT_EXPORT_METHOD|RCT_REMAP_METHOD|@objc' airborne-react-native/ios || trueRepository: juspay/airborne Length of output: 1497 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Android Airborne bridge files =="
fd -i 'Airborne.*\.kt|Airborne.*\.java' airborne-react-native/android || true
echo
echo "== Search for newly added APIs in Android =="
rg -n 'checkForUpdate|downloadUpdate|startBackgroundDownload|reloadApp' airborne-react-native/android || true
echo
echo "== Count exported methods in Android vs iOS =="
echo "Android exports:"
rg '@ReactMethod' airborne-react-native/android | wc -l || echo "0"
echo "iOS exports:"
rg 'RCT_EXPORT_METHOD' airborne-react-native/ios | wc -l || echo "0"Repository: juspay/airborne Length of output: 5626 iOS bridge missing new TurboModule methods. These four methods are now mandatory in the shared spec (lines 22-25), but iOS exports none of them. Android has full implementation. Consumers will hit "method not found" runtime errors when calling 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| export default TurboModuleRegistry.getEnforcing<Spec>('Airborne'); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't replace the public mTLS hook with a TODO.
Commenting out
setSslConfig()removes the only obvious API clients have for custom TLS configuration. IfApplicationManagersupport is not ready yet, keep a stub here that fails loudly instead of silently dropping the method from the surface.🤖 Prompt for AI Agents