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 +21 -0
- package/README.md +241 -0
- package/android/build.gradle +65 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/appexitlib/AppExitModule.kt +32 -0
- package/android/src/main/java/com/appexitlib/AppExitPackage.kt +32 -0
- package/android/src/newarch/java/com/appexitlib/NativeAppExitSpec.kt +9 -0
- package/android/src/oldarch/java/com/appexitlib/NativeAppExitSpec.kt +14 -0
- package/ios/AppExit.h +5 -0
- package/ios/AppExit.mm +45 -0
- package/lib/commonjs/NativeAppExit.js +9 -0
- package/lib/commonjs/NativeAppExit.js.map +1 -0
- package/lib/commonjs/index.js +58 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/NativeAppExit.js +5 -0
- package/lib/module/NativeAppExit.js.map +1 -0
- package/lib/module/index.js +54 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/src/NativeAppExit.d.ts +28 -0
- package/lib/typescript/src/NativeAppExit.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +45 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +121 -0
- package/rn-app-exit.podspec +35 -0
- package/src/NativeAppExit.ts +31 -0
- package/src/index.ts +66 -0
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
|
+
[](https://www.npmjs.com/package/rn-app-exit)
|
|
6
|
+
[](https://www.npmjs.com/package/rn-app-exit)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](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,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
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 @@
|
|
|
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;
|