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.
Files changed (54) hide show
  1. package/DeviceIntegrity.podspec +21 -0
  2. package/LICENSE +20 -0
  3. package/README.md +286 -0
  4. package/android/build.gradle +55 -0
  5. package/android/src/main/AndroidManifest.xml +20 -0
  6. package/android/src/main/java/com/deviceintegrity/DeviceIntegrityModule.kt +48 -0
  7. package/android/src/main/java/com/deviceintegrity/DeviceIntegrityPackage.kt +31 -0
  8. package/android/src/main/java/com/deviceintegrity/IntegrityChecks.kt +454 -0
  9. package/app.plugin.js +1 -0
  10. package/ios/DIIntegrityChecks.h +15 -0
  11. package/ios/DIIntegrityChecks.m +395 -0
  12. package/ios/DeviceIntegrity.h +5 -0
  13. package/ios/DeviceIntegrity.mm +29 -0
  14. package/ios/PrivacyInfo.xcprivacy +14 -0
  15. package/lib/module/NativeDeviceIntegrity.js +5 -0
  16. package/lib/module/NativeDeviceIntegrity.js.map +1 -0
  17. package/lib/module/checkIntegrity.js +15 -0
  18. package/lib/module/checkIntegrity.js.map +1 -0
  19. package/lib/module/checkIntegrity.native.js +67 -0
  20. package/lib/module/checkIntegrity.native.js.map +1 -0
  21. package/lib/module/index.js +5 -0
  22. package/lib/module/index.js.map +1 -0
  23. package/lib/module/package.json +1 -0
  24. package/lib/module/resolveStatus.js +76 -0
  25. package/lib/module/resolveStatus.js.map +1 -0
  26. package/lib/module/types.js +4 -0
  27. package/lib/module/types.js.map +1 -0
  28. package/lib/module/useDeviceIntegrity.js +62 -0
  29. package/lib/module/useDeviceIntegrity.js.map +1 -0
  30. package/lib/typescript/package.json +1 -0
  31. package/lib/typescript/src/NativeDeviceIntegrity.d.ts +17 -0
  32. package/lib/typescript/src/NativeDeviceIntegrity.d.ts.map +1 -0
  33. package/lib/typescript/src/checkIntegrity.d.ts +6 -0
  34. package/lib/typescript/src/checkIntegrity.d.ts.map +1 -0
  35. package/lib/typescript/src/checkIntegrity.native.d.ts +3 -0
  36. package/lib/typescript/src/checkIntegrity.native.d.ts.map +1 -0
  37. package/lib/typescript/src/index.d.ts +4 -0
  38. package/lib/typescript/src/index.d.ts.map +1 -0
  39. package/lib/typescript/src/resolveStatus.d.ts +8 -0
  40. package/lib/typescript/src/resolveStatus.d.ts.map +1 -0
  41. package/lib/typescript/src/types.d.ts +23 -0
  42. package/lib/typescript/src/types.d.ts.map +1 -0
  43. package/lib/typescript/src/useDeviceIntegrity.d.ts +10 -0
  44. package/lib/typescript/src/useDeviceIntegrity.d.ts.map +1 -0
  45. package/package.json +210 -0
  46. package/plugin/build/index.d.ts +19 -0
  47. package/plugin/build/index.js +49 -0
  48. package/src/NativeDeviceIntegrity.ts +19 -0
  49. package/src/checkIntegrity.native.ts +88 -0
  50. package/src/checkIntegrity.ts +16 -0
  51. package/src/index.tsx +10 -0
  52. package/src/resolveStatus.ts +125 -0
  53. package/src/types.ts +41 -0
  54. package/src/useDeviceIntegrity.ts +92 -0
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { AppState } from 'react-native';
5
+ import { checkIntegrity } from './checkIntegrity';
6
+ export function useDeviceIntegrity(options = {}) {
7
+ const treatEmulatorAsCompromised = options.treatEmulatorAsCompromised;
8
+ const timeoutMs = options.timeoutMs;
9
+ const [result, setResult] = useState(null);
10
+ const [loading, setLoading] = useState(true);
11
+ const requestIdRef = useRef(0);
12
+ const mountedRef = useRef(true);
13
+ const appStateRef = useRef(AppState.currentState);
14
+ const run = useCallback(async () => {
15
+ const requestId = ++requestIdRef.current;
16
+ setLoading(true);
17
+ const next = await checkIntegrity({
18
+ treatEmulatorAsCompromised,
19
+ timeoutMs
20
+ });
21
+ if (mountedRef.current && requestId === requestIdRef.current) {
22
+ setResult(next);
23
+ setLoading(false);
24
+ }
25
+ return next;
26
+ }, [treatEmulatorAsCompromised, timeoutMs]);
27
+ useEffect(() => {
28
+ mountedRef.current = true;
29
+ run().catch(() => {
30
+ // checkIntegrity never rejects; guard against unexpected throws
31
+ });
32
+ return () => {
33
+ mountedRef.current = false;
34
+ };
35
+ }, [run]);
36
+ useEffect(() => {
37
+ const onChange = nextState => {
38
+ const previous = appStateRef.current;
39
+ appStateRef.current = nextState;
40
+ if ((previous === 'background' || previous === 'inactive') && nextState === 'active') {
41
+ run().catch(() => {
42
+ // checkIntegrity never rejects; guard against unexpected throws
43
+ });
44
+ }
45
+ };
46
+ const subscription = AppState.addEventListener('change', onChange);
47
+ return () => {
48
+ subscription.remove();
49
+ };
50
+ }, [run]);
51
+ const refresh = useCallback(() => {
52
+ return run();
53
+ }, [run]);
54
+ return {
55
+ status: result?.status ?? 'unknown',
56
+ signals: result?.signals ?? [],
57
+ result,
58
+ loading,
59
+ refresh
60
+ };
61
+ }
62
+ //# sourceMappingURL=useDeviceIntegrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useCallback","useEffect","useRef","useState","AppState","checkIntegrity","useDeviceIntegrity","options","treatEmulatorAsCompromised","timeoutMs","result","setResult","loading","setLoading","requestIdRef","mountedRef","appStateRef","currentState","run","requestId","current","next","catch","onChange","nextState","previous","subscription","addEventListener","remove","refresh","status","signals"],"sourceRoot":"../../src","sources":["useDeviceIntegrity.ts"],"mappings":";;AAAA,SAASA,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,OAAO;AAChE,SAASC,QAAQ,QAA6B,cAAc;AAC5D,SAASC,cAAc,QAAQ,kBAAkB;AAgBjD,OAAO,SAASC,kBAAkBA,CAChCC,OAA8B,GAAG,CAAC,CAAC,EACT;EAC1B,MAAMC,0BAA0B,GAAGD,OAAO,CAACC,0BAA0B;EACrE,MAAMC,SAAS,GAAGF,OAAO,CAACE,SAAS;EAEnC,MAAM,CAACC,MAAM,EAAEC,SAAS,CAAC,GAAGR,QAAQ,CAAyB,IAAI,CAAC;EAClE,MAAM,CAACS,OAAO,EAAEC,UAAU,CAAC,GAAGV,QAAQ,CAAC,IAAI,CAAC;EAE5C,MAAMW,YAAY,GAAGZ,MAAM,CAAC,CAAC,CAAC;EAC9B,MAAMa,UAAU,GAAGb,MAAM,CAAC,IAAI,CAAC;EAC/B,MAAMc,WAAW,GAAGd,MAAM,CAACE,QAAQ,CAACa,YAAY,CAAC;EAEjD,MAAMC,GAAG,GAAGlB,WAAW,CAAC,YAAsC;IAC5D,MAAMmB,SAAS,GAAG,EAAEL,YAAY,CAACM,OAAO;IACxCP,UAAU,CAAC,IAAI,CAAC;IAEhB,MAAMQ,IAAI,GAAG,MAAMhB,cAAc,CAAC;MAChCG,0BAA0B;MAC1BC;IACF,CAAC,CAAC;IAEF,IAAIM,UAAU,CAACK,OAAO,IAAID,SAAS,KAAKL,YAAY,CAACM,OAAO,EAAE;MAC5DT,SAAS,CAACU,IAAI,CAAC;MACfR,UAAU,CAAC,KAAK,CAAC;IACnB;IAEA,OAAOQ,IAAI;EACb,CAAC,EAAE,CAACb,0BAA0B,EAAEC,SAAS,CAAC,CAAC;EAE3CR,SAAS,CAAC,MAAM;IACdc,UAAU,CAACK,OAAO,GAAG,IAAI;IACzBF,GAAG,CAAC,CAAC,CAACI,KAAK,CAAC,MAAM;MAChB;IAAA,CACD,CAAC;IAEF,OAAO,MAAM;MACXP,UAAU,CAACK,OAAO,GAAG,KAAK;IAC5B,CAAC;EACH,CAAC,EAAE,CAACF,GAAG,CAAC,CAAC;EAETjB,SAAS,CAAC,MAAM;IACd,MAAMsB,QAAQ,GAAIC,SAAyB,IAAK;MAC9C,MAAMC,QAAQ,GAAGT,WAAW,CAACI,OAAO;MACpCJ,WAAW,CAACI,OAAO,GAAGI,SAAS;MAE/B,IACE,CAACC,QAAQ,KAAK,YAAY,IAAIA,QAAQ,KAAK,UAAU,KACrDD,SAAS,KAAK,QAAQ,EACtB;QACAN,GAAG,CAAC,CAAC,CAACI,KAAK,CAAC,MAAM;UAChB;QAAA,CACD,CAAC;MACJ;IACF,CAAC;IAED,MAAMI,YAAY,GAAGtB,QAAQ,CAACuB,gBAAgB,CAAC,QAAQ,EAAEJ,QAAQ,CAAC;IAClE,OAAO,MAAM;MACXG,YAAY,CAACE,MAAM,CAAC,CAAC;IACvB,CAAC;EACH,CAAC,EAAE,CAACV,GAAG,CAAC,CAAC;EAET,MAAMW,OAAO,GAAG7B,WAAW,CAAC,MAAgC;IAC1D,OAAOkB,GAAG,CAAC,CAAC;EACd,CAAC,EAAE,CAACA,GAAG,CAAC,CAAC;EAET,OAAO;IACLY,MAAM,EAAEpB,MAAM,EAAEoB,MAAM,IAAI,SAAS;IACnCC,OAAO,EAAErB,MAAM,EAAEqB,OAAO,IAAI,EAAE;IAC9BrB,MAAM;IACNE,OAAO;IACPiB;EACF,CAAC;AACH","ignoreList":[]}
@@ -0,0 +1 @@
1
+ {"type":"module"}
@@ -0,0 +1,17 @@
1
+ import { type TurboModule } from 'react-native';
2
+ export type NativeSignal = {
3
+ id: string;
4
+ category: string;
5
+ description: string;
6
+ };
7
+ export type NativeIntegrityReport = {
8
+ completed: boolean;
9
+ reason?: string;
10
+ signals: NativeSignal[];
11
+ };
12
+ export interface Spec extends TurboModule {
13
+ checkIntegrity(): Promise<NativeIntegrityReport>;
14
+ }
15
+ declare const _default: Spec | null | undefined;
16
+ export default _default;
17
+ //# sourceMappingURL=NativeDeviceIntegrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeDeviceIntegrity.d.ts","sourceRoot":"","sources":["../../../src/NativeDeviceIntegrity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB,CAAC;AAEF,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,cAAc,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;CAClD;;AAED,wBAAgE"}
@@ -0,0 +1,6 @@
1
+ import type { CheckIntegrityOptions, IntegrityResult } from './types.js';
2
+ /**
3
+ * Web / unsupported-platform entry. Must not import the native TurboModule.
4
+ */
5
+ export declare function checkIntegrity(_options?: CheckIntegrityOptions): Promise<IntegrityResult>;
6
+ //# sourceMappingURL=checkIntegrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkIntegrity.d.ts","sourceRoot":"","sources":["../../../src/checkIntegrity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAS,CAAC;AAEtE;;GAEG;AACH,wBAAsB,cAAc,CAClC,QAAQ,CAAC,EAAE,qBAAqB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAO1B"}
@@ -0,0 +1,3 @@
1
+ import type { CheckIntegrityOptions, IntegrityResult } from './types.js';
2
+ export declare function checkIntegrity(options?: CheckIntegrityOptions): Promise<IntegrityResult>;
3
+ //# sourceMappingURL=checkIntegrity.native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkIntegrity.native.d.ts","sourceRoot":"","sources":["../../../src/checkIntegrity.native.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,YAAS,CAAC;AA8BtE,wBAAsB,cAAc,CAClC,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,eAAe,CAAC,CAoD1B"}
@@ -0,0 +1,4 @@
1
+ export { checkIntegrity } from './checkIntegrity';
2
+ export { useDeviceIntegrity } from './useDeviceIntegrity.js';
3
+ export type { CheckIntegrityOptions, IntegrityResult, IntegrityStatus, Signal, SignalCategory, UnknownReason, } from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAsB,CAAC;AAC1D,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,MAAM,EACN,cAAc,EACd,aAAa,GACd,MAAM,YAAS,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Platform } from 'react-native';
2
+ import type { CheckIntegrityOptions, IntegrityResult } from './types.js';
3
+ /**
4
+ * Maps a native integrity report + options to the public IntegrityResult.
5
+ * Compromise evidence wins over incomplete runs; incomplete/malformed never become 'clean'.
6
+ */
7
+ export declare function resolveStatus(report: unknown, options: CheckIntegrityOptions | undefined, platform: typeof Platform.OS): IntegrityResult;
8
+ //# sourceMappingURL=resolveStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveStatus.d.ts","sourceRoot":"","sources":["../../../src/resolveStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EACV,qBAAqB,EACrB,eAAe,EAIhB,MAAM,YAAS,CAAC;AAwDjB;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,qBAAqB,YAAK,EACnC,QAAQ,EAAE,OAAO,QAAQ,CAAC,EAAE,GAC3B,eAAe,CAqDjB"}
@@ -0,0 +1,23 @@
1
+ import type { Platform } from 'react-native';
2
+ export type IntegrityStatus = 'clean' | 'compromised' | 'unknown';
3
+ export type SignalCategory = 'jailbreak' | 'root' | 'hooking' | 'debugger' | 'emulator' | 'tamper' | 'environment';
4
+ export interface Signal {
5
+ id: string;
6
+ category: SignalCategory;
7
+ description: string;
8
+ }
9
+ export type UnknownReason = 'unsupported_platform' | 'native_module_unavailable' | 'native_error' | 'timeout' | 'incomplete' | 'not_implemented';
10
+ export interface IntegrityResult {
11
+ status: IntegrityStatus;
12
+ signals: Signal[];
13
+ platform: typeof Platform.OS;
14
+ reason?: UnknownReason;
15
+ error?: string;
16
+ }
17
+ export interface CheckIntegrityOptions {
18
+ /** Treat emulator/simulator signals as compromising. Default false (reported, but status stays 'clean'). */
19
+ treatEmulatorAsCompromised?: boolean;
20
+ /** Resolve as 'unknown' with reason 'timeout' if native does not answer in time. Default 10000 ms. */
21
+ timeoutMs?: number;
22
+ }
23
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;AAElE,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,MAAM,GACN,SAAS,GACT,UAAU,GACV,UAAU,GACV,QAAQ,GACR,aAAa,CAAC;AAElB,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,cAAc,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,GACrB,sBAAsB,GACtB,2BAA2B,GAC3B,cAAc,GACd,SAAS,GACT,YAAY,GACZ,iBAAiB,CAAC;AAEtB,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,OAAO,QAAQ,CAAC,EAAE,CAAC;IAC7B,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,4GAA4G;IAC5G,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
@@ -0,0 +1,10 @@
1
+ import type { CheckIntegrityOptions, IntegrityResult, IntegrityStatus, Signal } from './types.js';
2
+ export type UseDeviceIntegrityResult = {
3
+ status: IntegrityStatus;
4
+ signals: Signal[];
5
+ result: IntegrityResult | null;
6
+ loading: boolean;
7
+ refresh: () => Promise<IntegrityResult>;
8
+ };
9
+ export declare function useDeviceIntegrity(options?: CheckIntegrityOptions): UseDeviceIntegrityResult;
10
+ //# sourceMappingURL=useDeviceIntegrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDeviceIntegrity.d.ts","sourceRoot":"","sources":["../../../src/useDeviceIntegrity.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,MAAM,EACP,MAAM,YAAS,CAAC;AAEjB,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAC;CACzC,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,qBAA0B,GAClC,wBAAwB,CAuE1B"}
package/package.json ADDED
@@ -0,0 +1,210 @@
1
+ {
2
+ "name": "react-native-device-integrity",
3
+ "version": "0.1.0",
4
+ "description": "Jailbreak, root, hooking, debugger and emulator detection for React Native and Expo",
5
+ "main": "./lib/module/index.js",
6
+ "types": "./lib/typescript/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "react-native-device-integrity-source": "./src/index.tsx",
10
+ "types": "./lib/typescript/src/index.d.ts",
11
+ "default": "./lib/module/index.js"
12
+ },
13
+ "./package.json": "./package.json"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "lib",
18
+ "android",
19
+ "ios",
20
+ "cpp",
21
+ "app.plugin.js",
22
+ "plugin/build",
23
+ "*.podspec",
24
+ "react-native.config.js",
25
+ "!ios/build",
26
+ "!android/build",
27
+ "!android/app/build",
28
+ "!android/gradle",
29
+ "!android/gradlew",
30
+ "!android/gradlew.bat",
31
+ "!android/local.properties",
32
+ "!**/__tests__",
33
+ "!**/__fixtures__",
34
+ "!**/__mocks__",
35
+ "!**/.*"
36
+ ],
37
+ "scripts": {
38
+ "example": "yarn workspace react-native-device-integrity-example",
39
+ "clean": "del-cli lib",
40
+ "prepare": "bob build && yarn build:plugin",
41
+ "build:plugin": "tsc -p plugin",
42
+ "typecheck": "tsc && tsc -p plugin --noEmit",
43
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
44
+ "test": "jest",
45
+ "release": "release-it --only-version"
46
+ },
47
+ "keywords": [
48
+ "react-native",
49
+ "ios",
50
+ "android",
51
+ "expo",
52
+ "expo-config-plugin"
53
+ ],
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/RRigueira/react-native-device-integrity.git"
57
+ },
58
+ "author": "Rui Rigueira <14238908+RRigueira@users.noreply.github.com> (https://github.com/RRigueira)",
59
+ "license": "MIT",
60
+ "bugs": {
61
+ "url": "https://github.com/RRigueira/react-native-device-integrity/issues"
62
+ },
63
+ "homepage": "https://github.com/RRigueira/react-native-device-integrity#readme",
64
+ "publishConfig": {
65
+ "registry": "https://registry.npmjs.org/",
66
+ "access": "public",
67
+ "provenance": true
68
+ },
69
+ "devDependencies": {
70
+ "@commitlint/cli": "^21.2.3",
71
+ "@commitlint/config-conventional": "^21.2.3",
72
+ "@eslint/compat": "^2.1.0",
73
+ "@eslint/eslintrc": "^3.3.6",
74
+ "@eslint/js": "^9.39.5",
75
+ "@react-native/babel-preset": "0.86.2",
76
+ "@react-native/eslint-config": "0.86.2",
77
+ "@react-native/jest-preset": "0.86.3",
78
+ "@release-it/conventional-changelog": "^12.0.2",
79
+ "@testing-library/react-native": "^13.3.3",
80
+ "@types/jest": "^30.0.0",
81
+ "@types/node": "^26.6.3",
82
+ "@types/react": "^19.2.0",
83
+ "@types/react-test-renderer": "^19",
84
+ "del-cli": "^7.0.0",
85
+ "eslint": "^9.39.5",
86
+ "eslint-config-prettier": "^10.1.8",
87
+ "eslint-plugin-ft-flow": "^3.0.11",
88
+ "eslint-plugin-prettier": "^5.5.6",
89
+ "expo": "~57.0.26",
90
+ "jest": "^30.5.2",
91
+ "lefthook": "^2.1.14",
92
+ "prettier": "^3.9.6",
93
+ "react": "19.2.3",
94
+ "react-native": "0.86.3",
95
+ "react-native-builder-bob": "^0.43.1",
96
+ "react-test-renderer": "19.2.3",
97
+ "release-it": "^21.1.0",
98
+ "turbo": "^2.10.8",
99
+ "typescript": "^6.0.3"
100
+ },
101
+ "peerDependencies": {
102
+ "expo": ">=52",
103
+ "react": "*",
104
+ "react-native": "*"
105
+ },
106
+ "peerDependenciesMeta": {
107
+ "expo": {
108
+ "optional": true
109
+ }
110
+ },
111
+ "workspaces": [
112
+ "example"
113
+ ],
114
+ "packageManager": "yarn@4.11.0",
115
+ "react-native-builder-bob": {
116
+ "source": "src",
117
+ "output": "lib",
118
+ "targets": [
119
+ [
120
+ "module",
121
+ {
122
+ "esm": true
123
+ }
124
+ ],
125
+ [
126
+ "typescript",
127
+ {
128
+ "project": "tsconfig.build.json"
129
+ }
130
+ ]
131
+ ]
132
+ },
133
+ "codegenConfig": {
134
+ "name": "DeviceIntegritySpec",
135
+ "type": "modules",
136
+ "jsSrcsDir": "src",
137
+ "android": {
138
+ "javaPackageName": "com.deviceintegrity"
139
+ }
140
+ },
141
+ "prettier": {
142
+ "quoteProps": "consistent",
143
+ "singleQuote": true,
144
+ "tabWidth": 2,
145
+ "trailingComma": "es5",
146
+ "useTabs": false
147
+ },
148
+ "jest": {
149
+ "preset": "react-native",
150
+ "modulePathIgnorePatterns": [
151
+ "<rootDir>/example/node_modules",
152
+ "<rootDir>/lib/",
153
+ "<rootDir>/plugin/build"
154
+ ],
155
+ "testPathIgnorePatterns": [
156
+ "<rootDir>/plugin/build"
157
+ ],
158
+ "collectCoverageFrom": [
159
+ "src/**/*.{ts,tsx}",
160
+ "!src/**/__tests__/**",
161
+ "!src/NativeDeviceIntegrity.ts",
162
+ "!src/types.ts",
163
+ "plugin/src/**/*.ts",
164
+ "!plugin/src/**/__tests__/**"
165
+ ],
166
+ "coverageThreshold": {
167
+ "global": {
168
+ "statements": 95,
169
+ "branches": 90,
170
+ "functions": 85,
171
+ "lines": 95
172
+ }
173
+ }
174
+ },
175
+ "commitlint": {
176
+ "extends": [
177
+ "@commitlint/config-conventional"
178
+ ]
179
+ },
180
+ "release-it": {
181
+ "git": {
182
+ "commitMessage": "chore: release ${version}",
183
+ "tagName": "v${version}",
184
+ "requireBranch": "main",
185
+ "requireCleanWorkingDir": true
186
+ },
187
+ "npm": {
188
+ "publish": false
189
+ },
190
+ "github": {
191
+ "release": true
192
+ },
193
+ "plugins": {
194
+ "@release-it/conventional-changelog": {
195
+ "preset": {
196
+ "name": "angular"
197
+ },
198
+ "infile": "CHANGELOG.md"
199
+ }
200
+ }
201
+ },
202
+ "create-react-native-library": {
203
+ "type": "turbo-module",
204
+ "languages": "kotlin-objc",
205
+ "tools": [
206
+ "eslint"
207
+ ],
208
+ "version": "0.63.1"
209
+ }
210
+ }
@@ -0,0 +1,19 @@
1
+ import { type ConfigPlugin, type InfoPlist } from 'expo/config-plugins';
2
+ export declare const JAILBREAK_URL_SCHEMES: readonly ["cydia", "sileo", "zbra", "filza", "undecimus", "activator"];
3
+ export type DeviceIntegrityPluginProps = {
4
+ ios?: {
5
+ /** When false, skip Info.plist changes. Default true. */
6
+ jailbreakUrlSchemes?: boolean;
7
+ };
8
+ };
9
+ /**
10
+ * Merge jailbreak URL schemes into LSApplicationQueriesSchemes.
11
+ * Keeps existing entries/order, appends missing ones, never removes.
12
+ *
13
+ * Android needs nothing — the library AndroidManifest `<queries>` merges
14
+ * automatically via the Gradle manifest merger.
15
+ */
16
+ export declare function addJailbreakQueriesSchemes(infoPlist: InfoPlist): InfoPlist;
17
+ export declare const withDeviceIntegrity: ConfigPlugin<DeviceIntegrityPluginProps | void>;
18
+ declare const _default: ConfigPlugin<void | DeviceIntegrityPluginProps>;
19
+ export default _default;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withDeviceIntegrity = exports.JAILBREAK_URL_SCHEMES = void 0;
4
+ exports.addJailbreakQueriesSchemes = addJailbreakQueriesSchemes;
5
+ const config_plugins_1 = require("expo/config-plugins");
6
+ // keep in sync with ios/DIIntegrityChecks.m hasJailbreakURLSchemes
7
+ exports.JAILBREAK_URL_SCHEMES = [
8
+ 'cydia',
9
+ 'sileo',
10
+ 'zbra',
11
+ 'filza',
12
+ 'undecimus',
13
+ 'activator',
14
+ ];
15
+ /**
16
+ * Merge jailbreak URL schemes into LSApplicationQueriesSchemes.
17
+ * Keeps existing entries/order, appends missing ones, never removes.
18
+ *
19
+ * Android needs nothing — the library AndroidManifest `<queries>` merges
20
+ * automatically via the Gradle manifest merger.
21
+ */
22
+ function addJailbreakQueriesSchemes(infoPlist) {
23
+ const existing = infoPlist.LSApplicationQueriesSchemes;
24
+ const current = Array.isArray(existing) ? existing.map(String) : [];
25
+ const seen = new Set(current);
26
+ for (const scheme of exports.JAILBREAK_URL_SCHEMES) {
27
+ if (!seen.has(scheme)) {
28
+ current.push(scheme);
29
+ seen.add(scheme);
30
+ }
31
+ }
32
+ return {
33
+ ...infoPlist,
34
+ LSApplicationQueriesSchemes: current,
35
+ };
36
+ }
37
+ const withDeviceIntegrity = (config, props) => {
38
+ var _a;
39
+ if (((_a = props === null || props === void 0 ? void 0 : props.ios) === null || _a === void 0 ? void 0 : _a.jailbreakUrlSchemes) === false) {
40
+ return config;
41
+ }
42
+ return (0, config_plugins_1.withInfoPlist)(config, (modConfig) => {
43
+ modConfig.modResults = addJailbreakQueriesSchemes(modConfig.modResults);
44
+ return modConfig;
45
+ });
46
+ };
47
+ exports.withDeviceIntegrity = withDeviceIntegrity;
48
+ const pkg = require('../../package.json');
49
+ exports.default = (0, config_plugins_1.createRunOncePlugin)(exports.withDeviceIntegrity, pkg.name, pkg.version);
@@ -0,0 +1,19 @@
1
+ import { TurboModuleRegistry, type TurboModule } from 'react-native';
2
+
3
+ export type NativeSignal = {
4
+ id: string;
5
+ category: string;
6
+ description: string;
7
+ };
8
+
9
+ export type NativeIntegrityReport = {
10
+ completed: boolean;
11
+ reason?: string;
12
+ signals: NativeSignal[];
13
+ };
14
+
15
+ export interface Spec extends TurboModule {
16
+ checkIntegrity(): Promise<NativeIntegrityReport>;
17
+ }
18
+
19
+ export default TurboModuleRegistry.get<Spec>('DeviceIntegrity');
@@ -0,0 +1,88 @@
1
+ import { Platform } from 'react-native';
2
+ import NativeDeviceIntegrity from './NativeDeviceIntegrity';
3
+ import { resolveStatus } from './resolveStatus';
4
+ import type { CheckIntegrityOptions, IntegrityResult } from './types';
5
+
6
+ const DEFAULT_TIMEOUT_MS = 10_000;
7
+
8
+ class IntegrityTimeoutError extends Error {
9
+ constructor() {
10
+ super('Device integrity check timed out');
11
+ this.name = 'IntegrityTimeoutError';
12
+ }
13
+ }
14
+
15
+ function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
16
+ return new Promise<T>((resolve, reject) => {
17
+ const timer = setTimeout(() => {
18
+ reject(new IntegrityTimeoutError());
19
+ }, timeoutMs);
20
+
21
+ promise.then(
22
+ (value) => {
23
+ clearTimeout(timer);
24
+ resolve(value);
25
+ },
26
+ (error: unknown) => {
27
+ clearTimeout(timer);
28
+ reject(error);
29
+ }
30
+ );
31
+ });
32
+ }
33
+
34
+ export async function checkIntegrity(
35
+ options: CheckIntegrityOptions = {}
36
+ ): Promise<IntegrityResult> {
37
+ if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
38
+ return {
39
+ status: 'unknown',
40
+ signals: [],
41
+ platform: Platform.OS,
42
+ reason: 'unsupported_platform',
43
+ };
44
+ }
45
+
46
+ if (NativeDeviceIntegrity == null) {
47
+ return {
48
+ status: 'unknown',
49
+ signals: [],
50
+ platform: Platform.OS,
51
+ reason: 'native_module_unavailable',
52
+ };
53
+ }
54
+
55
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
56
+
57
+ try {
58
+ const report = await withTimeout(
59
+ NativeDeviceIntegrity.checkIntegrity(),
60
+ timeoutMs
61
+ );
62
+ return resolveStatus(report, options, Platform.OS);
63
+ } catch (error) {
64
+ if (error instanceof IntegrityTimeoutError) {
65
+ return {
66
+ status: 'unknown',
67
+ signals: [],
68
+ platform: Platform.OS,
69
+ reason: 'timeout',
70
+ };
71
+ }
72
+
73
+ const message =
74
+ error instanceof Error
75
+ ? error.message
76
+ : typeof error === 'string'
77
+ ? error
78
+ : 'Native integrity check failed';
79
+
80
+ return {
81
+ status: 'unknown',
82
+ signals: [],
83
+ platform: Platform.OS,
84
+ reason: 'native_error',
85
+ error: message,
86
+ };
87
+ }
88
+ }
@@ -0,0 +1,16 @@
1
+ import { Platform } from 'react-native';
2
+ import type { CheckIntegrityOptions, IntegrityResult } from './types';
3
+
4
+ /**
5
+ * Web / unsupported-platform entry. Must not import the native TurboModule.
6
+ */
7
+ export async function checkIntegrity(
8
+ _options?: CheckIntegrityOptions
9
+ ): Promise<IntegrityResult> {
10
+ return {
11
+ status: 'unknown',
12
+ signals: [],
13
+ platform: Platform.OS,
14
+ reason: 'unsupported_platform',
15
+ };
16
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,10 @@
1
+ export { checkIntegrity } from './checkIntegrity';
2
+ export { useDeviceIntegrity } from './useDeviceIntegrity';
3
+ export type {
4
+ CheckIntegrityOptions,
5
+ IntegrityResult,
6
+ IntegrityStatus,
7
+ Signal,
8
+ SignalCategory,
9
+ UnknownReason,
10
+ } from './types';