@koderlabs/tasks-sdk-rn-native 0.1.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 +179 -0
- package/README.md +9 -0
- package/TasksSdkRnNative.podspec +32 -0
- package/android/build.gradle +43 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/CMakeLists.txt +22 -0
- package/android/src/main/cpp/tasks_native_crash.cpp +174 -0
- package/android/src/main/java/expo/modules/tasksnative/TasksSdkRnNativeModule.kt +188 -0
- package/dist/index.cjs +132 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +83 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/TasksSdkRnNativeModule.swift +167 -0
- package/package.json +85 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
PROPRIETARY SOFTWARE LICENSE — ALL RIGHTS RESERVED
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KoderLabs. All rights reserved.
|
|
4
|
+
Author: Jawaid Gadiwala <jawaidgadiwala@gmail.com>
|
|
5
|
+
|
|
6
|
+
================================================================================
|
|
7
|
+
1. NO LICENSE GRANTED BY DEFAULT
|
|
8
|
+
================================================================================
|
|
9
|
+
|
|
10
|
+
This software, including all source code, object code, documentation,
|
|
11
|
+
configuration files, build artifacts, test fixtures, and any associated
|
|
12
|
+
materials (collectively, the "Software"), is the proprietary and confidential
|
|
13
|
+
property of KoderLabs.
|
|
14
|
+
|
|
15
|
+
NO license, right, title, interest, or permission of any kind — express,
|
|
16
|
+
implied, statutory, by estoppel, by exhaustion, by patent exhaustion, or by
|
|
17
|
+
any other legal theory — is granted to any person or entity by:
|
|
18
|
+
|
|
19
|
+
(a) viewing the Software on any registry, repository, mirror, CDN,
|
|
20
|
+
cache, or other distribution channel (including but not limited to
|
|
21
|
+
npmjs.com, GitHub, GitLab, or any private registry);
|
|
22
|
+
(b) downloading, cloning, or otherwise obtaining a copy of the Software;
|
|
23
|
+
(c) the act of the Software being technically accessible due to a registry
|
|
24
|
+
requirement, mirror, or third-party distribution;
|
|
25
|
+
(d) any prior course of dealing, custom, or industry practice.
|
|
26
|
+
|
|
27
|
+
The ability to access the Software does NOT imply any permission to use it.
|
|
28
|
+
A separate, signed, written license agreement executed by an authorised
|
|
29
|
+
representative of KoderLabs is the ONLY mechanism by which any rights may be
|
|
30
|
+
granted.
|
|
31
|
+
|
|
32
|
+
================================================================================
|
|
33
|
+
2. PROHIBITED ACTIVITIES (NON-EXHAUSTIVE)
|
|
34
|
+
================================================================================
|
|
35
|
+
|
|
36
|
+
Without prior signed written permission from KoderLabs, the following are
|
|
37
|
+
expressly PROHIBITED and constitute infringement, breach of contract, and
|
|
38
|
+
unauthorised use:
|
|
39
|
+
|
|
40
|
+
(a) Copying the Software in whole or in part, in any medium;
|
|
41
|
+
(b) Modifying, adapting, translating, porting, or creating derivative works
|
|
42
|
+
of the Software;
|
|
43
|
+
(c) Distributing, republishing, mirroring, hosting, transmitting,
|
|
44
|
+
sublicensing, leasing, lending, renting, selling, offering for sale,
|
|
45
|
+
bartering, gifting, or otherwise transferring the Software or any
|
|
46
|
+
portion of it;
|
|
47
|
+
(d) Forking the Software's repository, whether on GitHub, GitLab, Bitbucket,
|
|
48
|
+
Codeberg, Sourcehut, or any other version-control hosting service;
|
|
49
|
+
(e) Reverse engineering, decompiling, disassembling, deobfuscating,
|
|
50
|
+
extracting source from compiled or minified artifacts, or attempting to
|
|
51
|
+
derive the source code, algorithms, or trade secrets;
|
|
52
|
+
(f) Removing, altering, or obscuring any copyright, trademark, license,
|
|
53
|
+
attribution, or proprietary notice;
|
|
54
|
+
(g) Using the Software, in whole or in part, to train, fine-tune,
|
|
55
|
+
evaluate, or benchmark any machine-learning model, embedding model, or
|
|
56
|
+
AI system;
|
|
57
|
+
(h) Using the Software to provide a hosted service, SaaS offering,
|
|
58
|
+
managed offering, or any form of public or commercial offering;
|
|
59
|
+
(i) Using the Software in any production, staging, development, evaluation,
|
|
60
|
+
or testing capacity, whether commercial or non-commercial;
|
|
61
|
+
(j) Bypassing, disabling, or attempting to circumvent any technical
|
|
62
|
+
protection measure (license check, telemetry, signature verification,
|
|
63
|
+
etc.) embedded in the Software;
|
|
64
|
+
(k) Combining or integrating the Software with any work licensed under a
|
|
65
|
+
copyleft licence (including but not limited to GPL, AGPL, LGPL, MPL,
|
|
66
|
+
EPL) in a manner that would purport to relicense the Software;
|
|
67
|
+
(l) Filing, prosecuting, or threatening patent litigation against KoderLabs
|
|
68
|
+
or its customers based on any feature, design, or behaviour of the
|
|
69
|
+
Software ("defensive termination" — any such action automatically and
|
|
70
|
+
immediately terminates any rights granted to the litigant elsewhere).
|
|
71
|
+
|
|
72
|
+
================================================================================
|
|
73
|
+
3. NO IMPLIED RIGHT TO INTERNAL USE
|
|
74
|
+
================================================================================
|
|
75
|
+
|
|
76
|
+
The Software is NOT licensed for internal evaluation, internal production,
|
|
77
|
+
internal testing, or any other internal use unless and until a separate
|
|
78
|
+
written commercial licence is executed. Possession of a copy of the Software,
|
|
79
|
+
by whatever means, confers no right to execute, run, deploy, or use it.
|
|
80
|
+
|
|
81
|
+
================================================================================
|
|
82
|
+
4. NO PATENT, TRADEMARK, OR OTHER IP GRANT
|
|
83
|
+
================================================================================
|
|
84
|
+
|
|
85
|
+
No patent licence, trademark licence, trade-secret disclosure, design-right
|
|
86
|
+
licence, or any other intellectual-property licence is granted under this
|
|
87
|
+
notice. KoderLabs retains all such rights.
|
|
88
|
+
|
|
89
|
+
================================================================================
|
|
90
|
+
5. CONFIDENTIALITY
|
|
91
|
+
================================================================================
|
|
92
|
+
|
|
93
|
+
The Software contains trade secrets and confidential information of
|
|
94
|
+
KoderLabs. Any party in possession of the Software shall (a) treat it as
|
|
95
|
+
confidential, (b) take reasonable measures to prevent unauthorised access or
|
|
96
|
+
disclosure, and (c) not disclose, publish, or make available the Software or
|
|
97
|
+
any portion thereof to any third party.
|
|
98
|
+
|
|
99
|
+
================================================================================
|
|
100
|
+
6. AUTOMATIC TERMINATION
|
|
101
|
+
================================================================================
|
|
102
|
+
|
|
103
|
+
Any rights that may exist under a separate written agreement terminate
|
|
104
|
+
AUTOMATICALLY AND IMMEDIATELY upon any breach of this notice, without notice
|
|
105
|
+
and without judicial action. Upon termination, the breaching party shall
|
|
106
|
+
immediately destroy all copies of the Software in its possession or control
|
|
107
|
+
and certify destruction in writing to KoderLabs.
|
|
108
|
+
|
|
109
|
+
================================================================================
|
|
110
|
+
7. NO WARRANTY
|
|
111
|
+
================================================================================
|
|
112
|
+
|
|
113
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
114
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
115
|
+
FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ACCURACY, RELIABILITY,
|
|
116
|
+
TITLE, AVAILABILITY, OR THAT THE SOFTWARE WILL OPERATE UNINTERRUPTED OR
|
|
117
|
+
ERROR-FREE.
|
|
118
|
+
|
|
119
|
+
================================================================================
|
|
120
|
+
8. NO LIABILITY
|
|
121
|
+
================================================================================
|
|
122
|
+
|
|
123
|
+
IN NO EVENT SHALL KODERLABS OR ITS AFFILIATES, OFFICERS, EMPLOYEES, AGENTS,
|
|
124
|
+
LICENSORS, OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
125
|
+
LIABILITY (INCLUDING DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
126
|
+
CONSEQUENTIAL, PUNITIVE, LOST PROFITS, LOST DATA, OR BUSINESS INTERRUPTION),
|
|
127
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT
|
|
128
|
+
LIABILITY, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
129
|
+
SOFTWARE OR THE USE OF OR INABILITY TO USE THE SOFTWARE, EVEN IF ADVISED OF
|
|
130
|
+
THE POSSIBILITY OF SUCH DAMAGE. IN NO EVENT SHALL KODERLABS' TOTAL CUMULATIVE
|
|
131
|
+
LIABILITY EXCEED ONE U.S. DOLLAR (USD 1.00).
|
|
132
|
+
|
|
133
|
+
================================================================================
|
|
134
|
+
9. EQUITABLE RELIEF
|
|
135
|
+
================================================================================
|
|
136
|
+
|
|
137
|
+
The parties acknowledge that any breach of this notice would cause
|
|
138
|
+
irreparable harm to KoderLabs for which monetary damages would be inadequate.
|
|
139
|
+
KoderLabs is therefore entitled, in addition to all other remedies available
|
|
140
|
+
at law, to specific performance and injunctive relief without the need to
|
|
141
|
+
post bond.
|
|
142
|
+
|
|
143
|
+
================================================================================
|
|
144
|
+
10. GOVERNING LAW AND VENUE
|
|
145
|
+
================================================================================
|
|
146
|
+
|
|
147
|
+
This notice shall be governed by and construed in accordance with the laws
|
|
148
|
+
of the Islamic Republic of Pakistan, without regard to its conflict-of-laws
|
|
149
|
+
principles. Any dispute arising out of or in connection with this notice
|
|
150
|
+
shall be subject to the exclusive jurisdiction of the courts of Karachi,
|
|
151
|
+
Pakistan.
|
|
152
|
+
|
|
153
|
+
The United Nations Convention on Contracts for the International Sale of
|
|
154
|
+
Goods shall not apply.
|
|
155
|
+
|
|
156
|
+
================================================================================
|
|
157
|
+
11. SEVERABILITY AND ENTIRE NOTICE
|
|
158
|
+
================================================================================
|
|
159
|
+
|
|
160
|
+
If any provision of this notice is held invalid or unenforceable, the
|
|
161
|
+
remaining provisions shall continue in full force and effect. This notice,
|
|
162
|
+
together with any separate signed written licence executed between KoderLabs
|
|
163
|
+
and a licensee, constitutes the entire agreement concerning the Software and
|
|
164
|
+
supersedes all prior or contemporaneous understandings.
|
|
165
|
+
|
|
166
|
+
================================================================================
|
|
167
|
+
12. CONTACT
|
|
168
|
+
================================================================================
|
|
169
|
+
|
|
170
|
+
For licensing inquiries, audit requests, or notice of suspected
|
|
171
|
+
infringement, contact:
|
|
172
|
+
|
|
173
|
+
KoderLabs
|
|
174
|
+
Attn: Jawaid Gadiwala
|
|
175
|
+
Email: jawaidgadiwala@gmail.com
|
|
176
|
+
|
|
177
|
+
================================================================================
|
|
178
|
+
END OF LICENCE
|
|
179
|
+
================================================================================
|
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Proprietary Software
|
|
2
|
+
|
|
3
|
+
KoderLabs proprietary. All rights reserved.
|
|
4
|
+
|
|
5
|
+
See the bundled `LICENSE` for terms. No grant of any rights, express or
|
|
6
|
+
implied, is conferred by access to or possession of this package. A
|
|
7
|
+
separate signed written licence from KoderLabs is required for any use.
|
|
8
|
+
|
|
9
|
+
Licensing inquiries: jawaidgadiwala@gmail.com
|
|
@@ -0,0 +1,32 @@
|
|
|
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 = 'TasksSdkRnNative'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = 'MIT'
|
|
11
|
+
s.author = 'KoderLabs'
|
|
12
|
+
s.homepage = 'https://tasks.koderlabs.net'
|
|
13
|
+
s.platforms = { :ios => '13.4' }
|
|
14
|
+
s.swift_version = '5.4'
|
|
15
|
+
s.source = { :git => '' }
|
|
16
|
+
s.static_framework = true
|
|
17
|
+
|
|
18
|
+
s.dependency 'ExpoModulesCore'
|
|
19
|
+
# PLCrashReporter — BSD-licensed open-source library. Captures Mach
|
|
20
|
+
# exceptions, signals (SIGSEGV/SIGBUS/SIGABRT/SIGFPE), and ObjC NSExceptions
|
|
21
|
+
# and writes a binary crash report to disk that survives process death.
|
|
22
|
+
s.dependency 'PLCrashReporter', '~> 1.11'
|
|
23
|
+
|
|
24
|
+
s.pod_target_xcconfig = {
|
|
25
|
+
'DEFINES_MODULE' => 'YES',
|
|
26
|
+
# Force C++17 for ExpoModulesCore compatibility.
|
|
27
|
+
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
|
|
28
|
+
'SWIFT_COMPILATION_MODE' => 'wholemodule',
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
s.source_files = "ios/**/*.{h,m,mm,swift}"
|
|
32
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
apply plugin: 'com.android.library'
|
|
2
|
+
apply plugin: 'kotlin-android'
|
|
3
|
+
|
|
4
|
+
group = 'expo.modules.tasksnative'
|
|
5
|
+
version = '0.0.0'
|
|
6
|
+
|
|
7
|
+
def expoModulesCorePlugin = new File(
|
|
8
|
+
project(":expo-modules-core").projectDir.absolutePath,
|
|
9
|
+
"ExpoModulesCorePlugin.gradle"
|
|
10
|
+
)
|
|
11
|
+
apply from: expoModulesCorePlugin
|
|
12
|
+
applyKotlinExpoModulesCorePlugin()
|
|
13
|
+
useCoreDependencies()
|
|
14
|
+
useExpoPublishing()
|
|
15
|
+
|
|
16
|
+
android {
|
|
17
|
+
namespace "expo.modules.tasksnative"
|
|
18
|
+
defaultConfig {
|
|
19
|
+
minSdkVersion safeExtGet("minSdkVersion", 24)
|
|
20
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 34)
|
|
21
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 34)
|
|
22
|
+
versionCode 1
|
|
23
|
+
versionName "0.0.0"
|
|
24
|
+
|
|
25
|
+
externalNativeBuild {
|
|
26
|
+
cmake {
|
|
27
|
+
cppFlags "-std=c++17"
|
|
28
|
+
// Build for the architectures EAS / standard Android targets.
|
|
29
|
+
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
externalNativeBuild {
|
|
35
|
+
cmake {
|
|
36
|
+
path "src/main/cpp/CMakeLists.txt"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
packagingOptions {
|
|
41
|
+
pickFirst "**/libc++_shared.so"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.18)
|
|
2
|
+
project(tasksNative)
|
|
3
|
+
|
|
4
|
+
# Build a tiny native lib that installs SIGSEGV/SIGBUS/SIGABRT/SIGFPE
|
|
5
|
+
# handlers. On crash it serialises minimal context (signal, fault address,
|
|
6
|
+
# backtrace) to a file in the app's filesDir.
|
|
7
|
+
#
|
|
8
|
+
# We deliberately avoid linking Crashpad/Breakpad here — that's a 50MB+
|
|
9
|
+
# dependency and 1 week of integration work. The simple sigaction + raw
|
|
10
|
+
# backtrace covers the most common native crash modes (null deref, abort).
|
|
11
|
+
# Apps with heavy native code should add Crashpad as a follow-up.
|
|
12
|
+
|
|
13
|
+
add_library(tasks_native_crash SHARED
|
|
14
|
+
tasks_native_crash.cpp
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
target_link_libraries(tasks_native_crash
|
|
18
|
+
android
|
|
19
|
+
log
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
target_compile_features(tasks_native_crash PRIVATE cxx_std_17)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal native crash capture for Android.
|
|
3
|
+
*
|
|
4
|
+
* Installs a `sigaction` handler on a dedicated alternate signal stack for
|
|
5
|
+
* SIGSEGV / SIGBUS / SIGABRT / SIGFPE / SIGILL. When triggered, the handler:
|
|
6
|
+
*
|
|
7
|
+
* 1. Captures backtrace addresses via `unwind` (no symbolisation — we'd
|
|
8
|
+
* need libunwind or Breakpad/Crashpad for full names). Address-only
|
|
9
|
+
* output requires post-process symbolisation via the CLI source-map
|
|
10
|
+
* upload (planned to extend for .so files).
|
|
11
|
+
* 2. Writes a JSON file to the path configured at install time.
|
|
12
|
+
* 3. Chains to the previous handler so RN's redbox / Play Store ANR
|
|
13
|
+
* detection still works.
|
|
14
|
+
*
|
|
15
|
+
* Important constraints inside a signal handler:
|
|
16
|
+
* - Only async-signal-safe functions (no malloc, no printf, no Java calls)
|
|
17
|
+
* - Stack space is limited (use the alternate sigaltstack)
|
|
18
|
+
* - Avoid touching shared mutable state — read configured `crash_file_path`
|
|
19
|
+
* once at install time and treat it as immutable.
|
|
20
|
+
*
|
|
21
|
+
* Production hardening (out of scope for this commit, tracked in
|
|
22
|
+
* TasksSdkRnNative.java's TODO):
|
|
23
|
+
* - Use libunwind for unwinding past async-signal-safety boundary
|
|
24
|
+
* - Capture register dump
|
|
25
|
+
* - Resolve symbols on next launch (offline path, can use malloc safely)
|
|
26
|
+
* - Detect re-entrant crashes (handler crashing inside the handler)
|
|
27
|
+
*/
|
|
28
|
+
#include <jni.h>
|
|
29
|
+
#include <signal.h>
|
|
30
|
+
#include <unistd.h>
|
|
31
|
+
#include <fcntl.h>
|
|
32
|
+
#include <unwind.h>
|
|
33
|
+
#include <string.h>
|
|
34
|
+
#include <android/log.h>
|
|
35
|
+
|
|
36
|
+
#define LOG_TAG "TasksNative"
|
|
37
|
+
#define MAX_FRAMES 64
|
|
38
|
+
#define MAX_PATH 1024
|
|
39
|
+
|
|
40
|
+
static char g_crash_file_path[MAX_PATH] = {0};
|
|
41
|
+
static struct sigaction g_prev_handlers[NSIG];
|
|
42
|
+
static int g_handled_signals[] = {SIGSEGV, SIGBUS, SIGABRT, SIGFPE, SIGILL};
|
|
43
|
+
static const size_t g_handled_count = sizeof(g_handled_signals) / sizeof(g_handled_signals[0]);
|
|
44
|
+
|
|
45
|
+
struct UnwindContext {
|
|
46
|
+
void** frames;
|
|
47
|
+
size_t cap;
|
|
48
|
+
size_t count;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* ctx, void* arg) {
|
|
52
|
+
UnwindContext* uc = (UnwindContext*)arg;
|
|
53
|
+
if (uc->count >= uc->cap) return _URC_END_OF_STACK;
|
|
54
|
+
uintptr_t pc = _Unwind_GetIP(ctx);
|
|
55
|
+
if (pc) {
|
|
56
|
+
uc->frames[uc->count++] = (void*)pc;
|
|
57
|
+
}
|
|
58
|
+
return _URC_NO_REASON;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Write `len` bytes from `buf` to a file descriptor. Loops on partial writes.
|
|
63
|
+
* Async-signal-safe.
|
|
64
|
+
*/
|
|
65
|
+
static void safe_write(int fd, const char* buf, size_t len) {
|
|
66
|
+
while (len > 0) {
|
|
67
|
+
ssize_t n = write(fd, buf, len);
|
|
68
|
+
if (n <= 0) break;
|
|
69
|
+
buf += n;
|
|
70
|
+
len -= (size_t)n;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Itoa for unsigned long long. Async-signal-safe. */
|
|
75
|
+
static int u64_to_hex(unsigned long long v, char* out) {
|
|
76
|
+
static const char digits[] = "0123456789abcdef";
|
|
77
|
+
char buf[20];
|
|
78
|
+
int i = 0;
|
|
79
|
+
if (v == 0) { out[0] = '0'; return 1; }
|
|
80
|
+
while (v && i < (int)sizeof(buf)) { buf[i++] = digits[v & 0xf]; v >>= 4; }
|
|
81
|
+
for (int j = 0; j < i; j++) out[j] = buf[i - 1 - j];
|
|
82
|
+
return i;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static const char* signal_name(int signo) {
|
|
86
|
+
switch (signo) {
|
|
87
|
+
case SIGSEGV: return "SIGSEGV";
|
|
88
|
+
case SIGBUS: return "SIGBUS";
|
|
89
|
+
case SIGABRT: return "SIGABRT";
|
|
90
|
+
case SIGFPE: return "SIGFPE";
|
|
91
|
+
case SIGILL: return "SIGILL";
|
|
92
|
+
default: return "SIGNAL";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static void crash_handler(int signo, siginfo_t* info, void* /*ctx*/) {
|
|
97
|
+
// 1. Open the crash file (O_CREAT|O_TRUNC — overwrite any stale report).
|
|
98
|
+
int fd = open(g_crash_file_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
|
99
|
+
if (fd < 0) goto chain;
|
|
100
|
+
|
|
101
|
+
// 2. Emit JSON manually — we can't use snprintf here (not async-signal-safe).
|
|
102
|
+
safe_write(fd, "{\"platform\":\"android\",\"signal\":\"", 32);
|
|
103
|
+
const char* sname = signal_name(signo);
|
|
104
|
+
safe_write(fd, sname, strlen(sname));
|
|
105
|
+
safe_write(fd, "\",\"faultAddr\":\"0x", 17);
|
|
106
|
+
{
|
|
107
|
+
char hex[20];
|
|
108
|
+
int hn = u64_to_hex((unsigned long long)(uintptr_t)info->si_addr, hex);
|
|
109
|
+
safe_write(fd, hex, (size_t)hn);
|
|
110
|
+
}
|
|
111
|
+
safe_write(fd, "\",\"frames\":[", 12);
|
|
112
|
+
|
|
113
|
+
void* frames[MAX_FRAMES];
|
|
114
|
+
UnwindContext uc = { frames, MAX_FRAMES, 0 };
|
|
115
|
+
_Unwind_Backtrace(unwind_callback, &uc);
|
|
116
|
+
|
|
117
|
+
for (size_t i = 0; i < uc.count; i++) {
|
|
118
|
+
if (i > 0) safe_write(fd, ",", 1);
|
|
119
|
+
safe_write(fd, "\"0x", 3);
|
|
120
|
+
char hex[20];
|
|
121
|
+
int hn = u64_to_hex((unsigned long long)(uintptr_t)frames[i], hex);
|
|
122
|
+
safe_write(fd, hex, (size_t)hn);
|
|
123
|
+
safe_write(fd, "\"", 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
safe_write(fd, "]}", 2);
|
|
127
|
+
close(fd);
|
|
128
|
+
|
|
129
|
+
chain:
|
|
130
|
+
// 3. Chain into previous handler so RN / Play Store can also see it.
|
|
131
|
+
if (g_prev_handlers[signo].sa_flags & SA_SIGINFO) {
|
|
132
|
+
if (g_prev_handlers[signo].sa_sigaction) {
|
|
133
|
+
g_prev_handlers[signo].sa_sigaction(signo, info, nullptr);
|
|
134
|
+
}
|
|
135
|
+
} else if (g_prev_handlers[signo].sa_handler != SIG_DFL &&
|
|
136
|
+
g_prev_handlers[signo].sa_handler != SIG_IGN) {
|
|
137
|
+
g_prev_handlers[signo].sa_handler(signo);
|
|
138
|
+
}
|
|
139
|
+
// 4. Re-raise so the OS sees the death and writes its own tombstone.
|
|
140
|
+
signal(signo, SIG_DFL);
|
|
141
|
+
raise(signo);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
extern "C" JNIEXPORT void JNICALL
|
|
145
|
+
Java_expo_modules_tasksnative_TasksSdkRnNativeModule_nativeInstall(
|
|
146
|
+
JNIEnv* env, jobject /*thiz*/, jstring crashFilePath) {
|
|
147
|
+
const char* path = env->GetStringUTFChars(crashFilePath, nullptr);
|
|
148
|
+
if (!path) return;
|
|
149
|
+
size_t len = strnlen(path, MAX_PATH - 1);
|
|
150
|
+
memcpy(g_crash_file_path, path, len);
|
|
151
|
+
g_crash_file_path[len] = '\0';
|
|
152
|
+
env->ReleaseStringUTFChars(crashFilePath, path);
|
|
153
|
+
|
|
154
|
+
// Allocate a 64KB alternate stack so the handler can run when the main
|
|
155
|
+
// stack is corrupted (a real risk on SIGSEGV from stack overflow).
|
|
156
|
+
stack_t altstack;
|
|
157
|
+
altstack.ss_sp = malloc(SIGSTKSZ * 8); // pre-allocated, not signal-time
|
|
158
|
+
altstack.ss_size = SIGSTKSZ * 8;
|
|
159
|
+
altstack.ss_flags = 0;
|
|
160
|
+
sigaltstack(&altstack, nullptr);
|
|
161
|
+
|
|
162
|
+
struct sigaction sa;
|
|
163
|
+
memset(&sa, 0, sizeof(sa));
|
|
164
|
+
sa.sa_sigaction = crash_handler;
|
|
165
|
+
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
|
166
|
+
sigemptyset(&sa.sa_mask);
|
|
167
|
+
|
|
168
|
+
for (size_t i = 0; i < g_handled_count; i++) {
|
|
169
|
+
sigaction(g_handled_signals[i], &sa, &g_prev_handlers[g_handled_signals[i]]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
__android_log_print(ANDROID_LOG_INFO, LOG_TAG,
|
|
173
|
+
"Native signal handlers installed (path=%s)", g_crash_file_path);
|
|
174
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
package expo.modules.tasksnative
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import expo.modules.kotlin.modules.Module
|
|
6
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
7
|
+
import org.json.JSONArray
|
|
8
|
+
import org.json.JSONObject
|
|
9
|
+
import java.io.File
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Expo module that wires up Android native crash + ANR capture.
|
|
13
|
+
*
|
|
14
|
+
* Two layers:
|
|
15
|
+
* 1. JNI signal handler in `libtasks_native_crash.so` (built by CMake).
|
|
16
|
+
* Catches SIGSEGV/SIGBUS/SIGABRT/SIGFPE/SIGILL and writes a JSON
|
|
17
|
+
* crash file. See `cpp/tasks_native_crash.cpp` for the full
|
|
18
|
+
* async-signal-safe constraints.
|
|
19
|
+
* 2. ANR watchdog — a background thread that posts a heartbeat to the
|
|
20
|
+
* main looper every second. If the heartbeat doesn't fire within
|
|
21
|
+
* `anrThresholdMs` (default 5000ms), we synthesise an ANR event with
|
|
22
|
+
* the main thread's stack trace.
|
|
23
|
+
*
|
|
24
|
+
* The JS layer (`@koderlabs/tasks-sdk-rn-native`) calls:
|
|
25
|
+
* enableCrashReporter() — install signal handler
|
|
26
|
+
* enableAnrWatchdog(thresholdMs) — start watchdog thread
|
|
27
|
+
* getPendingCrashReport() — read JSON file, return as object
|
|
28
|
+
* purgePendingCrashReport() — delete file
|
|
29
|
+
*
|
|
30
|
+
* The native side never tries to POST events; that's the JS SDK's job.
|
|
31
|
+
*/
|
|
32
|
+
class TasksSdkRnNativeModule : Module() {
|
|
33
|
+
private companion object {
|
|
34
|
+
init {
|
|
35
|
+
System.loadLibrary("tasks_native_crash")
|
|
36
|
+
}
|
|
37
|
+
const val CRASH_FILE_NAME = "tasks-native-crash.json"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private external fun nativeInstall(crashFilePath: String)
|
|
41
|
+
|
|
42
|
+
private var watchdog: AnrWatchdog? = null
|
|
43
|
+
|
|
44
|
+
override fun definition() = ModuleDefinition {
|
|
45
|
+
Name("TasksSdkRnNative")
|
|
46
|
+
|
|
47
|
+
AsyncFunction("enableCrashReporter") {
|
|
48
|
+
val ctx = appContext.reactContext
|
|
49
|
+
?: throw IllegalStateException("React context unavailable")
|
|
50
|
+
val path = File(ctx.filesDir, CRASH_FILE_NAME).absolutePath
|
|
51
|
+
nativeInstall(path)
|
|
52
|
+
return@AsyncFunction true
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
AsyncFunction("enableAnrWatchdog") { thresholdMs: Int ->
|
|
56
|
+
val ctx = appContext.reactContext
|
|
57
|
+
?: throw IllegalStateException("React context unavailable")
|
|
58
|
+
watchdog?.stop()
|
|
59
|
+
val path = File(ctx.filesDir, "tasks-native-anr.json").absolutePath
|
|
60
|
+
watchdog = AnrWatchdog(thresholdMs.coerceAtLeast(1000), path).also { it.start() }
|
|
61
|
+
return@AsyncFunction true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
AsyncFunction("getPendingCrashReport") {
|
|
65
|
+
val ctx = appContext.reactContext ?: return@AsyncFunction null
|
|
66
|
+
val crashFile = File(ctx.filesDir, CRASH_FILE_NAME)
|
|
67
|
+
val anrFile = File(ctx.filesDir, "tasks-native-anr.json")
|
|
68
|
+
|
|
69
|
+
// Native crash takes priority — it killed the app. ANRs are recovered
|
|
70
|
+
// separately when the watchdog wrote one before the process resumed.
|
|
71
|
+
val source = when {
|
|
72
|
+
crashFile.exists() -> crashFile
|
|
73
|
+
anrFile.exists() -> anrFile
|
|
74
|
+
else -> return@AsyncFunction null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return@AsyncFunction try {
|
|
78
|
+
val raw = source.readText()
|
|
79
|
+
val json = JSONObject(raw)
|
|
80
|
+
json.put("kind", if (source == crashFile) "native_signal" else "anr")
|
|
81
|
+
jsonToMap(json)
|
|
82
|
+
} catch (e: Exception) {
|
|
83
|
+
// Drop corrupted file so we don't keep retrying.
|
|
84
|
+
source.delete()
|
|
85
|
+
null
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
AsyncFunction("purgePendingCrashReport") {
|
|
90
|
+
val ctx = appContext.reactContext ?: return@AsyncFunction false
|
|
91
|
+
File(ctx.filesDir, CRASH_FILE_NAME).delete()
|
|
92
|
+
File(ctx.filesDir, "tasks-native-anr.json").delete()
|
|
93
|
+
return@AsyncFunction true
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Convert JSONObject to Map<String, Any> for the Expo bridge. */
|
|
98
|
+
private fun jsonToMap(o: JSONObject): Map<String, Any?> {
|
|
99
|
+
val out = mutableMapOf<String, Any?>()
|
|
100
|
+
for (key in o.keys()) {
|
|
101
|
+
val v = o.get(key)
|
|
102
|
+
out[key] = when (v) {
|
|
103
|
+
is JSONObject -> jsonToMap(v)
|
|
104
|
+
is JSONArray -> jsonArrayToList(v)
|
|
105
|
+
JSONObject.NULL -> null
|
|
106
|
+
else -> v
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private fun jsonArrayToList(a: JSONArray): List<Any?> {
|
|
113
|
+
val out = mutableListOf<Any?>()
|
|
114
|
+
for (i in 0 until a.length()) {
|
|
115
|
+
val v = a.get(i)
|
|
116
|
+
out.add(when (v) {
|
|
117
|
+
is JSONObject -> jsonToMap(v)
|
|
118
|
+
is JSONArray -> jsonArrayToList(v)
|
|
119
|
+
JSONObject.NULL -> null
|
|
120
|
+
else -> v
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
return out
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* ANR watchdog — runs on a background thread and posts a heartbeat to the
|
|
129
|
+
* main looper every second. If `thresholdMs` passes without the heartbeat
|
|
130
|
+
* being acknowledged, we capture the main thread's stack trace.
|
|
131
|
+
*
|
|
132
|
+
* NOT a true Android ANR detector (the OS does that internally and shows
|
|
133
|
+
* the "App not responding" dialog at 5s for activities). This is a soft
|
|
134
|
+
* watchdog — useful for instrumentation, less so for production-grade
|
|
135
|
+
* detection. Real apps should use the Play Store's vitals API for ANR
|
|
136
|
+
* data when available; this gives signal during development.
|
|
137
|
+
*/
|
|
138
|
+
private class AnrWatchdog(
|
|
139
|
+
private val thresholdMs: Int,
|
|
140
|
+
private val outputPath: String,
|
|
141
|
+
) : Thread("tasks-anr-watchdog") {
|
|
142
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
143
|
+
@Volatile private var running = true
|
|
144
|
+
@Volatile private var lastAck = System.currentTimeMillis()
|
|
145
|
+
|
|
146
|
+
fun stopWatchdog() { running = false }
|
|
147
|
+
|
|
148
|
+
// Avoid clashing with Thread.stop() which is deprecated.
|
|
149
|
+
fun stop() { stopWatchdog() }
|
|
150
|
+
|
|
151
|
+
override fun run() {
|
|
152
|
+
while (running) {
|
|
153
|
+
val sent = System.currentTimeMillis()
|
|
154
|
+
mainHandler.post { lastAck = System.currentTimeMillis() }
|
|
155
|
+
Thread.sleep(thresholdMs.toLong())
|
|
156
|
+
if (!running) return
|
|
157
|
+
// If lastAck hasn't moved since we posted, the main thread is stuck.
|
|
158
|
+
if (lastAck < sent) {
|
|
159
|
+
captureAnr()
|
|
160
|
+
// Wait for main thread to recover before re-arming, otherwise we'd
|
|
161
|
+
// generate one ANR per cycle.
|
|
162
|
+
while (running && lastAck < sent) {
|
|
163
|
+
Thread.sleep(500)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private fun captureAnr() {
|
|
170
|
+
try {
|
|
171
|
+
val stack = Looper.getMainLooper().thread.stackTrace
|
|
172
|
+
val sb = StringBuilder()
|
|
173
|
+
sb.append("{")
|
|
174
|
+
sb.append("\"platform\":\"android\",")
|
|
175
|
+
sb.append("\"thresholdMs\":").append(thresholdMs).append(",")
|
|
176
|
+
sb.append("\"capturedAt\":\"").append(java.util.Date().toString()).append("\",")
|
|
177
|
+
sb.append("\"stack\":[")
|
|
178
|
+
for ((i, frame) in stack.withIndex()) {
|
|
179
|
+
if (i > 0) sb.append(",")
|
|
180
|
+
sb.append("\"").append(frame.toString().replace("\"", "\\\"")).append("\"")
|
|
181
|
+
}
|
|
182
|
+
sb.append("]}")
|
|
183
|
+
File(outputPath).writeText(sb.toString())
|
|
184
|
+
} catch (e: Throwable) {
|
|
185
|
+
// Watchdog must never raise.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|