react-native-intune 0.1.0-alpha.1

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 (110) hide show
  1. package/LICENSE +27 -0
  2. package/NOTICE +49 -0
  3. package/README.md +164 -0
  4. package/RNIntune.podspec +69 -0
  5. package/android/build.gradle +152 -0
  6. package/android/consumer-rules.pro +28 -0
  7. package/android/src/main/AndroidManifest.xml +2 -0
  8. package/android/src/main/java/com/reactnativeintune/RNIntuneAuth.kt +330 -0
  9. package/android/src/main/java/com/reactnativeintune/RNIntuneAuthCallback.kt +195 -0
  10. package/android/src/main/java/com/reactnativeintune/RNIntuneConfig.kt +63 -0
  11. package/android/src/main/java/com/reactnativeintune/RNIntuneEventQueue.kt +50 -0
  12. package/android/src/main/java/com/reactnativeintune/RNIntuneModule.kt +752 -0
  13. package/android/src/main/java/com/reactnativeintune/RNIntuneMsalConfig.kt +78 -0
  14. package/android/src/main/java/com/reactnativeintune/RNIntuneNotifications.kt +187 -0
  15. package/android/src/main/java/com/reactnativeintune/RNIntunePackage.kt +33 -0
  16. package/android/src/main/java/com/reactnativeintune/RNIntunePolicy.kt +108 -0
  17. package/android/src/main/java/com/reactnativeintune/RNIntuneReset.kt +85 -0
  18. package/android/src/main/java/com/reactnativeintune/RNIntuneResetJournal.kt +140 -0
  19. package/app.plugin.js +10 -0
  20. package/ios/RNIntune.h +20 -0
  21. package/ios/RNIntune.mm +467 -0
  22. package/ios/RNIntuneAuth.h +121 -0
  23. package/ios/RNIntuneAuth.m +390 -0
  24. package/ios/RNIntuneCore.h +150 -0
  25. package/ios/RNIntuneCore.m +535 -0
  26. package/ios/RNIntuneDelegates.h +70 -0
  27. package/ios/RNIntuneDelegates.m +268 -0
  28. package/ios/RNIntunePendingRequests.h +57 -0
  29. package/ios/RNIntunePendingRequests.m +230 -0
  30. package/ios/RNIntunePlistGuard.h +37 -0
  31. package/ios/RNIntunePlistGuard.m +147 -0
  32. package/ios/RNIntunePolicy.h +25 -0
  33. package/ios/RNIntunePolicy.m +112 -0
  34. package/ios/RNIntuneReset.h +34 -0
  35. package/ios/RNIntuneReset.m +93 -0
  36. package/ios/RNIntuneResetJournal.h +69 -0
  37. package/ios/RNIntuneResetJournal.m +106 -0
  38. package/lib/module/NativeIntune.js +20 -0
  39. package/lib/module/NativeIntune.js.map +1 -0
  40. package/lib/module/api/auth.js +66 -0
  41. package/lib/module/api/auth.js.map +1 -0
  42. package/lib/module/api/enrollment.js +104 -0
  43. package/lib/module/api/enrollment.js.map +1 -0
  44. package/lib/module/api/events.js +50 -0
  45. package/lib/module/api/events.js.map +1 -0
  46. package/lib/module/api/lifecycle.js +73 -0
  47. package/lib/module/api/lifecycle.js.map +1 -0
  48. package/lib/module/api/policy.js +23 -0
  49. package/lib/module/api/policy.js.map +1 -0
  50. package/lib/module/api/tokenProvider.js +80 -0
  51. package/lib/module/api/tokenProvider.js.map +1 -0
  52. package/lib/module/index.js +82 -0
  53. package/lib/module/index.js.map +1 -0
  54. package/lib/module/internal/decode.js +153 -0
  55. package/lib/module/internal/decode.js.map +1 -0
  56. package/lib/module/internal/emitter.js +15 -0
  57. package/lib/module/internal/emitter.js.map +1 -0
  58. package/lib/module/internal/errors.js +32 -0
  59. package/lib/module/internal/errors.js.map +1 -0
  60. package/lib/module/package.json +1 -0
  61. package/lib/module/types.js +143 -0
  62. package/lib/module/types.js.map +1 -0
  63. package/lib/typescript/package.json +1 -0
  64. package/lib/typescript/src/NativeIntune.d.ts +120 -0
  65. package/lib/typescript/src/NativeIntune.d.ts.map +1 -0
  66. package/lib/typescript/src/api/auth.d.ts +37 -0
  67. package/lib/typescript/src/api/auth.d.ts.map +1 -0
  68. package/lib/typescript/src/api/enrollment.d.ts +54 -0
  69. package/lib/typescript/src/api/enrollment.d.ts.map +1 -0
  70. package/lib/typescript/src/api/events.d.ts +24 -0
  71. package/lib/typescript/src/api/events.d.ts.map +1 -0
  72. package/lib/typescript/src/api/lifecycle.d.ts +33 -0
  73. package/lib/typescript/src/api/lifecycle.d.ts.map +1 -0
  74. package/lib/typescript/src/api/policy.d.ts +14 -0
  75. package/lib/typescript/src/api/policy.d.ts.map +1 -0
  76. package/lib/typescript/src/api/tokenProvider.d.ts +18 -0
  77. package/lib/typescript/src/api/tokenProvider.d.ts.map +1 -0
  78. package/lib/typescript/src/index.d.ts +67 -0
  79. package/lib/typescript/src/index.d.ts.map +1 -0
  80. package/lib/typescript/src/internal/decode.d.ts +48 -0
  81. package/lib/typescript/src/internal/decode.d.ts.map +1 -0
  82. package/lib/typescript/src/internal/emitter.d.ts +11 -0
  83. package/lib/typescript/src/internal/emitter.d.ts.map +1 -0
  84. package/lib/typescript/src/internal/errors.d.ts +22 -0
  85. package/lib/typescript/src/internal/errors.d.ts.map +1 -0
  86. package/lib/typescript/src/types.d.ts +317 -0
  87. package/lib/typescript/src/types.d.ts.map +1 -0
  88. package/package.json +204 -0
  89. package/plugin/transforms.js +153 -0
  90. package/plugin/withIntune.js +320 -0
  91. package/scripts/cli.mjs +72 -0
  92. package/scripts/doctor.mjs +232 -0
  93. package/scripts/fetch-sdks.mjs +456 -0
  94. package/scripts/lib/checks.mjs +720 -0
  95. package/scripts/lib/project.mjs +196 -0
  96. package/scripts/setup.mjs +233 -0
  97. package/sdk-lock.json +27 -0
  98. package/sdk-versions.json +70 -0
  99. package/src/NativeIntune.ts +130 -0
  100. package/src/api/auth.ts +73 -0
  101. package/src/api/enrollment.ts +112 -0
  102. package/src/api/events.ts +87 -0
  103. package/src/api/lifecycle.ts +75 -0
  104. package/src/api/policy.ts +22 -0
  105. package/src/api/tokenProvider.ts +80 -0
  106. package/src/index.ts +127 -0
  107. package/src/internal/decode.ts +191 -0
  108. package/src/internal/emitter.ts +14 -0
  109. package/src/internal/errors.ts +43 -0
  110. package/src/types.ts +381 -0
@@ -0,0 +1,467 @@
1
+ //
2
+ // Copyright (c) 2026 react-native-intune contributors. MIT.
3
+ //
4
+
5
+ #import "RNIntune.h"
6
+ #import "RNIntuneCore.h"
7
+
8
+ #import <UIKit/UIKit.h>
9
+
10
+ // The only file that may import React (CLAUDE.md rule 7). RCTPresentedViewController()
11
+ // finds the controller MSAL must present its sign-in from, including when the app already
12
+ // has a modal up — which is why the auth layer takes it as a parameter rather than
13
+ // guessing at the key window's root.
14
+ #import <React/RCTUtils.h>
15
+
16
+ /// Rejects with a stable code (SPEC §13.6). RCTPromiseRejectBlock reads `code` off the
17
+ /// NSError's userInfo, so every rejection built by RNIntuneCore arrives in JS with the
18
+ /// documented string rather than a platform error number.
19
+ static void RNIntuneReject(RCTPromiseRejectBlock reject, NSError *error)
20
+ {
21
+ NSString *code = error.userInfo[@"code"] ?: RNIntuneErrorNative;
22
+ reject(code, error.localizedDescription, error);
23
+ }
24
+
25
+ static void RNIntuneRejectNotConfigured(RCTPromiseRejectBlock reject, NSString *method)
26
+ {
27
+ NSError *error = [RNIntuneCore
28
+ errorWithCode:RNIntuneErrorNotConfigured
29
+ message:[NSString stringWithFormat:
30
+ @"%@ was called before configure() resolved.", method]];
31
+ RNIntuneReject(reject, error);
32
+ }
33
+
34
+ /// `authMode: 'external'` means the host app owns MSAL; signing in here would put a
35
+ /// second instance and a second cache in one binary (SPEC §3.2).
36
+ static void RNIntuneRejectExternalAuth(RCTPromiseRejectBlock reject, NSString *method)
37
+ {
38
+ NSError *error = [RNIntuneCore
39
+ errorWithCode:RNIntuneErrorExternalAuthMode
40
+ message:[NSString stringWithFormat:
41
+ @"%@ is unavailable in authMode 'external'. The host "
42
+ @"app owns MSAL there; supply the account id to "
43
+ @"enroll() and answer tokenRequest via "
44
+ @"setTokenProvider.",
45
+ method]];
46
+ RNIntuneReject(reject, error);
47
+ }
48
+
49
+ /// Every auth method needs the same three checks in the same order. Returns nil when the
50
+ /// caller may proceed, or the rejection has already been sent.
51
+ static BOOL RNIntuneAuthReady(RCTPromiseRejectBlock reject, NSString *method)
52
+ {
53
+ if (!RNIntuneCore.shared.isConfigured) {
54
+ RNIntuneRejectNotConfigured(reject, method);
55
+ return NO;
56
+ }
57
+ if (!RNIntuneCore.shared.builtinAuth) {
58
+ RNIntuneRejectExternalAuth(reject, method);
59
+ return NO;
60
+ }
61
+ return YES;
62
+ }
63
+
64
+ /// Answers one auth call: a result resolves, an error rejects with its stable code.
65
+ static RNIntuneAuthCompletion RNIntuneAuthHandler(RCTPromiseResolveBlock resolve,
66
+ RCTPromiseRejectBlock reject)
67
+ {
68
+ return ^(NSDictionary<NSString *, id> *result, NSError *error) {
69
+ if (result != nil) {
70
+ resolve(result);
71
+ return;
72
+ }
73
+ RNIntuneReject(reject, error);
74
+ };
75
+ }
76
+
77
+ @implementation RNIntune {
78
+ /// Events emitted before JS subscribed. Flushed in startObserving.
79
+ NSMutableArray<NSDictionary *> *_pendingEvents;
80
+ BOOL _observing;
81
+ }
82
+
83
+ // Generates +moduleName. The argument must match TurboModuleRegistry.getEnforcing in
84
+ // src/NativeIntune.ts (SPEC §2.2) — do not also declare +moduleName by hand, the macro
85
+ // already does and the duplicate does not compile.
86
+ RCT_EXPORT_MODULE(RNIntune)
87
+
88
+ - (instancetype)init
89
+ {
90
+ if ((self = [super init])) {
91
+ _pendingEvents = [NSMutableArray new];
92
+ _observing = NO;
93
+
94
+ // Wired here, not in configure(): the SDK can call a delegate before the app has
95
+ // called anything, and an event lost during startup means the reset machine never
96
+ // runs (SPEC §4.4). sendEventWithName: queues while nothing is listening.
97
+ __weak __typeof(self) weakSelf = self;
98
+ [RNIntuneCore.shared setEventSink:^(NSString *event, NSDictionary *body) {
99
+ // The SDK picks its own thread; RCTEventEmitter must be driven from the JS queue.
100
+ dispatch_async(dispatch_get_main_queue(), ^{
101
+ [weakSelf sendEventWithName:event body:body];
102
+ });
103
+ }];
104
+ }
105
+ return self;
106
+ }
107
+
108
+ + (BOOL)requiresMainQueueSetup
109
+ {
110
+ // Construction only allocates the queue. The SDK's own initialisation is triggered by
111
+ // configure(), not by module creation, so there is no reason to block app startup.
112
+ return NO;
113
+ }
114
+
115
+ #pragma mark - Events
116
+
117
+ - (NSArray<NSString *> *)supportedEvents
118
+ {
119
+ return @[
120
+ RNIntuneEventEnrollmentResult,
121
+ RNIntuneEventPolicyChanged,
122
+ RNIntuneEventUnenrollmentResult,
123
+ RNIntuneEventWipeRequested,
124
+ RNIntuneEventRestartRequired,
125
+ RNIntuneEventTokenRequest,
126
+ RNIntuneEventBrokerStatusChanged,
127
+ ];
128
+ }
129
+
130
+ - (void)startObserving
131
+ {
132
+ _observing = YES;
133
+
134
+ // A wipe notification lost during startup means the reset machine never runs, so
135
+ // anything emitted before this point is replayed rather than discarded (SPEC §4.4).
136
+ NSArray<NSDictionary *> *queued = [_pendingEvents copy];
137
+ [_pendingEvents removeAllObjects];
138
+ for (NSDictionary *event in queued) {
139
+ [super sendEventWithName:event[@"name"] body:event[@"body"]];
140
+ }
141
+ }
142
+
143
+ - (void)stopObserving
144
+ {
145
+ _observing = NO;
146
+ }
147
+
148
+ - (void)sendEventWithName:(NSString *)name body:(id)body
149
+ {
150
+ if (_observing) {
151
+ [super sendEventWithName:name body:body];
152
+ return;
153
+ }
154
+ [_pendingEvents addObject:@{@"name" : name, @"body" : body ?: NSNull.null}];
155
+ }
156
+
157
+ #pragma mark - Lifecycle
158
+
159
+ - (void)configure:(JS::NativeIntune::SpecConfigureConfig &)config
160
+ resolve:(RCTPromiseResolveBlock)resolve
161
+ reject:(RCTPromiseRejectBlock)reject
162
+ {
163
+ // The generated struct is a typed view over the NSDictionary, so a renamed spec field
164
+ // fails to compile here instead of silently reading nil. Everything crossing into the
165
+ // .m core below is a plain ObjC type.
166
+ RNIntuneConfig *resolved = [RNIntuneConfig new];
167
+ resolved.clientId = config.clientId();
168
+ resolved.tenantId = config.tenantId();
169
+ resolved.authority = config.authority();
170
+ resolved.redirectUri = config.redirectUri();
171
+ resolved.authMode = config.authMode();
172
+ resolved.maxFileProtectionLevel = config.maxFileProtectionLevel();
173
+ resolved.keychainGroupOverride = config.keychainGroupOverride();
174
+ resolved.verboseLogging = config.verboseLogging();
175
+ resolved.restartHandledByApp = config.restartHandledByApp();
176
+ resolved.telemetryEnabled = config.telemetryEnabled();
177
+ resolved.brandingBackground = config.brandingBackground();
178
+ resolved.brandingForeground = config.brandingForeground();
179
+ resolved.brandingAccent = config.brandingAccent();
180
+ resolved.brandingSecondaryBackground = config.brandingSecondaryBackground();
181
+ resolved.brandingSecondaryForeground = config.brandingSecondaryForeground();
182
+
183
+ NSError *error = nil;
184
+ if (![RNIntuneCore.shared applyConfig:resolved error:&error]) {
185
+ RNIntuneReject(reject, error);
186
+ return;
187
+ }
188
+ resolve(nil);
189
+ }
190
+
191
+ - (void)isSupported:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
192
+ {
193
+ resolve(@(RNIntuneCore.shared.sdkAvailable));
194
+ }
195
+
196
+ - (void)getBrokerStatus:(RCTPromiseResolveBlock)resolve
197
+ reject:(RCTPromiseRejectBlock)reject
198
+ {
199
+ resolve([RNIntuneCore.shared brokerStatus]);
200
+ }
201
+
202
+ - (void)openBrokerInstall:(RCTPromiseResolveBlock)resolve
203
+ reject:(RCTPromiseRejectBlock)reject
204
+ {
205
+ NSDictionary *status = [RNIntuneCore.shared brokerStatus];
206
+ if ([status[@"brokerAvailable"] boolValue]) {
207
+ RNIntuneReject(reject,
208
+ [RNIntuneCore errorWithCode:RNIntuneErrorNotNeeded
209
+ message:@"A broker is already installed."]);
210
+ return;
211
+ }
212
+
213
+ // A search URL rather than a hardcoded App Store id. The numeric id for Authenticator
214
+ // is well known but is not something this repository has verified, and a wrong one
215
+ // sends users to the wrong app — CLAUDE.md rule 1 applies to store identifiers too.
216
+ NSURL *url = [NSURL
217
+ URLWithString:@"itms-apps://apps.apple.com/search?term=Microsoft%20Authenticator"];
218
+
219
+ dispatch_async(dispatch_get_main_queue(), ^{
220
+ [UIApplication.sharedApplication openURL:url
221
+ options:@{}
222
+ completionHandler:^(BOOL success) {
223
+ if (success) {
224
+ resolve(nil);
225
+ } else {
226
+ RNIntuneReject(
227
+ reject,
228
+ [RNIntuneCore
229
+ errorWithCode:RNIntuneErrorNative
230
+ message:@"Could not open the App Store."]);
231
+ }
232
+ }];
233
+ });
234
+ }
235
+
236
+ #pragma mark - Auth
237
+
238
+ /// These five take `Object` in the Codegen spec, so the values arrive untyped and are
239
+ /// read defensively here. `index.ts` has already narrowed and defaulted them
240
+ /// (CLAUDE.md rule 11) — this is the second line of defence, not the first.
241
+ static NSArray<NSString *> *RNIntuneScopes(NSDictionary *params)
242
+ {
243
+ id scopes = params[@"scopes"];
244
+ return [scopes isKindOfClass:NSArray.class] ? scopes : @[];
245
+ }
246
+
247
+ static NSString *RNIntuneOptionalString(NSDictionary *params, NSString *key)
248
+ {
249
+ id value = params[key];
250
+ return [value isKindOfClass:NSString.class] && [value length] > 0 ? value : nil;
251
+ }
252
+
253
+ - (void)signIn:(NSDictionary *)params
254
+ resolve:(RCTPromiseResolveBlock)resolve
255
+ reject:(RCTPromiseRejectBlock)reject
256
+ {
257
+ if (!RNIntuneAuthReady(reject, @"signIn")) {
258
+ return;
259
+ }
260
+ [RNIntuneCore.shared.auth
261
+ signInWithScopes:RNIntuneScopes(params)
262
+ loginHint:RNIntuneOptionalString(params, @"loginHint")
263
+ prompt:RNIntuneOptionalString(params, @"prompt")
264
+ presentingController:RCTPresentedViewController()
265
+ completion:RNIntuneAuthHandler(resolve, reject)];
266
+ }
267
+
268
+ - (void)signInSilent:(NSDictionary *)params
269
+ resolve:(RCTPromiseResolveBlock)resolve
270
+ reject:(RCTPromiseRejectBlock)reject
271
+ {
272
+ if (!RNIntuneAuthReady(reject, @"signInSilent")) {
273
+ return;
274
+ }
275
+ [RNIntuneCore.shared.auth
276
+ signInSilentWithScopes:RNIntuneScopes(params)
277
+ accountId:RNIntuneOptionalString(params, @"accountId")
278
+ completion:RNIntuneAuthHandler(resolve, reject)];
279
+ }
280
+
281
+ - (void)acquireToken:(NSDictionary *)params
282
+ resolve:(RCTPromiseResolveBlock)resolve
283
+ reject:(RCTPromiseRejectBlock)reject
284
+ {
285
+ if (!RNIntuneAuthReady(reject, @"acquireToken")) {
286
+ return;
287
+ }
288
+ id forceRefresh = params[@"forceRefresh"];
289
+ [RNIntuneCore.shared.auth
290
+ acquireTokenWithScopes:RNIntuneScopes(params)
291
+ accountId:RNIntuneOptionalString(params, @"accountId")
292
+ forceRefresh:[forceRefresh isKindOfClass:NSNumber.class]
293
+ ? [forceRefresh boolValue]
294
+ : NO
295
+ completion:RNIntuneAuthHandler(resolve, reject)];
296
+ }
297
+
298
+ - (void)getAccounts:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
299
+ {
300
+ if (!RNIntuneAuthReady(reject, @"getAccounts")) {
301
+ return;
302
+ }
303
+ NSError *error = nil;
304
+ NSArray *accounts = [RNIntuneCore.shared.auth accountsWithError:&error];
305
+ if (accounts == nil) {
306
+ RNIntuneReject(reject, error);
307
+ return;
308
+ }
309
+ resolve(accounts);
310
+ }
311
+
312
+ - (void)signOut:(JS::NativeIntune::SpecSignOutParams &)params
313
+ resolve:(RCTPromiseResolveBlock)resolve
314
+ reject:(RCTPromiseRejectBlock)reject
315
+ {
316
+ if (!RNIntuneAuthReady(reject, @"signOut")) {
317
+ return;
318
+ }
319
+
320
+ NSString *accountId = params.accountId();
321
+ BOOL wipeIntune = params.wipeIntune();
322
+
323
+ // Off the main thread whenever a wipe is involved: the unregister inside the reset
324
+ // sequence blocks while it acquires the Intune AAD token (SPEC §5.3, §12.5).
325
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
326
+ if (wipeIntune) {
327
+ // The order is the whole point of doing this in one place: unregister first, then
328
+ // clear MSAL. Reversed, the unregister loses the token it needs and leaves the
329
+ // account registered — which is what the host app could not reliably sequence back
330
+ // when MSAL lived on its side (SPEC §13.1.1).
331
+ [RNIntuneCore.shared resetWithWipe:YES reason:@"logout"];
332
+ resolve(nil);
333
+ return;
334
+ }
335
+
336
+ NSError *error = nil;
337
+ if (![RNIntuneCore.shared.auth removeAccountId:accountId error:&error]) {
338
+ RNIntuneReject(reject, error);
339
+ return;
340
+ }
341
+ resolve(nil);
342
+ });
343
+ }
344
+
345
+ #pragma mark - Enrollment
346
+
347
+ - (void)enroll:(JS::NativeIntune::SpecEnrollParams &)params
348
+ resolve:(RCTPromiseResolveBlock)resolve
349
+ reject:(RCTPromiseRejectBlock)reject
350
+ {
351
+ if (!RNIntuneCore.shared.isConfigured) {
352
+ RNIntuneRejectNotConfigured(reject, @"enroll");
353
+ return;
354
+ }
355
+
356
+ NSString *accountId = params.accountId();
357
+ if (accountId.length == 0) {
358
+ RNIntuneReject(reject,
359
+ [RNIntuneCore errorWithCode:RNIntuneErrorInvalidAccountId
360
+ message:@"enroll() requires an Entra object ID."]);
361
+ return;
362
+ }
363
+
364
+ // Resolves for every outcome including failures — a non-success status is data, not an
365
+ // exception (SPEC §13.2). It only rejects for the programming errors above.
366
+ [RNIntuneCore.shared enrollAccountId:accountId
367
+ completion:^(NSDictionary<NSString *, id> *result) {
368
+ resolve(result);
369
+ }];
370
+ }
371
+
372
+ - (void)enrollInteractive:(JS::NativeIntune::SpecEnrollInteractiveParams &)params
373
+ resolve:(RCTPromiseResolveBlock)resolve
374
+ reject:(RCTPromiseRejectBlock)reject
375
+ {
376
+ if (!RNIntuneCore.shared.isConfigured) {
377
+ RNIntuneRejectNotConfigured(reject, @"enrollInteractive");
378
+ return;
379
+ }
380
+ [RNIntuneCore.shared
381
+ enrollInteractiveWithUpn:params.upn()
382
+ completion:^(NSDictionary<NSString *, id> *result) {
383
+ resolve(result);
384
+ }];
385
+ }
386
+
387
+ - (void)getState:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
388
+ {
389
+ // Deliberately never rejects: reporting `configured: false` is what makes this the
390
+ // reconciliation primitive (SPEC §13.2).
391
+ resolve([RNIntuneCore.shared state]);
392
+ }
393
+
394
+ - (void)reset:(JS::NativeIntune::SpecResetParams &)params
395
+ resolve:(RCTPromiseResolveBlock)resolve
396
+ reject:(RCTPromiseRejectBlock)reject
397
+ {
398
+ BOOL wipe = params.wipe();
399
+ NSString *reason = params.reason();
400
+
401
+ // deRegisterAndUnenrollAccountId: blocks while it acquires the Intune AAD token, so it
402
+ // can never run on the main thread (SPEC §5.3, §12.5).
403
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
404
+ [RNIntuneCore.shared resetWithWipe:wipe reason:reason];
405
+ resolve(nil);
406
+ });
407
+ }
408
+
409
+ - (void)completeReset:(RCTPromiseResolveBlock)resolve
410
+ reject:(RCTPromiseRejectBlock)reject
411
+ {
412
+ if ([RNIntuneCore.shared completeReset]) {
413
+ resolve(nil);
414
+ return;
415
+ }
416
+ // Left open on purpose: the next launch sees pendingReset and retries. Reporting
417
+ // success here would strand an account the SDK will keep trying to re-enroll.
418
+ RNIntuneReject(reject,
419
+ [RNIntuneCore errorWithCode:RNIntuneErrorResetInProgress
420
+ message:@"The account is still registered after "
421
+ @"reset, so the journal was left open. It "
422
+ @"will be retried on the next launch."]);
423
+ }
424
+
425
+ #pragma mark - Policy
426
+
427
+ - (void)getPolicy:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject
428
+ {
429
+ NSDictionary *snapshot = [RNIntuneCore.shared policySnapshot];
430
+ if (snapshot == nil) {
431
+ RNIntuneReject(reject,
432
+ [RNIntuneCore errorWithCode:RNIntuneErrorSDKUnavailable
433
+ message:@"The Intune MAM SDK is not linked."]);
434
+ return;
435
+ }
436
+ resolve(snapshot);
437
+ }
438
+
439
+ - (void)getDiagnostics:(RCTPromiseResolveBlock)resolve
440
+ reject:(RCTPromiseRejectBlock)reject
441
+ {
442
+ resolve([RNIntuneCore.shared diagnostics]);
443
+ }
444
+
445
+ #pragma mark - Token provider
446
+
447
+ // Not public API — setTokenProvider in index.ts wraps these. The token goes straight to
448
+ // the SDK and is never logged or returned to JS (CLAUDE.md rule 9).
449
+ - (void)resolveToken:(JS::NativeIntune::SpecResolveTokenParams &)params
450
+ {
451
+ [RNIntuneCore.shared resolveTokenRequest:params.requestId() token:params.token()];
452
+ }
453
+
454
+ - (void)rejectToken:(JS::NativeIntune::SpecRejectTokenParams &)params
455
+ {
456
+ [RNIntuneCore.shared rejectTokenRequest:params.requestId() reason:params.reason()];
457
+ }
458
+
459
+ #pragma mark - TurboModule
460
+
461
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
462
+ (const facebook::react::ObjCTurboModule::InitParams &)params
463
+ {
464
+ return std::make_shared<facebook::react::NativeIntuneSpecJSI>(params);
465
+ }
466
+
467
+ @end
@@ -0,0 +1,121 @@
1
+ //
2
+ // MSAL: the client, sign-in, and tokens for the app's own scopes (SPEC §3, spike S-3).
3
+ //
4
+ // Pure Objective-C and free of React imports, like every other file on this side of the
5
+ // boundary — `RNIntune.mm` is the only place that knows about React (CLAUDE.md rule 7).
6
+ // That is why the presenting view controller arrives as a parameter instead of being
7
+ // looked up here: finding it is the bridge's job.
8
+ //
9
+ // Two tokens exist and only one of them leaves this file. The MAM service token is never
10
+ // returned, never logged, and is not even acquired here — the SDK acquires it itself,
11
+ // from the cache this object populates (§5.1.3). Everything returned by these methods is
12
+ // for the *app's* scopes (§3.5, CLAUDE.md rule 9).
13
+ //
14
+
15
+ #import <Foundation/Foundation.h>
16
+ #import <UIKit/UIKit.h>
17
+
18
+ NS_ASSUME_NONNULL_BEGIN
19
+
20
+ /// Completes with an `AuthResult`-shaped dictionary, or an error carrying one of the
21
+ /// stable codes in SPEC §13.6. Never both.
22
+ typedef void (^RNIntuneAuthCompletion)(NSDictionary<NSString *, id> *_Nullable result,
23
+ NSError *_Nullable error);
24
+
25
+ @interface RNIntuneAuth : NSObject
26
+
27
+ /**
28
+ * Builds the MSAL client for one tenant.
29
+ *
30
+ * `keychainGroup` is the value resolved from the host app's plist, not from `configure`
31
+ * (§5.1.4): the Intune SDK's half of this setting has no runtime setter, so the group is
32
+ * a build-time value and `configure` only asserts agreement. Pass nil to let MSAL keep
33
+ * its own default, which is already what the Intune SDK expects.
34
+ *
35
+ * Returns NO and fills `error` when MSAL rejects the configuration — a malformed
36
+ * authority URL is the common case, and it is worth failing loudly at `configure` rather
37
+ * than at the first sign-in.
38
+ */
39
+ - (BOOL)configureWithClientId:(NSString *)clientId
40
+ authority:(NSString *)authority
41
+ redirectUri:(NSString *)redirectUri
42
+ keychainGroup:(nullable NSString *)keychainGroup
43
+ error:(NSError *_Nullable *_Nullable)error;
44
+
45
+ /// Drops the client and forgets the tenant. Called from `reset`, which must leave nothing
46
+ /// pinned to the old tenant (SPEC §7).
47
+ - (void)invalidate;
48
+
49
+ @property (nonatomic, readonly, getter=isConfigured) BOOL configured;
50
+
51
+ #pragma mark - Sign-in
52
+
53
+ /**
54
+ * Interactive sign-in. Uses the broker when one is present, which also gives device-wide
55
+ * SSO: a user already signed into another Microsoft app usually gets a token with no
56
+ * prompt at all.
57
+ *
58
+ * `scopes` may be empty — see `defaultScopes`. `prompt` takes the public API's strings
59
+ * (`selectAccount`, `login`, `consent`, `whenRequired`); anything else falls back to
60
+ * MSAL's default rather than rejecting, so a future value added to the JS enum cannot
61
+ * break a shipped native binary.
62
+ */
63
+ - (void)signInWithScopes:(NSArray<NSString *> *)scopes
64
+ loginHint:(nullable NSString *)loginHint
65
+ prompt:(nullable NSString *)prompt
66
+ presentingController:(UIViewController *)controller
67
+ completion:(RNIntuneAuthCompletion)completion;
68
+
69
+ /// Cache-first. Fails with `E_INTERACTION_REQUIRED` when a prompt is needed — the callers'
70
+ /// pattern is silent first, interactive on that failure.
71
+ - (void)signInSilentWithScopes:(NSArray<NSString *> *)scopes
72
+ accountId:(nullable NSString *)accountId
73
+ completion:(RNIntuneAuthCompletion)completion;
74
+
75
+ /// A token for the app's own scopes, for an account that is already signed in.
76
+ - (void)acquireTokenWithScopes:(NSArray<NSString *> *)scopes
77
+ accountId:(nullable NSString *)accountId
78
+ forceRefresh:(BOOL)forceRefresh
79
+ completion:(RNIntuneAuthCompletion)completion;
80
+
81
+ #pragma mark - Accounts
82
+
83
+ /**
84
+ * Accounts with cached refresh tokens, as `AuthAccount` dictionaries.
85
+ *
86
+ * Under single identity (SPEC §9) this holds at most one; more than one means state a
87
+ * reset should have cleared, which is worth surfacing rather than hiding.
88
+ */
89
+ - (nullable NSArray<NSDictionary<NSString *, id> *> *)accountsWithError:
90
+ (NSError *_Nullable *_Nullable)error;
91
+
92
+ /// Removes the account and its tokens from the MSAL cache. This is the half of `reset`
93
+ /// the module could not do while MSAL lived in the host app (SPEC §7 step 4).
94
+ - (BOOL)removeAccountId:(NSString *)accountId error:(NSError *_Nullable *_Nullable)error;
95
+
96
+ /// Empties the cache of every account. Used by `reset`, which runs without a specific
97
+ /// account in hand once the SDK has already unregistered it.
98
+ - (void)removeAllAccounts;
99
+
100
+ #pragma mark - Defaults
101
+
102
+ /**
103
+ * What `signIn` asks for when the caller names no scopes.
104
+ *
105
+ * `https://graph.microsoft.com/User.Read`, because MSAL leaves no cheaper option: it
106
+ * adds `openid`, `profile` and `offline_access` itself and *rejects* a call that names
107
+ * them ("reserved scopes and may not be specified"), so a sign-in must ask for some real
108
+ * resource. `User.Read` is the one Entra grants every new app registration by default,
109
+ * and the one Microsoft's own samples use for exactly this.
110
+ *
111
+ * It is a default, not a requirement — an app with its own API passes its own scopes.
112
+ * What matters for this module is the side effect either way: the refresh token lands in
113
+ * the shared cache, and the SDK then acquires the MAM token from it silently (§5.1.3).
114
+ * The refresh token is per account, not per resource, so which scope got it there does
115
+ * not affect enrollment.
116
+ */
117
+ @property (class, nonatomic, readonly) NSArray<NSString *> *defaultScopes;
118
+
119
+ @end
120
+
121
+ NS_ASSUME_NONNULL_END