react-native-device-integrity 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/DeviceIntegrity.podspec +21 -0
- package/LICENSE +20 -0
- package/README.md +286 -0
- package/android/build.gradle +55 -0
- package/android/src/main/AndroidManifest.xml +20 -0
- package/android/src/main/java/com/deviceintegrity/DeviceIntegrityModule.kt +48 -0
- package/android/src/main/java/com/deviceintegrity/DeviceIntegrityPackage.kt +31 -0
- package/android/src/main/java/com/deviceintegrity/IntegrityChecks.kt +454 -0
- package/app.plugin.js +1 -0
- package/ios/DIIntegrityChecks.h +15 -0
- package/ios/DIIntegrityChecks.m +395 -0
- package/ios/DeviceIntegrity.h +5 -0
- package/ios/DeviceIntegrity.mm +29 -0
- package/ios/PrivacyInfo.xcprivacy +14 -0
- package/lib/module/NativeDeviceIntegrity.js +5 -0
- package/lib/module/NativeDeviceIntegrity.js.map +1 -0
- package/lib/module/checkIntegrity.js +15 -0
- package/lib/module/checkIntegrity.js.map +1 -0
- package/lib/module/checkIntegrity.native.js +67 -0
- package/lib/module/checkIntegrity.native.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/resolveStatus.js +76 -0
- package/lib/module/resolveStatus.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/useDeviceIntegrity.js +62 -0
- package/lib/module/useDeviceIntegrity.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeDeviceIntegrity.d.ts +17 -0
- package/lib/typescript/src/NativeDeviceIntegrity.d.ts.map +1 -0
- package/lib/typescript/src/checkIntegrity.d.ts +6 -0
- package/lib/typescript/src/checkIntegrity.d.ts.map +1 -0
- package/lib/typescript/src/checkIntegrity.native.d.ts +3 -0
- package/lib/typescript/src/checkIntegrity.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/resolveStatus.d.ts +8 -0
- package/lib/typescript/src/resolveStatus.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +23 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/useDeviceIntegrity.d.ts +10 -0
- package/lib/typescript/src/useDeviceIntegrity.d.ts.map +1 -0
- package/package.json +210 -0
- package/plugin/build/index.d.ts +19 -0
- package/plugin/build/index.js +49 -0
- package/src/NativeDeviceIntegrity.ts +19 -0
- package/src/checkIntegrity.native.ts +88 -0
- package/src/checkIntegrity.ts +16 -0
- package/src/index.tsx +10 -0
- package/src/resolveStatus.ts +125 -0
- package/src/types.ts +41 -0
- package/src/useDeviceIntegrity.ts +92 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
package com.deviceintegrity
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.os.Debug
|
|
7
|
+
import java.io.BufferedReader
|
|
8
|
+
import java.io.File
|
|
9
|
+
import java.io.FileReader
|
|
10
|
+
import java.util.concurrent.TimeUnit
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check set informed by RootBeer and the OWASP MASTG; independent implementation.
|
|
14
|
+
*/
|
|
15
|
+
internal class IntegrityChecks(private val context: Context) {
|
|
16
|
+
|
|
17
|
+
data class Signal(
|
|
18
|
+
val id: String,
|
|
19
|
+
val category: String,
|
|
20
|
+
val description: String,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
data class Report(
|
|
24
|
+
val completed: Boolean,
|
|
25
|
+
val reason: String? = null,
|
|
26
|
+
val signals: List<Signal>,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
fun run(): Report {
|
|
30
|
+
val signals = mutableListOf<Signal>()
|
|
31
|
+
val seen = mutableSetOf<String>()
|
|
32
|
+
var completed = true
|
|
33
|
+
|
|
34
|
+
fun add(signal: Signal) {
|
|
35
|
+
if (seen.add(signal.id)) {
|
|
36
|
+
signals.add(signal)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fun runCheck(block: () -> Signal?) {
|
|
41
|
+
try {
|
|
42
|
+
block()?.let { add(it) }
|
|
43
|
+
} catch (_: Throwable) {
|
|
44
|
+
completed = false
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
runCheck { checkEmulator() }
|
|
49
|
+
runCheck { checkRootSuBinary() }
|
|
50
|
+
runCheck { checkRootManagementApps() }
|
|
51
|
+
runCheck { checkRootMagiskFiles() }
|
|
52
|
+
runCheck { checkRootTestKeys() }
|
|
53
|
+
runCheck { checkRootDangerousProps() }
|
|
54
|
+
runCheck { checkRootRwSystem() }
|
|
55
|
+
runCheck { checkHookingFrida() }
|
|
56
|
+
runCheck { checkHookingXposed() }
|
|
57
|
+
runCheck { checkDebuggerAttached() }
|
|
58
|
+
|
|
59
|
+
return Report(
|
|
60
|
+
completed = completed,
|
|
61
|
+
reason = if (completed) null else "incomplete",
|
|
62
|
+
signals = signals,
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --- emulator ---
|
|
67
|
+
|
|
68
|
+
private fun checkEmulator(): Signal? {
|
|
69
|
+
val fingerprint = Build.FINGERPRINT.orEmpty().lowercase()
|
|
70
|
+
val model = Build.MODEL.orEmpty()
|
|
71
|
+
val manufacturer = Build.MANUFACTURER.orEmpty()
|
|
72
|
+
val hardware = Build.HARDWARE.orEmpty().lowercase()
|
|
73
|
+
val product = Build.PRODUCT.orEmpty().lowercase()
|
|
74
|
+
val brand = Build.BRAND.orEmpty().lowercase()
|
|
75
|
+
val device = Build.DEVICE.orEmpty().lowercase()
|
|
76
|
+
|
|
77
|
+
val fingerprintGeneric =
|
|
78
|
+
fingerprint.startsWith("generic") ||
|
|
79
|
+
fingerprint.startsWith("unknown") ||
|
|
80
|
+
fingerprint.contains("emulator") ||
|
|
81
|
+
fingerprint.contains("sdk_gphone")
|
|
82
|
+
|
|
83
|
+
val hardwareEmulator = hardware in setOf("goldfish", "ranchu", "vbox86")
|
|
84
|
+
|
|
85
|
+
val qemuFiles =
|
|
86
|
+
File("/dev/qemu_pipe").exists() || File("/dev/socket/qemud").exists()
|
|
87
|
+
|
|
88
|
+
val modelHit =
|
|
89
|
+
model.contains("google_sdk", ignoreCase = true) ||
|
|
90
|
+
model.contains("Emulator", ignoreCase = true) ||
|
|
91
|
+
model.contains("Android SDK built for", ignoreCase = true) ||
|
|
92
|
+
model.contains("sdk_gphone", ignoreCase = true)
|
|
93
|
+
|
|
94
|
+
val manufacturerHit = manufacturer.contains("Genymotion", ignoreCase = true)
|
|
95
|
+
|
|
96
|
+
val productHit =
|
|
97
|
+
product.contains("sdk") ||
|
|
98
|
+
product.contains("sdk_gphone") ||
|
|
99
|
+
product.contains("vbox86p") ||
|
|
100
|
+
product.contains("emulator") ||
|
|
101
|
+
product.contains("simulator")
|
|
102
|
+
|
|
103
|
+
val brandDeviceGeneric = brand.startsWith("generic") && device.startsWith("generic")
|
|
104
|
+
|
|
105
|
+
val anyIndicator =
|
|
106
|
+
fingerprintGeneric ||
|
|
107
|
+
hardwareEmulator ||
|
|
108
|
+
qemuFiles ||
|
|
109
|
+
modelHit ||
|
|
110
|
+
manufacturerHit ||
|
|
111
|
+
productHit ||
|
|
112
|
+
brandDeviceGeneric
|
|
113
|
+
|
|
114
|
+
if (!anyIndicator) {
|
|
115
|
+
return null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Require at least one strong indicator to avoid false positives.
|
|
119
|
+
val strongIndicators = fingerprintGeneric || hardwareEmulator || qemuFiles
|
|
120
|
+
if (!strongIndicators) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Signal(
|
|
125
|
+
id = "emulator",
|
|
126
|
+
category = "emulator",
|
|
127
|
+
description = "Device appears to be an emulator",
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- root ---
|
|
132
|
+
|
|
133
|
+
private fun checkRootSuBinary(): Signal? {
|
|
134
|
+
val knownDirs =
|
|
135
|
+
listOf(
|
|
136
|
+
"/system/bin",
|
|
137
|
+
"/system/xbin",
|
|
138
|
+
"/sbin",
|
|
139
|
+
"/system/sd/xbin",
|
|
140
|
+
"/system/bin/failsafe",
|
|
141
|
+
"/data/local/xbin",
|
|
142
|
+
"/data/local/bin",
|
|
143
|
+
"/data/local",
|
|
144
|
+
"/su/bin",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
val pathDirs =
|
|
148
|
+
System.getenv("PATH")
|
|
149
|
+
?.split(':')
|
|
150
|
+
?.map { it.trim() }
|
|
151
|
+
?.filter { it.isNotEmpty() }
|
|
152
|
+
.orEmpty()
|
|
153
|
+
|
|
154
|
+
val dirs = (knownDirs + pathDirs).distinct()
|
|
155
|
+
|
|
156
|
+
val foundSu =
|
|
157
|
+
dirs.any { dir -> File(dir, "su").exists() } ||
|
|
158
|
+
File("/system/app/Superuser.apk").exists()
|
|
159
|
+
|
|
160
|
+
if (!foundSu) {
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return Signal(
|
|
165
|
+
id = "root_su_binary",
|
|
166
|
+
category = "root",
|
|
167
|
+
description = "A privileged elevation binary was found",
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private fun checkRootManagementApps(): Signal? {
|
|
172
|
+
val packages =
|
|
173
|
+
listOf(
|
|
174
|
+
"com.topjohnwu.magisk",
|
|
175
|
+
"io.github.huskydg.magisk",
|
|
176
|
+
"eu.chainfire.supersu",
|
|
177
|
+
"com.koushikdutta.superuser",
|
|
178
|
+
"com.noshufou.android.su",
|
|
179
|
+
"com.thirdparty.superuser",
|
|
180
|
+
"com.yellowes.su",
|
|
181
|
+
"com.kingroot.kinguser",
|
|
182
|
+
"com.kingo.root",
|
|
183
|
+
"com.zhiqupk.root.global",
|
|
184
|
+
"com.smedialink.oneclickroot",
|
|
185
|
+
"me.weishu.kernelsu",
|
|
186
|
+
"me.bmax.apatch",
|
|
187
|
+
"com.devadvance.rootcloak",
|
|
188
|
+
"de.robv.android.xposed.installer",
|
|
189
|
+
"org.lsposed.manager",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
val pm = context.packageManager
|
|
193
|
+
val found =
|
|
194
|
+
packages.any { packageName ->
|
|
195
|
+
try {
|
|
196
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
197
|
+
pm.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
|
|
198
|
+
} else {
|
|
199
|
+
@Suppress("DEPRECATION")
|
|
200
|
+
pm.getPackageInfo(packageName, 0)
|
|
201
|
+
}
|
|
202
|
+
true
|
|
203
|
+
} catch (_: PackageManager.NameNotFoundException) {
|
|
204
|
+
false
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!found) {
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return Signal(
|
|
213
|
+
id = "root_management_apps",
|
|
214
|
+
category = "root",
|
|
215
|
+
description = "A root or privilege-management app is installed",
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private fun checkRootMagiskFiles(): Signal? {
|
|
220
|
+
val paths =
|
|
221
|
+
listOf(
|
|
222
|
+
"/sbin/.magisk",
|
|
223
|
+
"/sbin/.core",
|
|
224
|
+
"/cache/.disable_magisk",
|
|
225
|
+
"/dev/.magisk.unblock",
|
|
226
|
+
"/data/adb/magisk",
|
|
227
|
+
"/data/adb/ksu",
|
|
228
|
+
"/data/adb/ap",
|
|
229
|
+
"/init.magisk.rc",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if (!paths.any { File(it).exists() }) {
|
|
233
|
+
return null
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return Signal(
|
|
237
|
+
id = "root_magisk_files",
|
|
238
|
+
category = "root",
|
|
239
|
+
description = "Root framework artifacts were detected",
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun checkRootTestKeys(): Signal? {
|
|
244
|
+
val tags = Build.TAGS ?: return null
|
|
245
|
+
if (!tags.contains("test-keys")) {
|
|
246
|
+
return null
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return Signal(
|
|
250
|
+
id = "root_test_keys",
|
|
251
|
+
category = "root",
|
|
252
|
+
description = "Build was signed with test keys",
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private fun checkRootDangerousProps(): Signal? {
|
|
257
|
+
val debuggable = readSystemProperty("ro.debuggable") ?: throw IllegalStateException("getprop failed")
|
|
258
|
+
val secure = readSystemProperty("ro.secure") ?: throw IllegalStateException("getprop failed")
|
|
259
|
+
|
|
260
|
+
if (debuggable != "1" && secure != "0") {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return Signal(
|
|
265
|
+
id = "root_dangerous_props",
|
|
266
|
+
category = "root",
|
|
267
|
+
description = "System security properties indicate an insecure build",
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private fun readSystemProperty(name: String): String? {
|
|
272
|
+
var process: Process? = null
|
|
273
|
+
return try {
|
|
274
|
+
process = ProcessBuilder("getprop", name).redirectErrorStream(true).start()
|
|
275
|
+
val finished = process.waitFor(1, TimeUnit.SECONDS)
|
|
276
|
+
if (!finished) {
|
|
277
|
+
process.destroy()
|
|
278
|
+
process.waitFor(200, TimeUnit.MILLISECONDS)
|
|
279
|
+
if (process.isAlive) {
|
|
280
|
+
process.destroyForcibly()
|
|
281
|
+
}
|
|
282
|
+
return null
|
|
283
|
+
}
|
|
284
|
+
process.inputStream.bufferedReader().use { it.readText().trim() }
|
|
285
|
+
} catch (_: Throwable) {
|
|
286
|
+
null
|
|
287
|
+
} finally {
|
|
288
|
+
try {
|
|
289
|
+
process?.destroy()
|
|
290
|
+
} catch (_: Throwable) {
|
|
291
|
+
// ignore
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private fun checkRootRwSystem(): Signal? {
|
|
297
|
+
val targets =
|
|
298
|
+
setOf("/system", "/system/bin", "/system/xbin", "/vendor", "/sbin", "/etc")
|
|
299
|
+
|
|
300
|
+
BufferedReader(FileReader("/proc/mounts")).use { reader ->
|
|
301
|
+
var line: String?
|
|
302
|
+
while (reader.readLine().also { line = it } != null) {
|
|
303
|
+
val parts = line!!.split(Regex("\\s+"))
|
|
304
|
+
if (parts.size < 4) {
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
val mountPoint = parts[1]
|
|
308
|
+
val options = parts[3].split(',')
|
|
309
|
+
if (mountPoint in targets && "rw" in options) {
|
|
310
|
+
return Signal(
|
|
311
|
+
id = "root_rw_system",
|
|
312
|
+
category = "root",
|
|
313
|
+
description = "A system partition is mounted read-write",
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return null
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// --- hooking ---
|
|
323
|
+
|
|
324
|
+
private fun checkHookingFrida(): Signal? {
|
|
325
|
+
val mapMarkers =
|
|
326
|
+
listOf("frida-agent", "frida-gadget", "libfrida", "gum-js-loop", "linjector")
|
|
327
|
+
|
|
328
|
+
if (mapsContainAny(mapMarkers)) {
|
|
329
|
+
return Signal(
|
|
330
|
+
id = "hooking_frida",
|
|
331
|
+
category = "hooking",
|
|
332
|
+
description = "Instrumentation framework artifacts were detected",
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Only gum-js-loop is treated as a strong task-name indicator.
|
|
337
|
+
if (taskCommContains("gum-js-loop")) {
|
|
338
|
+
return Signal(
|
|
339
|
+
id = "hooking_frida",
|
|
340
|
+
category = "hooking",
|
|
341
|
+
description = "Instrumentation framework artifacts were detected",
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return null
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private fun checkHookingXposed(): Signal? {
|
|
349
|
+
val mapMarkers =
|
|
350
|
+
listOf(
|
|
351
|
+
"XposedBridge.jar",
|
|
352
|
+
"liblspd",
|
|
353
|
+
"libriru",
|
|
354
|
+
"libsubstrate",
|
|
355
|
+
"libzygisk",
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if (mapsContainAny(mapMarkers)) {
|
|
359
|
+
return xposedSignal()
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (classPresent("de.robv.android.xposed.XposedBridge")) {
|
|
363
|
+
return xposedSignal()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
val stack = Thread.currentThread().stackTrace
|
|
367
|
+
val hooked =
|
|
368
|
+
stack.any { frame ->
|
|
369
|
+
val name = frame.className
|
|
370
|
+
name.startsWith("de.robv.android.xposed") ||
|
|
371
|
+
name.startsWith("com.saurik.substrate")
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (hooked) {
|
|
375
|
+
return xposedSignal()
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return null
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private fun xposedSignal(): Signal =
|
|
382
|
+
Signal(
|
|
383
|
+
id = "hooking_xposed",
|
|
384
|
+
category = "hooking",
|
|
385
|
+
description = "Code-injection framework artifacts were detected",
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
private fun classPresent(name: String): Boolean {
|
|
389
|
+
val loaders =
|
|
390
|
+
listOfNotNull(
|
|
391
|
+
context.classLoader,
|
|
392
|
+
ClassLoader.getSystemClassLoader(),
|
|
393
|
+
).distinct()
|
|
394
|
+
|
|
395
|
+
for (loader in loaders) {
|
|
396
|
+
try {
|
|
397
|
+
Class.forName(name, false, loader)
|
|
398
|
+
return true
|
|
399
|
+
} catch (_: ClassNotFoundException) {
|
|
400
|
+
// continue
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return false
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// --- debugger ---
|
|
407
|
+
|
|
408
|
+
private fun checkDebuggerAttached(): Signal? {
|
|
409
|
+
if (!Debug.isDebuggerConnected() && !Debug.waitingForDebugger()) {
|
|
410
|
+
return null
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return Signal(
|
|
414
|
+
id = "debugger_attached",
|
|
415
|
+
category = "debugger",
|
|
416
|
+
description = "A debugger is attached to the process",
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// --- helpers ---
|
|
421
|
+
|
|
422
|
+
private fun mapsContainAny(markers: List<String>): Boolean {
|
|
423
|
+
BufferedReader(FileReader("/proc/self/maps")).use { reader ->
|
|
424
|
+
var line: String?
|
|
425
|
+
while (reader.readLine().also { line = it } != null) {
|
|
426
|
+
val current = line!!
|
|
427
|
+
if (markers.any { marker -> current.contains(marker) }) {
|
|
428
|
+
return true
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return false
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private fun taskCommContains(name: String): Boolean {
|
|
436
|
+
val taskDir = File("/proc/self/task")
|
|
437
|
+
val tasks = taskDir.listFiles() ?: return false
|
|
438
|
+
for (task in tasks) {
|
|
439
|
+
val comm = File(task, "comm")
|
|
440
|
+
if (!comm.exists()) {
|
|
441
|
+
continue
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
val text = comm.readText().trim()
|
|
445
|
+
if (text == name) {
|
|
446
|
+
return true
|
|
447
|
+
}
|
|
448
|
+
} catch (_: Throwable) {
|
|
449
|
+
// skip unreadable task entries
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return false
|
|
453
|
+
}
|
|
454
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./plugin/build');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal integrity checks. Runs off the main thread (except URL-scheme
|
|
7
|
+
* queries) and delivers a NativeIntegrityReport-shaped dictionary.
|
|
8
|
+
*/
|
|
9
|
+
@interface DIIntegrityChecks : NSObject
|
|
10
|
+
|
|
11
|
+
+ (void)runWithCompletion:(void (^)(NSDictionary *report))completion;
|
|
12
|
+
|
|
13
|
+
@end
|
|
14
|
+
|
|
15
|
+
NS_ASSUME_NONNULL_END
|