rn-app-exit 1.0.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 pixelcube
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.
package/README.md ADDED
@@ -0,0 +1,241 @@
1
+ # rn-app-exit
2
+
3
+ > Exit or background your React Native app — with full **New Architecture (TurboModules)** support, written in **Kotlin** and **Objective-C++**.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/rn-app-exit.svg)](https://www.npmjs.com/package/rn-app-exit)
6
+ [![npm downloads](https://img.shields.io/npm/dm/rn-app-exit.svg)](https://www.npmjs.com/package/rn-app-exit)
7
+ [![license](https://img.shields.io/npm/l/rn-app-exit.svg)](LICENSE)
8
+ [![platform](https://img.shields.io/badge/platform-android%20%7C%20ios-lightgrey.svg)](https://reactnative.dev)
9
+
10
+ ---
11
+
12
+ ## Why this package?
13
+
14
+ Most apps eventually need one of two things:
15
+
16
+ - **Hard exit** — a logout button, a kiosk reset, a session wipe that kills the process
17
+ - **Background** — a "minimize" button, a back-to-home UX without killing the process
18
+
19
+ The original `react-native-exit-app` only does the first, is written in Java/Objective-C, has no TurboModule support, and hasn't been maintained since 2021. This package does both, properly.
20
+
21
+ ---
22
+
23
+ ## Features
24
+
25
+ | | react-native-exit-app | **react-native-app-exit** |
26
+ |---|---|---|
27
+ | Exit app | ✅ | ✅ |
28
+ | Send to background | ❌ | ✅ |
29
+ | Unified API | ❌ | ✅ |
30
+ | Capability flags | ❌ | ✅ |
31
+ | New Architecture (TurboModules) | ❌ | ✅ |
32
+ | Old Architecture | ✅ | ✅ |
33
+ | Language | Java / ObjC | **Kotlin / ObjC++** |
34
+ | Active maintenance | ❌ (2021) | ✅ |
35
+ | Min React Native | 0.60 | 0.68 |
36
+
37
+ ---
38
+
39
+ ## Installation
40
+
41
+ ```sh
42
+ npm install rn-app-exit
43
+ ```
44
+
45
+ ### iOS
46
+
47
+ ```sh
48
+ cd ios && pod install
49
+ ```
50
+
51
+ ### Android
52
+
53
+ No additional steps — auto-linked via React Native's autolinking.
54
+
55
+ ---
56
+
57
+ ## Usage
58
+
59
+ ```tsx
60
+ import AppExit from 'rn-app-exit';
61
+ ```
62
+
63
+ ### Hard exit (kill the process)
64
+
65
+ ```tsx
66
+ AppExit.exitApp();
67
+ ```
68
+
69
+ Terminates the app process immediately. The app is removed from recents on Android. On iOS, use this only for non-App-Store builds (kiosks, enterprise, dev tooling) — Apple's HIG discourages `exit()` in consumer App Store apps.
70
+
71
+ ### Send to background (keep process alive)
72
+
73
+ ```tsx
74
+ AppExit.sendToBackground();
75
+ ```
76
+
77
+ Moves the app to the background without terminating. The process stays alive in memory — the user can resume from the app switcher exactly where they left off.
78
+
79
+ - **Android**: calls `moveTaskToBack(true)` — native OS feature, fully reliable
80
+ - **iOS**: suspends via `UIApplication suspend` selector — works across all current iOS versions
81
+
82
+ ### Unified API
83
+
84
+ ```tsx
85
+ // Hard exit (default)
86
+ AppExit.exit();
87
+
88
+ // Background instead of kill
89
+ AppExit.exit({ background: true });
90
+ ```
91
+
92
+ The `exit()` method is the recommended API for most use cases. Pass `{ background: true }` to move to background instead of killing.
93
+
94
+ ### Check capability at runtime
95
+
96
+ ```tsx
97
+ if (AppExit.isBackgroundSupported) {
98
+ // Android: always true
99
+ // iOS: false — backgrounding is OS-controlled
100
+ AppExit.sendToBackground();
101
+ } else {
102
+ AppExit.sendToBackground(); // still works on iOS via best-effort suspend
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## API Reference
109
+
110
+ ### `AppExit.exit(options?)`
111
+
112
+ | Parameter | Type | Default | Description |
113
+ |---|---|---|---|
114
+ | `options.background` | `boolean` | `false` | When `true`, sends to background instead of killing |
115
+
116
+ ### `AppExit.exitApp()`
117
+
118
+ Kills the process.
119
+
120
+ | Platform | Implementation |
121
+ |---|---|
122
+ | Android | `activity.finish()` + `Process.killProcess(myPid())` |
123
+ | iOS | `exit(0)` |
124
+
125
+ > **iOS warning:** Apple's App Store review guidelines discourage programmatic exit. Prefer `sendToBackground()` in consumer iOS apps.
126
+
127
+ ### `AppExit.sendToBackground()`
128
+
129
+ Moves the app to the background without terminating the process.
130
+
131
+ | Platform | Implementation |
132
+ |---|---|
133
+ | Android | `activity.moveTaskToBack(true)` |
134
+ | iOS | `UIApplication` suspend selector |
135
+
136
+ ### `AppExit.isBackgroundSupported`
137
+
138
+ `boolean` — `true` on Android, `false` on iOS.
139
+
140
+ On Android, `moveTaskToBack` is a first-class OS feature. On iOS, backgrounding is managed by the OS and there is no public API — the package uses a best-effort approach that works on current iOS versions but is not a documented public API.
141
+
142
+ ---
143
+
144
+ ## TypeScript
145
+
146
+ Full TypeScript support is included. The exported types are:
147
+
148
+ ```ts
149
+ type AppExitOptions = {
150
+ background?: boolean;
151
+ };
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Platform notes
157
+
158
+ ### Android
159
+
160
+ All three methods work as expected on Android API 21+. `sendToBackground()` behaves exactly like pressing the Home button — the task moves to the back stack and the process continues running.
161
+
162
+ ### iOS
163
+
164
+ Apple does not provide a public API for programmatic backgrounding or exit in App Store apps. This package provides:
165
+
166
+ - `exitApp()` via `exit(0)` — works, but risks App Store rejection for consumer apps. Safe for enterprise/kiosk/dev builds.
167
+ - `sendToBackground()` via `UIApplication` suspend — has been stable across iOS versions and does not trigger App Store rejection the way `exit()` can.
168
+
169
+ For App Store consumer apps, the recommended pattern is:
170
+
171
+ ```tsx
172
+ // Show a "goodbye" screen or navigate home, then suspend
173
+ AppExit.sendToBackground();
174
+ ```
175
+
176
+ ---
177
+
178
+ ## New Architecture
179
+
180
+ This package supports React Native's New Architecture (TurboModules) out of the box. The JavaScript spec in `src/NativeAppExit.ts` is used by React Native's codegen to generate native bindings automatically.
181
+
182
+ No additional configuration is needed. The package detects the active architecture at build time and uses the appropriate native implementation.
183
+
184
+ ---
185
+
186
+ ## Common use cases
187
+
188
+ **Logout button that wipes state and exits:**
189
+ ```tsx
190
+ async function handleLogout() {
191
+ await clearUserSession();
192
+ AppExit.exitApp();
193
+ }
194
+ ```
195
+
196
+ **Kiosk reset button:**
197
+ ```tsx
198
+ function KioskResetButton() {
199
+ return (
200
+ <Pressable onPress={() => AppExit.exit()}>
201
+ <Text>Reset Kiosk</Text>
202
+ </Pressable>
203
+ );
204
+ }
205
+ ```
206
+
207
+ **Minimize button (Android UX pattern):**
208
+ ```tsx
209
+ function MinimizeButton() {
210
+ return (
211
+ <Pressable onPress={() => AppExit.sendToBackground()}>
212
+ <Text>Go to Home</Text>
213
+ </Pressable>
214
+ );
215
+ }
216
+ ```
217
+
218
+ **Hardware back button on Android to background instead of exit:**
219
+ ```tsx
220
+ import { BackHandler } from 'react-native';
221
+
222
+ useEffect(() => {
223
+ const sub = BackHandler.addEventListener('hardwareBackPress', () => {
224
+ AppExit.sendToBackground();
225
+ return true; // prevent default back behavior
226
+ });
227
+ return () => sub.remove();
228
+ }, []);
229
+ ```
230
+
231
+ ---
232
+
233
+ ## License
234
+
235
+ MIT © 2025 pixelcube
236
+
237
+ ---
238
+
239
+ ## Contributing
240
+
241
+ Issues and PRs welcome at [github.com/pixelcube/rn-app-exit](https://github.com/pixelcube/rn-app-exit).
@@ -0,0 +1,65 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+
7
+ dependencies {
8
+ classpath "com.android.tools.build:gradle:8.1.0"
9
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24"
10
+ }
11
+ }
12
+
13
+ def isNewArchitectureEnabled() {
14
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
15
+ }
16
+
17
+ apply plugin: "com.android.library"
18
+ apply plugin: "kotlin-android"
19
+
20
+ if (isNewArchitectureEnabled()) {
21
+ apply plugin: "com.facebook.react"
22
+ }
23
+
24
+ android {
25
+ compileSdkVersion 34
26
+ namespace "com.appexitlib"
27
+
28
+ defaultConfig {
29
+ minSdkVersion 21
30
+ targetSdkVersion 34
31
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
32
+ }
33
+
34
+ buildFeatures {
35
+ buildConfig true
36
+ }
37
+
38
+ compileOptions {
39
+ sourceCompatibility JavaVersion.VERSION_17
40
+ targetCompatibility JavaVersion.VERSION_17
41
+ }
42
+
43
+ kotlinOptions {
44
+ jvmTarget = "17"
45
+ }
46
+
47
+ sourceSets {
48
+ main {
49
+ if (isNewArchitectureEnabled()) {
50
+ java.srcDirs += ["src/newarch"]
51
+ } else {
52
+ java.srcDirs += ["src/oldarch"]
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ repositories {
59
+ mavenCentral()
60
+ google()
61
+ }
62
+
63
+ dependencies {
64
+ implementation "com.facebook.react:react-android"
65
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,32 @@
1
+ package com.appexitlib
2
+
3
+ import android.os.Process
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.module.annotations.ReactModule
6
+
7
+ @ReactModule(name = AppExitModule.NAME)
8
+ class AppExitModule(reactContext: ReactApplicationContext) : NativeAppExitSpec(reactContext) {
9
+
10
+ override fun getName(): String = NAME
11
+
12
+ override fun getConstants(): Map<String, Any> = mapOf(
13
+ "isBackgroundSupported" to true
14
+ )
15
+
16
+ override fun exitApp() {
17
+ // Finish the current activity first so the OS doesn't relaunch it,
18
+ // then kill the process to ensure a clean exit on all Android versions.
19
+ currentActivity?.finish()
20
+ Process.killProcess(Process.myPid())
21
+ }
22
+
23
+ override fun sendToBackground() {
24
+ // moveTaskToBack moves the entire task (all activities in the back stack)
25
+ // to the background. The process lives on — the user can resume from recents.
26
+ currentActivity?.moveTaskToBack(true)
27
+ }
28
+
29
+ companion object {
30
+ const val NAME = "AppExit"
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ package com.appexitlib
2
+
3
+ import com.facebook.react.TurboReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+
9
+ // TurboReactPackage works for both Old and New Architecture:
10
+ // - Old arch: uses the standard module registry
11
+ // - New arch: provides TurboModule instances via JSI
12
+ class AppExitPackage : TurboReactPackage() {
13
+
14
+ override fun getModule(
15
+ name: String,
16
+ reactContext: ReactApplicationContext,
17
+ ): NativeModule? =
18
+ if (name == AppExitModule.NAME) AppExitModule(reactContext) else null
19
+
20
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider {
21
+ mapOf(
22
+ AppExitModule.NAME to ReactModuleInfo(
23
+ _name = AppExitModule.NAME,
24
+ _className = AppExitModule.NAME,
25
+ _canOverrideExistingModule = false,
26
+ _needsEagerInit = false,
27
+ isCxxModule = false,
28
+ isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
29
+ )
30
+ )
31
+ }
32
+ }
@@ -0,0 +1,9 @@
1
+ package com.appexitlib
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+
5
+ // New Architecture: extends the codegen-generated TurboModule spec.
6
+ // The codegen reads src/NativeAppExit.ts and produces RNAppExitSpec.
7
+ // This class bridges our module to that generated spec.
8
+ abstract class NativeAppExitSpec(context: ReactApplicationContext) :
9
+ com.appexitlib.NativeRNAppExitSpec(context)
@@ -0,0 +1,14 @@
1
+ package com.appexitlib
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
+
6
+ // Old Architecture: a plain ReactContextBaseJavaModule with the same abstract
7
+ // interface so AppExitModule compiles identically in both architectures.
8
+ abstract class NativeAppExitSpec(context: ReactApplicationContext) :
9
+ ReactContextBaseJavaModule(context) {
10
+
11
+ abstract fun exitApp()
12
+ abstract fun sendToBackground()
13
+ abstract override fun getConstants(): Map<String, Any>?
14
+ }
package/ios/AppExit.h ADDED
@@ -0,0 +1,5 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface AppExit : NSObject <RCTBridgeModule>
4
+
5
+ @end
package/ios/AppExit.mm ADDED
@@ -0,0 +1,45 @@
1
+ #import "AppExit.h"
2
+ #import <UIKit/UIKit.h>
3
+
4
+ @implementation AppExit
5
+
6
+ RCT_EXPORT_MODULE(AppExit)
7
+
8
+ // Expose compile-time constants to JS so callers can branch without platform checks.
9
+ - (NSDictionary *)constantsToExport {
10
+ return @{
11
+ // iOS does not have a public OS-level background API, so we surface this as false.
12
+ // sendToBackground uses a best-effort private UIApplication selector that works
13
+ // on current iOS versions but is not App Store guaranteed.
14
+ @"isBackgroundSupported": @NO
15
+ };
16
+ }
17
+
18
+ + (BOOL)requiresMainQueueSetup {
19
+ return NO;
20
+ }
21
+
22
+ // Terminate the process.
23
+ // Apple's HIG recommends against calling exit() in App Store apps — iOS is designed
24
+ // to handle app lifecycle automatically. Prefer sendToBackground() in production.
25
+ // This exists for developer tooling, kiosks, or enterprise apps where hard exit is valid.
26
+ RCT_EXPORT_METHOD(exitApp) {
27
+ dispatch_async(dispatch_get_main_queue(), ^{
28
+ exit(0);
29
+ });
30
+ }
31
+
32
+ // Suspend the app by sending it to the background.
33
+ // Uses UIApplication's "suspend" selector, which is the same action triggered
34
+ // when the user presses the Home button. This is not a documented public API,
35
+ // but has been stable across iOS versions and doesn't risk App Store rejection
36
+ // the same way exit() does.
37
+ RCT_EXPORT_METHOD(sendToBackground) {
38
+ dispatch_async(dispatch_get_main_queue(), ^{
39
+ UIApplication *app = [UIApplication sharedApplication];
40
+ [app performSelector:@selector(suspend)];
41
+ });
42
+ }
43
+
44
+
45
+ @end
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _reactNative = require("react-native");
8
+ var _default = exports.default = _reactNative.TurboModuleRegistry.getEnforcing('AppExit');
9
+ //# sourceMappingURL=NativeAppExit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeAppExit.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GA6BpCC,gCAAmB,CAACC,YAAY,CAAO,SAAS,CAAC","ignoreList":[]}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = exports.AppExit = void 0;
7
+ var _reactNative = require("react-native");
8
+ var _NativeAppExit = _interopRequireDefault(require("./NativeAppExit.js"));
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ const constants = _NativeAppExit.default.getConstants();
11
+ const AppExit = exports.AppExit = {
12
+ /**
13
+ * True on Android where moveTaskToBack is a first-class OS feature.
14
+ * False on iOS — Apple controls backgrounding; there is no public API.
15
+ */
16
+ isBackgroundSupported: constants.isBackgroundSupported,
17
+ /**
18
+ * Exit or background the app.
19
+ *
20
+ * @example
21
+ * // Hard exit
22
+ * AppExit.exit();
23
+ *
24
+ * @example
25
+ * // Send to background instead of killing
26
+ * AppExit.exit({ background: true });
27
+ */
28
+ exit(options = {}) {
29
+ if (options.background) {
30
+ AppExit.sendToBackground();
31
+ } else {
32
+ AppExit.exitApp();
33
+ }
34
+ },
35
+ /**
36
+ * Terminate the app process immediately.
37
+ *
38
+ * iOS warning: Apple guidelines discourage calling exit() from production
39
+ * apps distributed via the App Store. Prefer sendToBackground() on iOS.
40
+ */
41
+ exitApp() {
42
+ _NativeAppExit.default.exitApp();
43
+ },
44
+ /**
45
+ * Move the app to the background without terminating the process.
46
+ *
47
+ * Android: moveTaskToBack(true) — native and reliable.
48
+ * iOS: best-effort suspend via UIApplication private selector.
49
+ * Not guaranteed to work across all iOS versions.
50
+ */
51
+ sendToBackground() {
52
+ if (_reactNative.Platform.OS === 'android' || _reactNative.Platform.OS === 'ios') {
53
+ _NativeAppExit.default.sendToBackground();
54
+ }
55
+ }
56
+ };
57
+ var _default = exports.default = AppExit;
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["_reactNative","require","_NativeAppExit","_interopRequireDefault","e","__esModule","default","constants","NativeAppExit","getConstants","AppExit","exports","isBackgroundSupported","exit","options","background","sendToBackground","exitApp","Platform","OS","_default"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAC,sBAAA,CAAAF,OAAA;AAA4C,SAAAE,uBAAAC,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAW5C,MAAMG,SAAS,GAAGC,sBAAa,CAACC,YAAY,CAAC,CAAC;AAE9C,MAAMC,OAAO,GAAAC,OAAA,CAAAD,OAAA,GAAG;EACd;AACF;AACA;AACA;EACEE,qBAAqB,EAAEL,SAAS,CAACK,qBAAqB;EAEtD;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,IAAIA,CAACC,OAAuB,GAAG,CAAC,CAAC,EAAQ;IACvC,IAAIA,OAAO,CAACC,UAAU,EAAE;MACtBL,OAAO,CAACM,gBAAgB,CAAC,CAAC;IAC5B,CAAC,MAAM;MACLN,OAAO,CAACO,OAAO,CAAC,CAAC;IACnB;EACF,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;EACEA,OAAOA,CAAA,EAAS;IACdT,sBAAa,CAACS,OAAO,CAAC,CAAC;EACzB,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;AACA;EACED,gBAAgBA,CAAA,EAAS;IACvB,IAAIE,qBAAQ,CAACC,EAAE,KAAK,SAAS,IAAID,qBAAQ,CAACC,EAAE,KAAK,KAAK,EAAE;MACtDX,sBAAa,CAACQ,gBAAgB,CAAC,CAAC;IAClC;EACF;AACF,CAAC;AAAC,IAAAI,QAAA,GAAAT,OAAA,CAAAL,OAAA,GAGaI,OAAO","ignoreList":[]}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+
3
+ import { TurboModuleRegistry } from 'react-native';
4
+ export default TurboModuleRegistry.getEnforcing('AppExit');
5
+ //# sourceMappingURL=NativeAppExit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeAppExit.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AA6BlD,eAAeA,mBAAmB,CAACC,YAAY,CAAO,SAAS,CAAC","ignoreList":[]}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+
3
+ import { Platform } from 'react-native';
4
+ import NativeAppExit from "./NativeAppExit.js";
5
+ const constants = NativeAppExit.getConstants();
6
+ const AppExit = {
7
+ /**
8
+ * True on Android where moveTaskToBack is a first-class OS feature.
9
+ * False on iOS — Apple controls backgrounding; there is no public API.
10
+ */
11
+ isBackgroundSupported: constants.isBackgroundSupported,
12
+ /**
13
+ * Exit or background the app.
14
+ *
15
+ * @example
16
+ * // Hard exit
17
+ * AppExit.exit();
18
+ *
19
+ * @example
20
+ * // Send to background instead of killing
21
+ * AppExit.exit({ background: true });
22
+ */
23
+ exit(options = {}) {
24
+ if (options.background) {
25
+ AppExit.sendToBackground();
26
+ } else {
27
+ AppExit.exitApp();
28
+ }
29
+ },
30
+ /**
31
+ * Terminate the app process immediately.
32
+ *
33
+ * iOS warning: Apple guidelines discourage calling exit() from production
34
+ * apps distributed via the App Store. Prefer sendToBackground() on iOS.
35
+ */
36
+ exitApp() {
37
+ NativeAppExit.exitApp();
38
+ },
39
+ /**
40
+ * Move the app to the background without terminating the process.
41
+ *
42
+ * Android: moveTaskToBack(true) — native and reliable.
43
+ * iOS: best-effort suspend via UIApplication private selector.
44
+ * Not guaranteed to work across all iOS versions.
45
+ */
46
+ sendToBackground() {
47
+ if (Platform.OS === 'android' || Platform.OS === 'ios') {
48
+ NativeAppExit.sendToBackground();
49
+ }
50
+ }
51
+ };
52
+ export { AppExit };
53
+ export default AppExit;
54
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["Platform","NativeAppExit","constants","getConstants","AppExit","isBackgroundSupported","exit","options","background","sendToBackground","exitApp","OS"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,OAAOC,aAAa,MAAM,oBAAiB;AAW3C,MAAMC,SAAS,GAAGD,aAAa,CAACE,YAAY,CAAC,CAAC;AAE9C,MAAMC,OAAO,GAAG;EACd;AACF;AACA;AACA;EACEC,qBAAqB,EAAEH,SAAS,CAACG,qBAAqB;EAEtD;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACEC,IAAIA,CAACC,OAAuB,GAAG,CAAC,CAAC,EAAQ;IACvC,IAAIA,OAAO,CAACC,UAAU,EAAE;MACtBJ,OAAO,CAACK,gBAAgB,CAAC,CAAC;IAC5B,CAAC,MAAM;MACLL,OAAO,CAACM,OAAO,CAAC,CAAC;IACnB;EACF,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;EACEA,OAAOA,CAAA,EAAS;IACdT,aAAa,CAACS,OAAO,CAAC,CAAC;EACzB,CAAC;EAED;AACF;AACA;AACA;AACA;AACA;AACA;EACED,gBAAgBA,CAAA,EAAS;IACvB,IAAIT,QAAQ,CAACW,EAAE,KAAK,SAAS,IAAIX,QAAQ,CAACW,EAAE,KAAK,KAAK,EAAE;MACtDV,aAAa,CAACQ,gBAAgB,CAAC,CAAC;IAClC;EACF;AACF,CAAC;AAED,SAASL,OAAO;AAChB,eAAeA,OAAO","ignoreList":[]}
@@ -0,0 +1,28 @@
1
+ import type { TurboModule } from 'react-native';
2
+ export interface Spec extends TurboModule {
3
+ /**
4
+ * Terminates the application process.
5
+ * Android: kills the process via Process.killProcess.
6
+ * iOS: calls exit(0). Note: Apple discourages this — avoid calling on iOS
7
+ * if your app is on the App Store; prefer sendToBackground instead.
8
+ */
9
+ exitApp(): void;
10
+ /**
11
+ * Moves the app to the background without terminating the process.
12
+ * Android: moveTaskToBack(true) — fully supported.
13
+ * iOS: suspends the app via the UIApplication suspend selector — best effort.
14
+ */
15
+ sendToBackground(): void;
16
+ /**
17
+ * Returns compile-time constants about platform capabilities.
18
+ */
19
+ getConstants(): {
20
+ /**
21
+ * True on Android. iOS returns false because backgrounding is OS-controlled.
22
+ */
23
+ isBackgroundSupported: boolean;
24
+ };
25
+ }
26
+ declare const _default: Spec;
27
+ export default _default;
28
+ //# sourceMappingURL=NativeAppExit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeAppExit.d.ts","sourceRoot":"","sources":["../../../src/NativeAppExit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC;;;;;OAKG;IACH,OAAO,IAAI,IAAI,CAAC;IAEhB;;;;OAIG;IACH,gBAAgB,IAAI,IAAI,CAAC;IAEzB;;OAEG;IACH,YAAY,IAAI;QACd;;WAEG;QACH,qBAAqB,EAAE,OAAO,CAAC;KAChC,CAAC;CACH;;AAED,wBAAiE"}
@@ -0,0 +1,45 @@
1
+ export type AppExitOptions = {
2
+ /**
3
+ * When true, moves the app to background instead of terminating.
4
+ * On iOS this suspends the app; on Android it calls moveTaskToBack.
5
+ * Defaults to false (full exit).
6
+ */
7
+ background?: boolean;
8
+ };
9
+ declare const AppExit: {
10
+ /**
11
+ * True on Android where moveTaskToBack is a first-class OS feature.
12
+ * False on iOS — Apple controls backgrounding; there is no public API.
13
+ */
14
+ isBackgroundSupported: boolean;
15
+ /**
16
+ * Exit or background the app.
17
+ *
18
+ * @example
19
+ * // Hard exit
20
+ * AppExit.exit();
21
+ *
22
+ * @example
23
+ * // Send to background instead of killing
24
+ * AppExit.exit({ background: true });
25
+ */
26
+ exit(options?: AppExitOptions): void;
27
+ /**
28
+ * Terminate the app process immediately.
29
+ *
30
+ * iOS warning: Apple guidelines discourage calling exit() from production
31
+ * apps distributed via the App Store. Prefer sendToBackground() on iOS.
32
+ */
33
+ exitApp(): void;
34
+ /**
35
+ * Move the app to the background without terminating the process.
36
+ *
37
+ * Android: moveTaskToBack(true) — native and reliable.
38
+ * iOS: best-effort suspend via UIApplication private selector.
39
+ * Not guaranteed to work across all iOS versions.
40
+ */
41
+ sendToBackground(): void;
42
+ };
43
+ export { AppExit };
44
+ export default AppExit;
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAIF,QAAA,MAAM,OAAO;IACX;;;OAGG;;IAGH;;;;;;;;;;OAUG;mBACW,cAAc,GAAQ,IAAI;IAQxC;;;;;OAKG;eACQ,IAAI;IAIf;;;;;;OAMG;wBACiB,IAAI;CAKzB,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,CAAC;AACnB,eAAe,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "rn-app-exit",
3
+ "version": "1.0.0",
4
+ "description": "Exit or background your React Native app — supports New Architecture (TurboModules) and Old Architecture with Kotlin/Swift native implementations.",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/src/index.d.ts",
8
+ "react-native": "src/index.ts",
9
+ "source": "src/index.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./lib/module/index.js",
13
+ "require": "./lib/commonjs/index.js",
14
+ "types": "./lib/typescript/src/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "src",
19
+ "lib",
20
+ "android",
21
+ "ios",
22
+ "cpp",
23
+ "*.podspec",
24
+ "LICENSE",
25
+ "!ios/build",
26
+ "!android/build",
27
+ "!android/.gradle",
28
+ "!**/__tests__",
29
+ "!**/__fixtures__",
30
+ "!**/__mocks__"
31
+ ],
32
+ "scripts": {
33
+ "build": "bob build",
34
+ "typecheck": "tsc --noEmit",
35
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
36
+ "prepare": "bob build",
37
+ "test": "jest"
38
+ },
39
+ "keywords": [
40
+ "react-native",
41
+ "ios",
42
+ "exit",
43
+ "quit",
44
+ "android",
45
+ "exit-app",
46
+ "background",
47
+ "turbomodule",
48
+ "new-architecture"
49
+ ],
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/pixelcube/rn-app-exit.git"
53
+ },
54
+ "author": "pixelcube",
55
+ "license": "MIT",
56
+ "bugs": {
57
+ "url": "https://github.com/pixelcube/rn-app-exit/issues"
58
+ },
59
+ "homepage": "https://github.com/pixelcube/rn-app-exit#readme",
60
+ "publishConfig": {
61
+ "registry": "https://registry.npmjs.org/"
62
+ },
63
+ "devDependencies": {
64
+ "@react-native/babel-preset": "^0.76.0",
65
+ "@react-native/eslint-config": "^0.76.0",
66
+ "@types/jest": "^29.5.12",
67
+ "@types/react": "^18.2.6",
68
+ "eslint": "^8.51.0",
69
+ "jest": "^29.7.0",
70
+ "react": "18.3.1",
71
+ "react-native": "0.76.0",
72
+ "react-native-builder-bob": "^0.30.0",
73
+ "typescript": "^5.0.2"
74
+ },
75
+ "peerDependencies": {
76
+ "react": "*",
77
+ "react-native": "*"
78
+ },
79
+ "workspaces": [
80
+ "example"
81
+ ],
82
+ "jest": {
83
+ "preset": "react-native",
84
+ "modulePathIgnorePatterns": [
85
+ "<rootDir>/example/node_modules",
86
+ "<rootDir>/lib/"
87
+ ]
88
+ },
89
+ "codegenConfig": {
90
+ "name": "RNAppExitSpec",
91
+ "type": "modules",
92
+ "jsSrcsDir": "src",
93
+ "android": {
94
+ "javaPackageName": "com.appexitlib"
95
+ }
96
+ },
97
+ "react-native-builder-bob": {
98
+ "source": "src",
99
+ "output": "lib",
100
+ "targets": [
101
+ [
102
+ "commonjs",
103
+ {
104
+ "esm": true
105
+ }
106
+ ],
107
+ [
108
+ "module",
109
+ {
110
+ "esm": true
111
+ }
112
+ ],
113
+ [
114
+ "typescript",
115
+ {
116
+ "project": "tsconfig.build.json"
117
+ }
118
+ ]
119
+ ]
120
+ }
121
+ }
@@ -0,0 +1,35 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "rn-app-exit"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => "13.0" }
14
+ s.source = { :git => package["repository"]["url"], :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm}"
17
+
18
+ # New Architecture (TurboModules)
19
+ if ENV["RCT_NEW_ARCH_ENABLED"] == "1"
20
+ s.compiler_flags = "-DRCT_NEW_ARCH_ENABLED=1"
21
+ s.pod_target_xcconfig = {
22
+ "DEFINES_MODULE" => "YES",
23
+ "SWIFT_COMPILATION_MODE" => "wholemodule"
24
+ }
25
+
26
+ s.dependency "React-RCTFabric"
27
+ s.dependency "React-Codegen"
28
+ s.dependency "RCT-Folly"
29
+ s.dependency "RCTRequired"
30
+ s.dependency "RCTTypeSafety"
31
+ s.dependency "ReactCommon/turbomodule/core"
32
+ else
33
+ s.dependency "React-Core"
34
+ end
35
+ end
@@ -0,0 +1,31 @@
1
+ import type { TurboModule } from 'react-native';
2
+ import { TurboModuleRegistry } from 'react-native';
3
+
4
+ export interface Spec extends TurboModule {
5
+ /**
6
+ * Terminates the application process.
7
+ * Android: kills the process via Process.killProcess.
8
+ * iOS: calls exit(0). Note: Apple discourages this — avoid calling on iOS
9
+ * if your app is on the App Store; prefer sendToBackground instead.
10
+ */
11
+ exitApp(): void;
12
+
13
+ /**
14
+ * Moves the app to the background without terminating the process.
15
+ * Android: moveTaskToBack(true) — fully supported.
16
+ * iOS: suspends the app via the UIApplication suspend selector — best effort.
17
+ */
18
+ sendToBackground(): void;
19
+
20
+ /**
21
+ * Returns compile-time constants about platform capabilities.
22
+ */
23
+ getConstants(): {
24
+ /**
25
+ * True on Android. iOS returns false because backgrounding is OS-controlled.
26
+ */
27
+ isBackgroundSupported: boolean;
28
+ };
29
+ }
30
+
31
+ export default TurboModuleRegistry.getEnforcing<Spec>('AppExit');
package/src/index.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { Platform } from 'react-native';
2
+ import NativeAppExit from './NativeAppExit';
3
+
4
+ export type AppExitOptions = {
5
+ /**
6
+ * When true, moves the app to background instead of terminating.
7
+ * On iOS this suspends the app; on Android it calls moveTaskToBack.
8
+ * Defaults to false (full exit).
9
+ */
10
+ background?: boolean;
11
+ };
12
+
13
+ const constants = NativeAppExit.getConstants();
14
+
15
+ const AppExit = {
16
+ /**
17
+ * True on Android where moveTaskToBack is a first-class OS feature.
18
+ * False on iOS — Apple controls backgrounding; there is no public API.
19
+ */
20
+ isBackgroundSupported: constants.isBackgroundSupported,
21
+
22
+ /**
23
+ * Exit or background the app.
24
+ *
25
+ * @example
26
+ * // Hard exit
27
+ * AppExit.exit();
28
+ *
29
+ * @example
30
+ * // Send to background instead of killing
31
+ * AppExit.exit({ background: true });
32
+ */
33
+ exit(options: AppExitOptions = {}): void {
34
+ if (options.background) {
35
+ AppExit.sendToBackground();
36
+ } else {
37
+ AppExit.exitApp();
38
+ }
39
+ },
40
+
41
+ /**
42
+ * Terminate the app process immediately.
43
+ *
44
+ * iOS warning: Apple guidelines discourage calling exit() from production
45
+ * apps distributed via the App Store. Prefer sendToBackground() on iOS.
46
+ */
47
+ exitApp(): void {
48
+ NativeAppExit.exitApp();
49
+ },
50
+
51
+ /**
52
+ * Move the app to the background without terminating the process.
53
+ *
54
+ * Android: moveTaskToBack(true) — native and reliable.
55
+ * iOS: best-effort suspend via UIApplication private selector.
56
+ * Not guaranteed to work across all iOS versions.
57
+ */
58
+ sendToBackground(): void {
59
+ if (Platform.OS === 'android' || Platform.OS === 'ios') {
60
+ NativeAppExit.sendToBackground();
61
+ }
62
+ },
63
+ };
64
+
65
+ export { AppExit };
66
+ export default AppExit;