react-native-device-integrity 0.1.0 → 0.2.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 (44) hide show
  1. package/README.md +138 -11
  2. package/android/src/main/java/com/deviceintegrity/DeviceIntegrityModule.kt +68 -2
  3. package/android/src/main/java/com/deviceintegrity/IntegrityChecks.kt +5 -1
  4. package/android/src/main/java/com/deviceintegrity/TamperChecks.kt +158 -0
  5. package/ios/DIIntegrityChecks.h +7 -1
  6. package/ios/DIIntegrityChecks.m +9 -1
  7. package/ios/DITamperChecks.h +27 -0
  8. package/ios/DITamperChecks.m +304 -0
  9. package/ios/DeviceIntegrity.mm +56 -4
  10. package/jest/index.d.ts +39 -0
  11. package/jest/index.js +100 -0
  12. package/lib/module/NativeDeviceIntegrity.js.map +1 -1
  13. package/lib/module/checkIntegrity.js +2 -0
  14. package/lib/module/checkIntegrity.js.map +1 -1
  15. package/lib/module/checkIntegrity.native.js +83 -6
  16. package/lib/module/checkIntegrity.native.js.map +1 -1
  17. package/lib/module/index.js +1 -0
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/resolveStatus.js +50 -5
  20. package/lib/module/resolveStatus.js.map +1 -1
  21. package/lib/module/types.js +4 -1
  22. package/lib/module/types.js.map +1 -1
  23. package/lib/module/useDeviceIntegrity.js +21 -7
  24. package/lib/module/useDeviceIntegrity.js.map +1 -1
  25. package/lib/typescript/src/NativeDeviceIntegrity.d.ts +2 -2
  26. package/lib/typescript/src/NativeDeviceIntegrity.d.ts.map +1 -1
  27. package/lib/typescript/src/checkIntegrity.d.ts.map +1 -1
  28. package/lib/typescript/src/checkIntegrity.native.d.ts +6 -0
  29. package/lib/typescript/src/checkIntegrity.native.d.ts.map +1 -1
  30. package/lib/typescript/src/index.d.ts +2 -1
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/resolveStatus.d.ts +1 -0
  33. package/lib/typescript/src/resolveStatus.d.ts.map +1 -1
  34. package/lib/typescript/src/types.d.ts +49 -2
  35. package/lib/typescript/src/types.d.ts.map +1 -1
  36. package/lib/typescript/src/useDeviceIntegrity.d.ts.map +1 -1
  37. package/package.json +8 -2
  38. package/src/NativeDeviceIntegrity.ts +5 -2
  39. package/src/checkIntegrity.native.ts +112 -15
  40. package/src/checkIntegrity.ts +2 -0
  41. package/src/index.tsx +5 -0
  42. package/src/resolveStatus.ts +69 -12
  43. package/src/types.ts +76 -2
  44. package/src/useDeviceIntegrity.ts +22 -7
@@ -0,0 +1,304 @@
1
+ // Independent implementation informed by OWASP MASTG (MASVS-RESILIENCE-2
2
+ // app integrity); no code copied.
3
+
4
+ #import "DITamperChecks.h"
5
+
6
+ #import <Security/Security.h>
7
+ #import <mach-o/dyld.h>
8
+ #import <mach-o/loader.h>
9
+ #import <string.h>
10
+
11
+ @implementation DITamperChecks
12
+
13
+ + (void)addSignalId:(NSString *)signalId
14
+ category:(NSString *)category
15
+ description:(NSString *)description
16
+ signals:(NSMutableArray<NSDictionary *> *)signals
17
+ seenIds:(NSMutableSet<NSString *> *)seenIds
18
+ {
19
+ if ([seenIds containsObject:signalId]) {
20
+ return;
21
+ }
22
+ [seenIds addObject:signalId];
23
+ [signals addObject:@{
24
+ @"id" : signalId,
25
+ @"category" : category,
26
+ @"description" : description,
27
+ }];
28
+ }
29
+
30
+ + (void)runWithConfig:(NSDictionary *)config
31
+ signals:(NSMutableArray<NSDictionary *> *)signals
32
+ seenIds:(NSMutableSet<NSString *> *)seenIds
33
+ completed:(BOOL *)completed
34
+ {
35
+ if (config == nil || config.count == 0) {
36
+ return;
37
+ }
38
+
39
+ #if TARGET_OS_SIMULATOR
40
+ // Simulator: App Store encryption and Team ID from keychain access groups
41
+ // are not meaningful on the host Mac — skip by design (not incomplete).
42
+ (void)signals;
43
+ (void)seenIds;
44
+ (void)completed;
45
+ return;
46
+ #else
47
+
48
+ NSArray *expectedTeamIds = config[@"expectedTeamIds"];
49
+ if ([expectedTeamIds isKindOfClass:[NSArray class]] && expectedTeamIds.count == 0) {
50
+ // Configured without a usable Team ID: the check cannot run.
51
+ if (completed != NULL) {
52
+ *completed = NO;
53
+ }
54
+ } else if ([expectedTeamIds isKindOfClass:[NSArray class]]) {
55
+ @try {
56
+ BOOL couldNotRun = NO;
57
+ NSString *teamId = [self currentTeamIdCouldNotRun:&couldNotRun];
58
+ if (couldNotRun) {
59
+ if (completed != NULL) {
60
+ *completed = NO;
61
+ }
62
+ } else if (teamId == nil || ![self teamId:teamId matchesExpected:expectedTeamIds]) {
63
+ [self addSignalId:@"tamper_team_id_mismatch"
64
+ category:@"tamper"
65
+ description:@"The app Team ID does not match an expected value"
66
+ signals:signals
67
+ seenIds:seenIds];
68
+ }
69
+ } @catch (__unused NSException *exception) {
70
+ if (completed != NULL) {
71
+ *completed = NO;
72
+ }
73
+ }
74
+ }
75
+
76
+ NSNumber *requireEncrypted = config[@"requireEncryptedBinary"];
77
+ if ([requireEncrypted isKindOfClass:[NSNumber class]] && [requireEncrypted boolValue]) {
78
+ @try {
79
+ if ([self isMainExecutableDecryptedOrMissingEncryption]) {
80
+ [self addSignalId:@"tamper_binary_decrypted"
81
+ category:@"tamper"
82
+ description:@"The main executable does not appear to be encrypted"
83
+ signals:signals
84
+ seenIds:seenIds];
85
+ }
86
+ } @catch (__unused NSException *exception) {
87
+ if (completed != NULL) {
88
+ *completed = NO;
89
+ }
90
+ }
91
+ }
92
+ #endif
93
+ }
94
+
95
+ #pragma mark - Team ID (keychain access group)
96
+
97
+ /*
98
+ * Public-API Team ID lookup: a Keychain generic-password item's
99
+ * kSecAttrAccessGroup is of the form "TEAMID.<app-id>". Adding (if needed)
100
+ * and querying such an item, then reading that attribute, yields the Team
101
+ * ID prefix without private APIs. Keychain APIs are not on Apple's
102
+ * required-reason API list. The temporary item is deleted after the query.
103
+ */
104
+ + (nullable NSString *)currentTeamIdCouldNotRun:(BOOL *)couldNotRun
105
+ {
106
+ if (couldNotRun != NULL) {
107
+ *couldNotRun = NO;
108
+ }
109
+
110
+ NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
111
+ if (bundleId.length == 0) {
112
+ bundleId = @"unknown";
113
+ }
114
+ NSString *service =
115
+ [NSString stringWithFormat:@"%@.deviceintegrity.teamid", bundleId];
116
+
117
+ NSDictionary *query = @{
118
+ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
119
+ (__bridge id)kSecAttrService : service,
120
+ (__bridge id)kSecReturnAttributes : @YES,
121
+ };
122
+
123
+ CFTypeRef result = NULL;
124
+ OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
125
+
126
+ if (status == errSecItemNotFound) {
127
+ NSDictionary *add = @{
128
+ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
129
+ (__bridge id)kSecAttrService : service,
130
+ (__bridge id)kSecAttrAccessible :
131
+ (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
132
+ (__bridge id)kSecValueData : [@"di" dataUsingEncoding:NSUTF8StringEncoding],
133
+ };
134
+ status = SecItemAdd((__bridge CFDictionaryRef)add, NULL);
135
+ if (status != errSecSuccess) {
136
+ if (couldNotRun != NULL) {
137
+ *couldNotRun = YES;
138
+ }
139
+ return nil;
140
+ }
141
+ result = NULL;
142
+ status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
143
+ }
144
+
145
+ if (status != errSecSuccess || result == NULL) {
146
+ if (couldNotRun != NULL) {
147
+ *couldNotRun = YES;
148
+ }
149
+ [self deleteTeamIdItemWithService:service];
150
+ return nil;
151
+ }
152
+
153
+ NSDictionary *attrs = (__bridge_transfer NSDictionary *)result;
154
+ NSString *accessGroup = attrs[(__bridge id)kSecAttrAccessGroup];
155
+
156
+ [self deleteTeamIdItemWithService:service];
157
+
158
+ if (![accessGroup isKindOfClass:[NSString class]] || accessGroup.length == 0) {
159
+ if (couldNotRun != NULL) {
160
+ *couldNotRun = YES;
161
+ }
162
+ return nil;
163
+ }
164
+
165
+ NSRange dot = [accessGroup rangeOfString:@"."];
166
+ if (dot.location == NSNotFound || dot.location == 0) {
167
+ if (couldNotRun != NULL) {
168
+ *couldNotRun = YES;
169
+ }
170
+ return nil;
171
+ }
172
+
173
+ NSString *prefix = [[accessGroup substringToIndex:dot.location]
174
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
175
+ if (prefix.length == 0) {
176
+ if (couldNotRun != NULL) {
177
+ *couldNotRun = YES;
178
+ }
179
+ return nil;
180
+ }
181
+ return prefix;
182
+ }
183
+
184
+ + (void)deleteTeamIdItemWithService:(NSString *)service
185
+ {
186
+ NSDictionary *del = @{
187
+ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
188
+ (__bridge id)kSecAttrService : service,
189
+ };
190
+ SecItemDelete((__bridge CFDictionaryRef)del);
191
+ }
192
+
193
+ + (BOOL)teamId:(NSString *)teamId matchesExpected:(NSArray *)expectedTeamIds
194
+ {
195
+ for (id raw in expectedTeamIds) {
196
+ if (![raw isKindOfClass:[NSString class]]) {
197
+ continue;
198
+ }
199
+ NSString *expected = [(NSString *)raw
200
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
201
+ if (expected.length == 0) {
202
+ continue;
203
+ }
204
+ // Team IDs are uppercase alphanumerics; compare case-sensitively.
205
+ if ([teamId isEqualToString:expected]) {
206
+ return YES;
207
+ }
208
+ }
209
+ return NO;
210
+ }
211
+
212
+ #pragma mark - Binary encryption (LC_ENCRYPTION_INFO)
213
+
214
+ + (const struct mach_header *)mainExecutableHeader
215
+ {
216
+ NSString *execPath = [[NSBundle mainBundle] executablePath];
217
+ const char *execPathC = execPath.fileSystemRepresentation;
218
+
219
+ // Prefer image 0 when its path matches the main bundle executable.
220
+ const char *image0Name = _dyld_get_image_name(0);
221
+ if (image0Name != NULL && execPathC != NULL && strcmp(image0Name, execPathC) == 0) {
222
+ return _dyld_get_image_header(0);
223
+ }
224
+
225
+ // Otherwise find the MH_EXECUTE image (or path match).
226
+ uint32_t count = _dyld_image_count();
227
+ for (uint32_t i = 0; i < count; i++) {
228
+ const struct mach_header *header = _dyld_get_image_header(i);
229
+ if (header == NULL) {
230
+ continue;
231
+ }
232
+ if (header->filetype == MH_EXECUTE) {
233
+ return header;
234
+ }
235
+ const char *name = _dyld_get_image_name(i);
236
+ if (name != NULL && execPathC != NULL && strcmp(name, execPathC) == 0) {
237
+ return header;
238
+ }
239
+ }
240
+
241
+ // Last resort: image 0 is conventionally the main executable.
242
+ return _dyld_get_image_header(0);
243
+ }
244
+
245
+ + (BOOL)isMainExecutableDecryptedOrMissingEncryption
246
+ {
247
+ const struct mach_header *header = [self mainExecutableHeader];
248
+ if (header == NULL) {
249
+ // Cannot inspect — treat as incomplete via exception path; return NO here
250
+ // and let the caller mark incomplete only on throw. Missing header means
251
+ // we could not run the check.
252
+ @throw [NSException exceptionWithName:@"DITamperChecksException"
253
+ reason:@"main executable header unavailable"
254
+ userInfo:nil];
255
+ }
256
+
257
+ BOOL is64 = (header->magic == MH_MAGIC_64 || header->magic == MH_CIGAM_64);
258
+ BOOL is32 = (header->magic == MH_MAGIC || header->magic == MH_CIGAM);
259
+ if (!is64 && !is32) {
260
+ @throw [NSException exceptionWithName:@"DITamperChecksException"
261
+ reason:@"unrecognized mach-o magic"
262
+ userInfo:nil];
263
+ }
264
+
265
+ uint32_t ncmds = 0;
266
+ const struct load_command *cmd = NULL;
267
+
268
+ if (is64) {
269
+ const struct mach_header_64 *h64 = (const struct mach_header_64 *)header;
270
+ ncmds = h64->ncmds;
271
+ cmd = (const struct load_command *)(h64 + 1);
272
+ } else {
273
+ ncmds = header->ncmds;
274
+ cmd = (const struct load_command *)(header + 1);
275
+ }
276
+
277
+ BOOL found = NO;
278
+ for (uint32_t i = 0; i < ncmds; i++) {
279
+ if (cmd->cmdsize == 0) {
280
+ break;
281
+ }
282
+ if (cmd->cmd == LC_ENCRYPTION_INFO_64) {
283
+ const struct encryption_info_command_64 *enc =
284
+ (const struct encryption_info_command_64 *)cmd;
285
+ found = YES;
286
+ if (enc->cryptid == 0) {
287
+ return YES;
288
+ }
289
+ } else if (cmd->cmd == LC_ENCRYPTION_INFO) {
290
+ const struct encryption_info_command *enc =
291
+ (const struct encryption_info_command *)cmd;
292
+ found = YES;
293
+ if (enc->cryptid == 0) {
294
+ return YES;
295
+ }
296
+ }
297
+ cmd = (const struct load_command *)((const uint8_t *)cmd + cmd->cmdsize);
298
+ }
299
+
300
+ // Missing encryption load command ⇒ treat as decrypted / not encrypted.
301
+ return !found;
302
+ }
303
+
304
+ @end
@@ -14,13 +14,65 @@
14
14
  return @"DeviceIntegrity";
15
15
  }
16
16
 
17
- - (void)checkIntegrity:(RCTPromiseResolveBlock)resolve
17
+ /**
18
+ * Defensively parse options[@"ios"]. Wrong types / empty arrays ⇒ not
19
+ * configured (check does not run, report is not incomplete).
20
+ */
21
+ static NSDictionary *DIParseIOSConfig(NSDictionary *options)
22
+ {
23
+ if (![options isKindOfClass:[NSDictionary class]]) {
24
+ return nil;
25
+ }
26
+
27
+ id iosRaw = options[@"ios"];
28
+ if (![iosRaw isKindOfClass:[NSDictionary class]]) {
29
+ return nil;
30
+ }
31
+ NSDictionary *ios = (NSDictionary *)iosRaw;
32
+ NSMutableDictionary *config = [NSMutableDictionary dictionary];
33
+
34
+ id teamIdsRaw = ios[@"expectedTeamIds"];
35
+ if ([teamIdsRaw isKindOfClass:[NSArray class]]) {
36
+ NSMutableArray<NSString *> *valid = [NSMutableArray array];
37
+ for (id item in (NSArray *)teamIdsRaw) {
38
+ if (![item isKindOfClass:[NSString class]]) {
39
+ continue;
40
+ }
41
+ NSString *trimmed = [(NSString *)item
42
+ stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
43
+ if (trimmed.length > 0) {
44
+ [valid addObject:trimmed];
45
+ }
46
+ }
47
+ // Keep an empty list when values were given but none are usable, so the
48
+ // check reports incomplete instead of silently not running.
49
+ if (valid.count > 0 || ((NSArray *)teamIdsRaw).count > 0) {
50
+ config[@"expectedTeamIds"] = [valid copy];
51
+ }
52
+ }
53
+
54
+ id requireEncRaw = ios[@"requireEncryptedBinary"];
55
+ if ([requireEncRaw isKindOfClass:[NSNumber class]] &&
56
+ [(NSNumber *)requireEncRaw boolValue]) {
57
+ config[@"requireEncryptedBinary"] = @YES;
58
+ }
59
+
60
+ if (config.count == 0) {
61
+ return nil;
62
+ }
63
+ return [config copy];
64
+ }
65
+
66
+ - (void)checkIntegrity:(NSDictionary *)options
67
+ resolve:(RCTPromiseResolveBlock)resolve
18
68
  reject:(RCTPromiseRejectBlock)reject
19
69
  {
20
70
  @try {
21
- [DIIntegrityChecks runWithCompletion:^(NSDictionary *report) {
22
- resolve(report);
23
- }];
71
+ NSDictionary *iosConfig = DIParseIOSConfig(options);
72
+ [DIIntegrityChecks runWithIOSConfig:iosConfig
73
+ completion:^(NSDictionary *report) {
74
+ resolve(report);
75
+ }];
24
76
  } @catch (NSException *exception) {
25
77
  reject(@"E_INTEGRITY", exception.reason ?: @"Unexpected integrity check failure", nil);
26
78
  }
@@ -0,0 +1,39 @@
1
+ import type {
2
+ CheckIntegrityOptions,
3
+ IntegrityResult,
4
+ IntegrityStatus,
5
+ Signal,
6
+ SignalId,
7
+ } from '../src/types';
8
+
9
+ export type {
10
+ CheckIntegrityOptions,
11
+ IntegrityResult,
12
+ IntegrityStatus,
13
+ Signal,
14
+ SignalId,
15
+ };
16
+
17
+ export declare const SIGNAL_IDS: readonly SignalId[];
18
+
19
+ export type UseDeviceIntegrityResult = {
20
+ status: IntegrityStatus;
21
+ signals: Signal[];
22
+ result: IntegrityResult;
23
+ loading: false;
24
+ refresh: () => Promise<IntegrityResult>;
25
+ };
26
+
27
+ export declare function checkIntegrity(
28
+ options?: CheckIntegrityOptions
29
+ ): Promise<IntegrityResult>;
30
+
31
+ export declare function useDeviceIntegrity(
32
+ options?: CheckIntegrityOptions
33
+ ): UseDeviceIntegrityResult;
34
+
35
+ export declare function setMockIntegrityResult(
36
+ partial: Partial<IntegrityResult>
37
+ ): void;
38
+
39
+ export declare function resetMockIntegrity(): void;
package/jest/index.js ADDED
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Jest mock for `react-native-device-integrity`.
5
+ *
6
+ * Usage in a test file:
7
+ *
8
+ * jest.mock(
9
+ * 'react-native-device-integrity',
10
+ * () => require('react-native-device-integrity/jest')
11
+ * );
12
+ */
13
+
14
+ const SIGNAL_IDS = Object.freeze([
15
+ 'simulator',
16
+ 'jailbreak_files',
17
+ 'jailbreak_url_schemes',
18
+ 'jailbreak_symlinks',
19
+ 'jailbreak_writable_system',
20
+ 'hooking_libraries',
21
+ 'hooking_dyld_insert',
22
+ 'debugger_attached',
23
+ 'emulator',
24
+ 'root_su_binary',
25
+ 'root_management_apps',
26
+ 'root_magisk_files',
27
+ 'root_test_keys',
28
+ 'root_dangerous_props',
29
+ 'root_rw_system',
30
+ 'hooking_frida',
31
+ 'hooking_xposed',
32
+ 'tamper_signature_mismatch',
33
+ 'tamper_untrusted_installer',
34
+ 'tamper_binary_decrypted',
35
+ 'tamper_team_id_mismatch',
36
+ ]);
37
+
38
+ function defaultResult() {
39
+ return {
40
+ status: 'clean',
41
+ signals: [],
42
+ ignored: [],
43
+ platform: 'ios',
44
+ durationMs: 0,
45
+ };
46
+ }
47
+
48
+ let mockResult = defaultResult();
49
+
50
+ function cloneResult(result) {
51
+ return {
52
+ ...result,
53
+ signals: Array.isArray(result.signals) ? result.signals.slice() : [],
54
+ ignored: Array.isArray(result.ignored) ? result.ignored.slice() : [],
55
+ };
56
+ }
57
+
58
+ function setMockIntegrityResult(partial) {
59
+ mockResult = {
60
+ ...mockResult,
61
+ ...partial,
62
+ signals:
63
+ partial && Object.prototype.hasOwnProperty.call(partial, 'signals')
64
+ ? partial.signals
65
+ : mockResult.signals,
66
+ ignored:
67
+ partial && Object.prototype.hasOwnProperty.call(partial, 'ignored')
68
+ ? partial.ignored
69
+ : mockResult.ignored,
70
+ };
71
+ }
72
+
73
+ function resetMockIntegrity() {
74
+ mockResult = defaultResult();
75
+ }
76
+
77
+ function checkIntegrity() {
78
+ return Promise.resolve(cloneResult(mockResult));
79
+ }
80
+
81
+ function useDeviceIntegrity() {
82
+ const result = cloneResult(mockResult);
83
+ return {
84
+ status: result.status,
85
+ signals: result.signals,
86
+ result,
87
+ loading: false,
88
+ refresh: function refresh() {
89
+ return checkIntegrity();
90
+ },
91
+ };
92
+ }
93
+
94
+ module.exports = {
95
+ SIGNAL_IDS,
96
+ checkIntegrity,
97
+ useDeviceIntegrity,
98
+ setMockIntegrityResult,
99
+ resetMockIntegrity,
100
+ };
@@ -1 +1 @@
1
- {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeDeviceIntegrity.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;AAkBpE,eAAeA,mBAAmB,CAACC,GAAG,CAAO,iBAAiB,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["TurboModuleRegistry","get"],"sourceRoot":"../../src","sources":["NativeDeviceIntegrity.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAoBlD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,iBAAiB,CAAC","ignoreList":[]}
@@ -8,6 +8,8 @@ export async function checkIntegrity(_options) {
8
8
  return {
9
9
  status: 'unknown',
10
10
  signals: [],
11
+ ignored: [],
12
+ durationMs: 0,
11
13
  platform: Platform.OS,
12
14
  reason: 'unsupported_platform'
13
15
  };
@@ -1 +1 @@
1
- {"version":3,"names":["Platform","checkIntegrity","_options","status","signals","platform","OS","reason"],"sourceRoot":"../../src","sources":["checkIntegrity.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AAGvC;AACA;AACA;AACA,OAAO,eAAeC,cAAcA,CAClCC,QAAgC,EACN;EAC1B,OAAO;IACLC,MAAM,EAAE,SAAS;IACjBC,OAAO,EAAE,EAAE;IACXC,QAAQ,EAAEL,QAAQ,CAACM,EAAE;IACrBC,MAAM,EAAE;EACV,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"names":["Platform","checkIntegrity","_options","status","signals","ignored","durationMs","platform","OS","reason"],"sourceRoot":"../../src","sources":["checkIntegrity.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AAGvC;AACA;AACA;AACA,OAAO,eAAeC,cAAcA,CAClCC,QAAgC,EACN;EAC1B,OAAO;IACLC,MAAM,EAAE,SAAS;IACjBC,OAAO,EAAE,EAAE;IACXC,OAAO,EAAE,EAAE;IACXC,UAAU,EAAE,CAAC;IACbC,QAAQ,EAAEP,QAAQ,CAACQ,EAAE;IACrBC,MAAM,EAAE;EACV,CAAC;AACH","ignoreList":[]}
@@ -10,6 +10,13 @@ class IntegrityTimeoutError extends Error {
10
10
  this.name = 'IntegrityTimeoutError';
11
11
  }
12
12
  }
13
+ function nowMs() {
14
+ const perf = globalThis.performance;
15
+ if (perf != null && typeof perf.now === 'function') {
16
+ return perf.now();
17
+ }
18
+ return Date.now();
19
+ }
13
20
  function withTimeout(promise, timeoutMs) {
14
21
  return new Promise((resolve, reject) => {
15
22
  const timer = setTimeout(() => {
@@ -24,11 +31,73 @@ function withTimeout(promise, timeoutMs) {
24
31
  });
25
32
  });
26
33
  }
34
+
35
+ // A blank entry makes native treat the check as configured but unusable,
36
+ // so it reports the run as incomplete instead of skipping the check.
37
+ const UNUSABLE_LIST = [''];
38
+ function stringArray(value) {
39
+ if (value == null) {
40
+ return undefined;
41
+ }
42
+ if (!Array.isArray(value)) {
43
+ return UNUSABLE_LIST;
44
+ }
45
+ const strings = value.filter(item => typeof item === 'string');
46
+ if (value.length > 0 && strings.length === 0) {
47
+ return UNUSABLE_LIST;
48
+ }
49
+ return strings;
50
+ }
51
+
52
+ /**
53
+ * Build the native options bag: only `android` / `ios`, with sanitized fields.
54
+ * Missing fields are omitted. A list that is provided but unusable (wrong type,
55
+ * no strings) is sent as a blank entry so the check fails closed as incomplete.
56
+ */
57
+ export function sanitizeNativeOptions(options) {
58
+ const native = {};
59
+ if (options.android != null && typeof options.android === 'object') {
60
+ const android = {};
61
+ const certs = stringArray(options.android.expectedSigningCertificates);
62
+ if (certs != null) {
63
+ android.expectedSigningCertificates = certs;
64
+ }
65
+ const installers = stringArray(options.android.allowedInstallers);
66
+ if (installers != null) {
67
+ android.allowedInstallers = installers;
68
+ }
69
+ if (Object.keys(android).length > 0) {
70
+ native.android = android;
71
+ }
72
+ }
73
+ if (options.ios != null && typeof options.ios === 'object') {
74
+ const ios = {};
75
+ const teamIds = stringArray(options.ios.expectedTeamIds);
76
+ if (teamIds != null) {
77
+ ios.expectedTeamIds = teamIds;
78
+ }
79
+ if (typeof options.ios.requireEncryptedBinary === 'boolean') {
80
+ ios.requireEncryptedBinary = options.ios.requireEncryptedBinary;
81
+ }
82
+ if (Object.keys(ios).length > 0) {
83
+ native.ios = ios;
84
+ }
85
+ }
86
+ return native;
87
+ }
88
+ function withDuration(result, startedAt) {
89
+ return {
90
+ ...result,
91
+ durationMs: Math.max(0, nowMs() - startedAt)
92
+ };
93
+ }
27
94
  export async function checkIntegrity(options = {}) {
28
95
  if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
29
96
  return {
30
97
  status: 'unknown',
31
98
  signals: [],
99
+ ignored: [],
100
+ durationMs: 0,
32
101
  platform: Platform.OS,
33
102
  reason: 'unsupported_platform'
34
103
  };
@@ -37,31 +106,39 @@ export async function checkIntegrity(options = {}) {
37
106
  return {
38
107
  status: 'unknown',
39
108
  signals: [],
109
+ ignored: [],
110
+ durationMs: 0,
40
111
  platform: Platform.OS,
41
112
  reason: 'native_module_unavailable'
42
113
  };
43
114
  }
44
115
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
116
+ const startedAt = nowMs();
117
+ const nativeOptions = sanitizeNativeOptions(options);
45
118
  try {
46
- const report = await withTimeout(NativeDeviceIntegrity.checkIntegrity(), timeoutMs);
47
- return resolveStatus(report, options, Platform.OS);
119
+ const report = await withTimeout(NativeDeviceIntegrity.checkIntegrity(nativeOptions), timeoutMs);
120
+ return withDuration(resolveStatus(report, options, Platform.OS), startedAt);
48
121
  } catch (error) {
49
122
  if (error instanceof IntegrityTimeoutError) {
50
- return {
123
+ return withDuration({
51
124
  status: 'unknown',
52
125
  signals: [],
126
+ ignored: [],
127
+ durationMs: 0,
53
128
  platform: Platform.OS,
54
129
  reason: 'timeout'
55
- };
130
+ }, startedAt);
56
131
  }
57
132
  const message = error instanceof Error ? error.message : typeof error === 'string' ? error : 'Native integrity check failed';
58
- return {
133
+ return withDuration({
59
134
  status: 'unknown',
60
135
  signals: [],
136
+ ignored: [],
137
+ durationMs: 0,
61
138
  platform: Platform.OS,
62
139
  reason: 'native_error',
63
140
  error: message
64
- };
141
+ }, startedAt);
65
142
  }
66
143
  }
67
144
  //# sourceMappingURL=checkIntegrity.native.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["Platform","NativeDeviceIntegrity","resolveStatus","DEFAULT_TIMEOUT_MS","IntegrityTimeoutError","Error","constructor","name","withTimeout","promise","timeoutMs","Promise","resolve","reject","timer","setTimeout","then","value","clearTimeout","error","checkIntegrity","options","OS","status","signals","platform","reason","report","message"],"sourceRoot":"../../src","sources":["checkIntegrity.native.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AACvC,OAAOC,qBAAqB,MAAM,4BAAyB;AAC3D,SAASC,aAAa,QAAQ,oBAAiB;AAG/C,MAAMC,kBAAkB,GAAG,MAAM;AAEjC,MAAMC,qBAAqB,SAASC,KAAK,CAAC;EACxCC,WAAWA,CAAA,EAAG;IACZ,KAAK,CAAC,kCAAkC,CAAC;IACzC,IAAI,CAACC,IAAI,GAAG,uBAAuB;EACrC;AACF;AAEA,SAASC,WAAWA,CAAIC,OAAmB,EAAEC,SAAiB,EAAc;EAC1E,OAAO,IAAIC,OAAO,CAAI,CAACC,OAAO,EAAEC,MAAM,KAAK;IACzC,MAAMC,KAAK,GAAGC,UAAU,CAAC,MAAM;MAC7BF,MAAM,CAAC,IAAIT,qBAAqB,CAAC,CAAC,CAAC;IACrC,CAAC,EAAEM,SAAS,CAAC;IAEbD,OAAO,CAACO,IAAI,CACTC,KAAK,IAAK;MACTC,YAAY,CAACJ,KAAK,CAAC;MACnBF,OAAO,CAACK,KAAK,CAAC;IAChB,CAAC,EACAE,KAAc,IAAK;MAClBD,YAAY,CAACJ,KAAK,CAAC;MACnBD,MAAM,CAACM,KAAK,CAAC;IACf,CACF,CAAC;EACH,CAAC,CAAC;AACJ;AAEA,OAAO,eAAeC,cAAcA,CAClCC,OAA8B,GAAG,CAAC,CAAC,EACT;EAC1B,IAAIrB,QAAQ,CAACsB,EAAE,KAAK,KAAK,IAAItB,QAAQ,CAACsB,EAAE,KAAK,SAAS,EAAE;IACtD,OAAO;MACLC,MAAM,EAAE,SAAS;MACjBC,OAAO,EAAE,EAAE;MACXC,QAAQ,EAAEzB,QAAQ,CAACsB,EAAE;MACrBI,MAAM,EAAE;IACV,CAAC;EACH;EAEA,IAAIzB,qBAAqB,IAAI,IAAI,EAAE;IACjC,OAAO;MACLsB,MAAM,EAAE,SAAS;MACjBC,OAAO,EAAE,EAAE;MACXC,QAAQ,EAAEzB,QAAQ,CAACsB,EAAE;MACrBI,MAAM,EAAE;IACV,CAAC;EACH;EAEA,MAAMhB,SAAS,GAAGW,OAAO,CAACX,SAAS,IAAIP,kBAAkB;EAEzD,IAAI;IACF,MAAMwB,MAAM,GAAG,MAAMnB,WAAW,CAC9BP,qBAAqB,CAACmB,cAAc,CAAC,CAAC,EACtCV,SACF,CAAC;IACD,OAAOR,aAAa,CAACyB,MAAM,EAAEN,OAAO,EAAErB,QAAQ,CAACsB,EAAE,CAAC;EACpD,CAAC,CAAC,OAAOH,KAAK,EAAE;IACd,IAAIA,KAAK,YAAYf,qBAAqB,EAAE;MAC1C,OAAO;QACLmB,MAAM,EAAE,SAAS;QACjBC,OAAO,EAAE,EAAE;QACXC,QAAQ,EAAEzB,QAAQ,CAACsB,EAAE;QACrBI,MAAM,EAAE;MACV,CAAC;IACH;IAEA,MAAME,OAAO,GACXT,KAAK,YAAYd,KAAK,GAClBc,KAAK,CAACS,OAAO,GACb,OAAOT,KAAK,KAAK,QAAQ,GACvBA,KAAK,GACL,+BAA+B;IAEvC,OAAO;MACLI,MAAM,EAAE,SAAS;MACjBC,OAAO,EAAE,EAAE;MACXC,QAAQ,EAAEzB,QAAQ,CAACsB,EAAE;MACrBI,MAAM,EAAE,cAAc;MACtBP,KAAK,EAAES;IACT,CAAC;EACH;AACF","ignoreList":[]}
1
+ {"version":3,"names":["Platform","NativeDeviceIntegrity","resolveStatus","DEFAULT_TIMEOUT_MS","IntegrityTimeoutError","Error","constructor","name","nowMs","perf","globalThis","performance","now","Date","withTimeout","promise","timeoutMs","Promise","resolve","reject","timer","setTimeout","then","value","clearTimeout","error","UNUSABLE_LIST","stringArray","undefined","Array","isArray","strings","filter","item","length","sanitizeNativeOptions","options","native","android","certs","expectedSigningCertificates","installers","allowedInstallers","Object","keys","ios","teamIds","expectedTeamIds","requireEncryptedBinary","withDuration","result","startedAt","durationMs","Math","max","checkIntegrity","OS","status","signals","ignored","platform","reason","nativeOptions","report","message"],"sourceRoot":"../../src","sources":["checkIntegrity.native.ts"],"mappings":"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","ignoreList":[]}
@@ -2,4 +2,5 @@
2
2
 
3
3
  export { checkIntegrity } from './checkIntegrity';
4
4
  export { useDeviceIntegrity } from "./useDeviceIntegrity.js";
5
+ export { SIGNAL_IDS } from "./types.js";
5
6
  //# sourceMappingURL=index.js.map