native-mobile reports the errors of NativePHP iOS and Android apps, including SuperNative screens and native crashes. It is a NativePHP plugin ("type": "nativephp-plugin", manifest in nativephp.json). The PHP side hooks native-core into the app; the native side catches crashes in Swift and...
php artisan boost:add-skill zinxan/native-mobile
Save this content to: AGENTS.md
# zinxan/native-mobile
`native-mobile` reports the errors of NativePHP iOS and Android apps, including SuperNative screens and native crashes. It is a NativePHP plugin (`"type": "nativephp-plugin"`, manifest in `nativephp.json`). The PHP side hooks `native-core` into the app; the native side catches crashes in Swift and Kotlin. Everything else comes from `native-core`. Read `packages/native-core/AGENTS.md` first.
| Path | What it is |
| --- | --- |
| `src/MobileServiceProvider.php` | Hooks into the persistent runtime and collects native crashes after the first response of a launch. |
| `src/MobileContext.php` | Reads the device, the OS and the app through the bridge. |
| `src/Bridge.php` | The only class that calls `nativephp_call()`. |
| `src/NativeCrashes.php` | Moves native crash reports into the outbox of `native-core`. |
| `resources/ios/Sources/` | Swift: the crash reporter (`init_function`) and the bridge functions. |
| `resources/android/src/` | Kotlin: the crash reporter (`init_function`) and the bridge functions. |
---
## 1. Rules
- Put a feature that desktop apps also need in `native-core`, not here.
- Call the bridge only through `Bridge`. NativePHP 4 defines a PHP Jump helper in
browser previews too. Function presence alone does not establish a device:
require the internal extension, the native runtime flag or an explicit Jump
relay. Native test fixtures must set `nativephp-internal.running=true`.
- Read the device once per process. `MobileContext` caches it, because each bridge call costs a round trip.
- Clear the scope between requests. NativePHP mobile keeps one PHP process alive (`Native\Mobile\Runtime`), so breadcrumbs and users leak into the next request without `Runtime::onReset()`. Keep the hook in `MobileServiceProvider::bootPlatform()`.
- Send the device name and the device ID only when `send_default_pii` is `true`.
- Treat the release `DEBUG` (the default version of a development build) as no release.
- Do not import a class of `nativephp/mobile` outside a `class_exists()` check. Declare each bridge symbol that Larastan needs in `stubs/nativephp.php`.
---
## 2. Native crashes
A crashing process can only write a file. Sending happens on the next launch, in PHP:
1. NativePHP calls the `init_function` of each platform at launch: `NativeWatchCrashReporter.install()` (Swift) and `installNativeWatchCrashReporter(context)` (Kotlin). Keep both idempotent.
2. On a crash, native code writes one JSON report in the shape of the ingest API to `nativewatch/crashes/<event_id>.json`: Application Support on iOS, `filesDir` on Android. Keep at most 20 reports.
3. After the first response of the next launch, `NativeCrashes::collect()` reads the reports through `NativeWatch.PendingCrashReports`, passes each to `Client::captureEvent()`, and deletes them through `NativeWatch.DeleteCrashReports`.
Sources of crashes:
- iOS: KSCrash owns Objective-C, Mach and signal capture and unwinding. NativeWatch admits fatal reports using preinitialized integer atomics, preserves the original consent interval and imports an allowlisted event after relaunch. Keep binary UUID, architecture, offset and instruction/return-address kind together. Use the exception-origin backtrace for NSException, not the later handler's stack. MetricKit adds delayed device diagnostics; only attach an architecture when its exact UUID is known.
- Keep the signal handler async-signal-safe: no allocation, no locks, no Swift strings or arrays, no Objective-C.
- Android: uncaught JVM exceptions through `Thread.setDefaultUncaughtExceptionHandler` (written at once, then the previous handler runs), and ANRs and native (NDK) crashes through `ApplicationExitInfo` (Android 11 and later) on the next launch.
Rules:
- Keep native frames stable between launches of one build. Use binary offsets, never absolute addresses: ASLR changes them on each launch and splits every crash into its own issue.
- Mark a frame `in_app` when it belongs to the app: the executable of the bundle on iOS, the package name of the app on Android.
- Add a bridge function to `nativephp.json`, Swift and Kotlin together. Give each Android bridge class `"android_params": ["context"]`.
- Never let crash reporting throw. Wrap native file work in `try?` (Swift) or `try` (Kotlin), and always run the previous handler.
- Compile the actual iOS example with its SwiftPM dependencies and verify a Release crash/relaunch through the receiver before finishing native changes. A standalone Swift stub cannot establish compatibility with the recorder.
- Validate the plugin from `examples/mobile`: `php artisan native:plugin:validate vendor/zinxan/native-mobile`.
- Upload dSYMs only from CI using `nativewatch:symbols` and the private CI token; read `docs/symbolication.md`. ProGuard/Android native mapping support is separate.
---
## 3. Tests
- Put the tests in `packages/native-mobile/tests/`. They run in the root suite.
- Use `tests/Fixtures/bridge.php` for a fake `nativephp_call()` and `tests/Fixtures/Runtime.php` for a fake runtime. Put pending crash reports in `$GLOBALS['nativewatch_bridge']['reports']` and read the calls of PHP from `$GLOBALS['nativewatch_bridge']['calls']`. Do not install `nativephp/mobile` in the root app.
- Remember that the fake `nativephp_call()` exists for the whole test process once a mobile test file loads. Do not write a test that needs the function to be missing.
- Try the SDK on a simulator with `examples/mobile`.
native-mobile reports the errors of NativePHP iOS and Android apps, including SuperNative screens and native crashes. It is a NativePHP plugin ("type": "nativephp-plugin", manifest in nativephp.json). The PHP side hooks native-core into the app; the native side catches crashes in Swift and Kotlin. Everything else comes from native-core. Read packages/native-core/AGENTS.md first.
| Path | What it is |
|---|---|
src/MobileServiceProvider.php |
Hooks into the persistent runtime and collects native crashes after the first response of a launch. |
src/MobileContext.php |
Reads the device, the OS and the app through the bridge. |
src/Bridge.php |
The only class that calls nativephp_call(). |
src/NativeCrashes.php |
Moves native crash reports into the outbox of native-core. |
resources/ios/Sources/ |
Swift: the crash reporter (init_function) and the bridge functions. |
resources/android/src/ |
Kotlin: the crash reporter (init_function) and the bridge functions. |
native-core, not here.Bridge. NativePHP 4 defines a PHP Jump helper in
browser previews too. Function presence alone does not establish a device:
require the internal extension, the native runtime flag or an explicit Jump
relay. Native test fixtures must set nativephp-internal.running=true.MobileContext caches it, because each bridge call costs a round trip.Native\Mobile\Runtime), so breadcrumbs and users leak into the next request without Runtime::onReset(). Keep the hook in MobileServiceProvider::bootPlatform().send_default_pii is true.DEBUG (the default version of a development build) as no release.nativephp/mobile outside a class_exists() check. Declare each bridge symbol that Larastan needs in stubs/nativephp.php.A crashing process can only write a file. Sending happens on the next launch, in PHP:
init_function of each platform at launch: NativeWatchCrashReporter.install() (Swift) and installNativeWatchCrashReporter(context) (Kotlin). Keep both idempotent.nativewatch/crashes/<event_id>.json: Application Support on iOS, filesDir on Android. Keep at most 20 reports.NativeCrashes::collect() reads the reports through NativeWatch.PendingCrashReports, passes each to Client::captureEvent(), and deletes them through NativeWatch.DeleteCrashReports.Sources of crashes:
Thread.setDefaultUncaughtExceptionHandler (written at once, then the previous handler runs), and ANRs and native (NDK) crashes through ApplicationExitInfo (Android 11 and later) on the next launch.Rules:
in_app when it belongs to the app: the executable of the bundle on iOS, the package name of the app on Android.nativephp.json, Swift and Kotlin together. Give each Android bridge class "android_params": ["context"].try? (Swift) or try (Kotlin), and always run the previous handler.examples/mobile: php artisan native:plugin:validate vendor/zinxan/native-mobile.nativewatch:symbols and the private CI token; read docs/symbolication.md. ProGuard/Android native mapping support is separate.packages/native-mobile/tests/. They run in the root suite.tests/Fixtures/bridge.php for a fake nativephp_call() and tests/Fixtures/Runtime.php for a fake runtime. Put pending crash reports in $GLOBALS['nativewatch_bridge']['reports'] and read the calls of PHP from $GLOBALS['nativewatch_bridge']['calls']. Do not install nativephp/mobile in the root app.nativephp_call() exists for the whole test process once a mobile test file loads. Do not write a test that needs the function to be missing.examples/mobile.How can I help you explore Laravel packages today?