react-native-msal2 1.0.8 → 1.0.9

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.
@@ -1,38 +1,20 @@
1
- buildscript {
2
- repositories {
3
- google()
4
- mavenCentral()
5
- }
6
-
7
- dependencies {
8
- classpath 'com.android.tools.build:gradle:4.0.2'
9
- }
10
- }
11
-
12
- apply plugin: 'com.android.library'
1
+ apply plugin: "com.android.library"
2
+ apply plugin: "org.jetbrains.kotlin.android"
13
3
 
14
4
  def safeExtGet(prop, fallback) {
15
5
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
16
6
  }
17
7
 
18
8
  android {
19
- namespace 'com.reactnativemsal'
20
- compileSdkVersion safeExtGet('compileSdkVersion', 33)
9
+ namespace "com.reactnativemsal"
10
+
11
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
12
+
21
13
  defaultConfig {
22
- minSdkVersion safeExtGet('minSdkVersion', 21)
23
- targetSdkVersion safeExtGet('targetSdkVersion', 33)
24
- versionCode 1
25
- versionName "1.0"
14
+ minSdkVersion safeExtGet("minSdkVersion", 21)
15
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
26
16
  }
27
17
 
28
- buildTypes {
29
- release {
30
- minifyEnabled false
31
- }
32
- }
33
- lintOptions {
34
- disable 'GradleCompatible'
35
- }
36
18
  compileOptions {
37
19
  sourceCompatibility JavaVersion.VERSION_1_8
38
20
  targetCompatibility JavaVersion.VERSION_1_8
@@ -40,19 +22,13 @@ android {
40
22
  }
41
23
 
42
24
  repositories {
43
- mavenLocal()
44
- maven {
45
- // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
46
- url("$rootDir/../node_modules/react-native/android")
47
- }
48
25
  google()
49
26
  mavenCentral()
50
27
  }
51
28
 
52
29
  dependencies {
53
- // noinspection GradleDynamicVersion
54
- implementation 'com.facebook.react:react-native:+' // From node_modules
55
- implementation('com.microsoft.identity.client:msal:5.1.0') {
56
- exclude group: 'com.microsoft.device.display' // This group is obsolete nowadays
57
- }
30
+ //noinspection UseTomlInstead
31
+ compileOnly "com.facebook.react:react-android"
32
+ //noinspection UseTomlInstead
33
+ implementation "com.microsoft.identity.client:msal:8.2.1"
58
34
  }
@@ -0,0 +1,511 @@
1
+ package com.reactnativemsal
2
+
3
+ import android.content.pm.PackageManager
4
+ import android.net.Uri
5
+ import android.util.Base64
6
+ import android.util.Log
7
+ import com.facebook.react.bridge.Arguments.createArray
8
+ import com.facebook.react.bridge.Arguments.createMap
9
+ import com.facebook.react.bridge.Arguments.fromArray
10
+ import com.facebook.react.bridge.Promise
11
+ import com.facebook.react.bridge.ReactApplicationContext
12
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
13
+ import com.facebook.react.bridge.ReactMethod
14
+ import com.facebook.react.bridge.ReadableArray
15
+ import com.facebook.react.bridge.ReadableMap
16
+ import com.facebook.react.bridge.WritableArray
17
+ import com.facebook.react.bridge.WritableMap
18
+ import com.microsoft.identity.client.AcquireTokenParameters
19
+ import com.microsoft.identity.client.AcquireTokenSilentParameters
20
+ import com.microsoft.identity.client.AuthenticationCallback
21
+ import com.microsoft.identity.client.IAccount
22
+ import com.microsoft.identity.client.IAuthenticationResult
23
+ import com.microsoft.identity.client.IMultiTenantAccount
24
+ import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
25
+ import com.microsoft.identity.client.IMultipleAccountPublicClientApplication.RemoveAccountCallback
26
+ import com.microsoft.identity.client.Prompt
27
+ import com.microsoft.identity.client.PublicClientApplication
28
+ import com.microsoft.identity.client.SilentAuthenticationCallback
29
+ import com.microsoft.identity.client.exception.MsalException
30
+ import org.json.JSONArray
31
+ import org.json.JSONException
32
+ import org.json.JSONObject
33
+ import java.io.File
34
+ import java.io.FileWriter
35
+ import java.security.MessageDigest
36
+ import java.util.AbstractMap
37
+ import java.util.regex.Matcher
38
+ import java.util.regex.Pattern
39
+
40
+ class RNMSALModule(reactContext: ReactApplicationContext?) :
41
+ ReactContextBaseJavaModule(reactContext) {
42
+ private var publicClientApplication: IMultipleAccountPublicClientApplication? = null
43
+
44
+ override fun getName(): String {
45
+ return NAME
46
+ }
47
+
48
+ @ReactMethod
49
+ fun createPublicClientApplication(params: ReadableMap, promise: Promise) {
50
+ val context = reactApplicationContext
51
+ try {
52
+ // We have to make a JSON file containing the MSAL configuration, then use that file to
53
+ // create the PublicClientApplication
54
+ // We first need to create the JSON model using the passed in parameters
55
+
56
+ val config =
57
+ if (params.hasKey("androidConfigOptions")) params.getMap("androidConfigOptions") else null
58
+
59
+ val msalConfigJsonObj =
60
+ if (config != null) ReadableMapUtils.toJsonObject(config) else JSONObject()
61
+
62
+ // Account mode. Required to be MULTIPLE for this library
63
+ msalConfigJsonObj.put("account_mode", "MULTIPLE")
64
+
65
+ // If broker_redirect_uri_registered is not provided in androidConfigOptions,
66
+ // default it to false
67
+ if (!msalConfigJsonObj.has("broker_redirect_uri_registered")) {
68
+ msalConfigJsonObj.put("broker_redirect_uri_registered", false)
69
+ }
70
+
71
+ val auth = params.getMap("auth")
72
+
73
+ // Authority
74
+ val authority = ReadableMapUtils.getStringOrDefault(
75
+ auth,
76
+ "authority",
77
+ "https://login.microsoftonline.com/common"
78
+ )
79
+ msalConfigJsonObj.put("authority", authority)
80
+
81
+ // Client id
82
+ msalConfigJsonObj.put("client_id", ReadableMapUtils.getStringOrThrow(auth, "clientId"))
83
+
84
+ // Redirect URI
85
+ msalConfigJsonObj.put(
86
+ "redirect_uri",
87
+ if (auth!!.hasKey("redirectUri")) auth.getString("redirectUri") else makeRedirectUri(
88
+ context
89
+ ).toString()
90
+ )
91
+
92
+ // Authorities
93
+ val knownAuthorities = auth.getArray("knownAuthorities")
94
+ // List WILL be instantiated and empty if `knownAuthorities` is null
95
+ val authoritiesList = readableArrayToStringList(knownAuthorities)
96
+ // Make sure the `authority` makes it in the authority list
97
+ if (!authoritiesList.contains(authority)) {
98
+ authoritiesList.add(authority)
99
+ }
100
+ // The authoritiesList is just a list of urls (strings), but the native android MSAL
101
+ // library expects an array of objects, so we have to parse the urls
102
+ val authoritiesJsonArr = makeAuthoritiesJsonArray(authoritiesList, authority)
103
+ msalConfigJsonObj.put("authorities", authoritiesJsonArr)
104
+
105
+ // Serialize the JSON config to a string
106
+ val serializedMsalConfig = msalConfigJsonObj.toString()
107
+ Log.d("RNMSALModule", serializedMsalConfig)
108
+
109
+ // Create a temporary file and write the serialized config to it
110
+ val file = File.createTempFile("RNMSAL_msal_config", ".tmp")
111
+ file.deleteOnExit()
112
+ val writer = FileWriter(file)
113
+ writer.write(serializedMsalConfig)
114
+ writer.close()
115
+
116
+ // Finally, create the PCA with the temporary config file we created
117
+ publicClientApplication =
118
+ PublicClientApplication.createMultipleAccountPublicClientApplication(
119
+ context, file
120
+ )
121
+ promise.resolve(null)
122
+ } catch (e: Exception) {
123
+ promise.reject(e)
124
+ }
125
+ }
126
+
127
+ @Throws(JSONException::class, IllegalArgumentException::class)
128
+ private fun makeAuthoritiesJsonArray(
129
+ authorityUrls: MutableList<String>,
130
+ authority: String?
131
+ ): JSONArray {
132
+ val authoritiesJsonArr = JSONArray()
133
+ var foundDefaultAuthority = false
134
+
135
+ for (authorityUrl in authorityUrls) {
136
+ val authorityJsonObj = JSONObject()
137
+
138
+ // Authority is set as the default if one is not set yet, and it matches `authority`
139
+ if (!foundDefaultAuthority && authorityUrl == authority) {
140
+ authorityJsonObj.put("default", true)
141
+ foundDefaultAuthority = true
142
+ }
143
+
144
+ val aadAuthorityMatcher: Matcher = aadAuthorityPattern.matcher(authorityUrl)
145
+ val b2cAuthorityMatcher: Matcher = b2cAuthorityPattern.matcher(authorityUrl)
146
+
147
+ if (aadAuthorityMatcher.find()) {
148
+ val group = aadAuthorityMatcher.group(1)
149
+ requireNotNull(group) { "Could not match group 1 for regex https://login.microsoftonline.com/([^/]+) in authority \"$authorityUrl\"" }
150
+
151
+ val audience = when (group) {
152
+ "common" -> JSONObject().put("type", "AzureADandPersonalMicrosoftAccount")
153
+ "organizations" -> JSONObject().put("type", "AzureADMultipleOrgs")
154
+ "consumers" -> JSONObject().put("type", "PersonalMicrosoftAccount")
155
+ else -> // assume `group` is a tenant id
156
+ JSONObject().put("type", "AzureADMyOrg").put("tenant_id", group)
157
+ }
158
+ authorityJsonObj.put("type", AUTHORITY_TYPE_AAD)
159
+ authorityJsonObj.put("audience", audience)
160
+ } else if (b2cAuthorityMatcher.find()) {
161
+ authorityJsonObj.put("type", AUTHORITY_TYPE_B2C)
162
+ authorityJsonObj.put("authority_url", authorityUrl)
163
+ } else {
164
+ throw IllegalArgumentException("Authority \"$authorityUrl\" doesn't match AAD regex https://login.microsoftonline.com/([^/]+) or B2C regex https://([^/]+)/tfp/([^/]+)/.+")
165
+ }
166
+
167
+ authoritiesJsonArr.put(authorityJsonObj)
168
+ }
169
+
170
+ // If a default authority was not found, we set the first authority as the default
171
+ if (!foundDefaultAuthority && authoritiesJsonArr.length() > 0) {
172
+ authoritiesJsonArr.getJSONObject(0).put("default", true)
173
+ }
174
+
175
+ return authoritiesJsonArr
176
+ }
177
+
178
+ @Throws(Exception::class)
179
+ private fun makeRedirectUri(context: ReactApplicationContext): Uri? {
180
+ try {
181
+ val packageName = context.packageName
182
+ val info = context.packageManager
183
+ .getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
184
+ if (info.signatures?.size != 1) {
185
+ throw RuntimeException("RNMSAL expected there to be exactly one signature for package $packageName")
186
+ }
187
+ val signature = info.signatures?.first()
188
+ val messageDigest = MessageDigest.getInstance("SHA")
189
+ messageDigest.update(signature!!.toByteArray())
190
+ val signatureHash = Base64.encodeToString(messageDigest.digest(), Base64.NO_WRAP)
191
+ Log.d("RNMSALModule", signatureHash)
192
+
193
+ return Uri.Builder().scheme("msauth")
194
+ .authority(packageName)
195
+ .appendPath(signatureHash)
196
+ .build()
197
+ } catch (ex: Exception) {
198
+ throw Exception(
199
+ "Could not create redirect uri from package name and signature hash",
200
+ ex
201
+ )
202
+ }
203
+ }
204
+
205
+ @ReactMethod
206
+ fun acquireToken(params: ReadableMap, promise: Promise) {
207
+ try {
208
+ val acquireTokenParameters =
209
+ AcquireTokenParameters.Builder()
210
+ .startAuthorizationFromActivity(this.reactApplicationContext.currentActivity)
211
+
212
+ // Required parameters
213
+ val scopes = readableArrayToStringList(params.getArray("scopes"))
214
+ acquireTokenParameters.withScopes(scopes)
215
+
216
+ // Optional parameters
217
+ if (params.hasKey("authority")) {
218
+ acquireTokenParameters.fromAuthority(params.getString("authority"))
219
+ }
220
+
221
+ if (params.hasKey("promptType")) {
222
+ acquireTokenParameters.withPrompt(Prompt.entries[params.getInt("promptType")])
223
+ }
224
+
225
+ if (params.hasKey("loginHint")) {
226
+ acquireTokenParameters.withLoginHint(params.getString("loginHint"))
227
+ }
228
+
229
+ if (params.hasKey("extraScopesToConsent")) {
230
+ acquireTokenParameters.withOtherScopesToAuthorize(
231
+ readableArrayToStringList(params.getArray("extraScopesToConsent"))
232
+ )
233
+ }
234
+
235
+ if (params.hasKey("extraQueryParameters")) {
236
+ val parameters: MutableList<MutableMap.MutableEntry<String?, String?>?> =
237
+ ArrayList()
238
+ params.getMap("extraQueryParameters")?.toHashMap()?.entries?.let {
239
+ for (entry in it) {
240
+ parameters.add(
241
+ AbstractMap.SimpleEntry<String?, String?>(
242
+ entry.key,
243
+ entry.value.toString()
244
+ )
245
+ )
246
+ }
247
+ }
248
+ acquireTokenParameters.withAuthorizationQueryStringParameters(parameters)
249
+ }
250
+
251
+ acquireTokenParameters.withCallback(getAuthInteractiveCallback(promise))
252
+ publicClientApplication!!.acquireToken(acquireTokenParameters.build())
253
+ } catch (e: Exception) {
254
+ promise.reject(e)
255
+ }
256
+ }
257
+
258
+ private fun getAuthInteractiveCallback(promise: Promise): AuthenticationCallback {
259
+ return object : AuthenticationCallback {
260
+ override fun onCancel() {
261
+ promise.reject("userCancel", "userCancel")
262
+ }
263
+
264
+ override fun onSuccess(authenticationResult: IAuthenticationResult?) {
265
+ if (authenticationResult != null) {
266
+ promise.resolve(msalResultToDictionary(authenticationResult))
267
+ } else {
268
+ promise.resolve(null)
269
+ }
270
+ }
271
+
272
+ override fun onError(exception: MsalException) {
273
+ promise.reject(exception)
274
+ }
275
+ }
276
+ }
277
+
278
+ @ReactMethod
279
+ fun acquireTokenSilent(params: ReadableMap, promise: Promise) {
280
+ try {
281
+ val acquireTokenSilentParameters =
282
+ AcquireTokenSilentParameters.Builder()
283
+
284
+ // Required parameters
285
+ val scopes = readableArrayToStringList(params.getArray("scopes"))
286
+ acquireTokenSilentParameters.withScopes(scopes)
287
+
288
+ val accountIn = params.getMap("account")
289
+ val accountIdentifier = accountIn?.getString("identifier")
290
+ val account = publicClientApplication!!.getAccount(
291
+ accountIdentifier!!
292
+ )
293
+ acquireTokenSilentParameters.forAccount(account)
294
+
295
+ // Optional parameters
296
+ var authority: String? =
297
+ publicClientApplication!!
298
+ .configuration
299
+ .defaultAuthority
300
+ .authorityURL
301
+ .toString()
302
+ if (params.hasKey("authority")) {
303
+ authority = params.getString("authority")
304
+ }
305
+ acquireTokenSilentParameters.fromAuthority(authority)
306
+
307
+ if (params.hasKey("forceRefresh")) {
308
+ acquireTokenSilentParameters.forceRefresh(params.getBoolean("forceRefresh"))
309
+ }
310
+
311
+ acquireTokenSilentParameters.withCallback(getAuthSilentCallback(promise))
312
+ publicClientApplication!!.acquireTokenSilentAsync(acquireTokenSilentParameters.build())
313
+ } catch (e: Exception) {
314
+ promise.reject(e)
315
+ }
316
+ }
317
+
318
+ private fun getAuthSilentCallback(promise: Promise): SilentAuthenticationCallback {
319
+ return object : SilentAuthenticationCallback {
320
+ override fun onSuccess(authenticationResult: IAuthenticationResult?) {
321
+ if (authenticationResult != null) {
322
+ promise.resolve(msalResultToDictionary(authenticationResult))
323
+ } else {
324
+ promise.resolve(null)
325
+ }
326
+ }
327
+
328
+ override fun onError(exception: MsalException) {
329
+ promise.reject(exception)
330
+ }
331
+ }
332
+ }
333
+
334
+ @ReactMethod
335
+ fun getAccounts(promise: Promise) {
336
+ try {
337
+ val accounts = publicClientApplication!!.accounts
338
+ val array = createArray()
339
+ if (accounts != null) {
340
+ for (account in accounts) {
341
+ array.pushMap(accountToMap(account))
342
+ }
343
+ }
344
+ promise.resolve(array)
345
+ } catch (e: Exception) {
346
+ promise.reject(e)
347
+ }
348
+ }
349
+
350
+ @ReactMethod
351
+ fun getAccount(accountIdentifier: String, promise: Promise) {
352
+ try {
353
+ val account = publicClientApplication!!.getAccount(accountIdentifier)
354
+ if (account != null) {
355
+ promise.resolve(accountToMap(account))
356
+ } else {
357
+ promise.resolve(null)
358
+ }
359
+ } catch (e: Exception) {
360
+ promise.reject(e)
361
+ }
362
+ }
363
+
364
+ @ReactMethod
365
+ fun removeAccount(accountIn: ReadableMap, promise: Promise) {
366
+ try {
367
+ // Required parameters
368
+ publicClientApplication?.let { app ->
369
+ accountIn.getString(("identifier"))?.let {
370
+ val account = app.getAccount(it)
371
+
372
+ app.removeAccount(
373
+ account,
374
+ object : RemoveAccountCallback {
375
+ override fun onRemoved() {
376
+ promise.resolve(true)
377
+ }
378
+
379
+ override fun onError(exception: MsalException) {
380
+ promise.reject(exception)
381
+ }
382
+ })
383
+ }
384
+ }
385
+ } catch (e: Exception) {
386
+ promise.reject(e)
387
+ }
388
+ }
389
+
390
+ private fun msalResultToDictionary(result: IAuthenticationResult): WritableMap {
391
+ val map = createMap()
392
+ map.putString("accessToken", result.accessToken)
393
+ map.putString("expiresOn", String.format("%s", result.expiresOn.time / 1000))
394
+ var idToken = result.account.idToken
395
+ if (idToken == null) {
396
+ idToken =
397
+ (result.account as IMultiTenantAccount).tenantProfiles[result.tenantId]?.idToken
398
+ }
399
+ map.putString("idToken", idToken)
400
+ map.putArray("scopes", fromArray(result.scope))
401
+ map.putString("tenantId", result.tenantId)
402
+ map.putMap("account", accountToMap(result.account))
403
+ return map
404
+ }
405
+
406
+ private fun accountToMap(account: IAccount): WritableMap {
407
+ val map = createMap()
408
+ map.putString("identifier", account.id)
409
+ map.putString("username", account.username)
410
+ map.putString("tenantId", account.tenantId)
411
+ val claims = account.claims
412
+ if (claims != null) {
413
+ map.putMap("claims", toWritableMap(claims))
414
+ }
415
+ return map
416
+ }
417
+
418
+
419
+ private fun readableArrayToStringList(readableArray: ReadableArray?): MutableList<String> {
420
+ val list: MutableList<String> = ArrayList()
421
+ if (readableArray != null) {
422
+ for (item in readableArray.toArrayList()) {
423
+ list.add(item.toString())
424
+ }
425
+ }
426
+ return list
427
+ }
428
+
429
+ private fun toWritableMap(map: MutableMap<String?, *>): WritableMap {
430
+ val writableMap = createMap()
431
+ for (entry in map.entries) {
432
+ val key: String = entry.key!!
433
+ when (val value: Any? = entry.value) {
434
+ null -> {
435
+ writableMap.putNull(key)
436
+ }
437
+
438
+ is Boolean -> {
439
+ writableMap.putBoolean(key, value)
440
+ }
441
+
442
+ is Double -> {
443
+ writableMap.putDouble(key, value)
444
+ }
445
+
446
+ is Int -> {
447
+ writableMap.putInt(key, value)
448
+ }
449
+
450
+ is String -> {
451
+ writableMap.putString(key, value)
452
+ }
453
+
454
+ is MutableMap<*, *> -> {
455
+ writableMap.putMap(key, toWritableMap(value as MutableMap<String?, *>))
456
+ }
457
+
458
+ is MutableList<*> -> {
459
+ writableMap.putArray(key, toWritableArray(value))
460
+ }
461
+ }
462
+ }
463
+ return writableMap
464
+ }
465
+
466
+ private fun toWritableArray(list: MutableList<*>): WritableArray {
467
+ val writableArray = createArray()
468
+ for (value in list.toTypedArray()) {
469
+ when (value) {
470
+ null -> {
471
+ writableArray.pushNull()
472
+ }
473
+
474
+ is Boolean -> {
475
+ writableArray.pushBoolean(value)
476
+ }
477
+
478
+ is Double -> {
479
+ writableArray.pushDouble(value)
480
+ }
481
+
482
+ is Int -> {
483
+ writableArray.pushInt(value)
484
+ }
485
+
486
+ is String -> {
487
+ writableArray.pushString(value)
488
+ }
489
+
490
+ is MutableMap<*, *> -> {
491
+ writableArray.pushMap(toWritableMap(value as MutableMap<String?, *>))
492
+ }
493
+
494
+ is MutableList<*> -> {
495
+ writableArray.pushArray(toWritableArray(value))
496
+ }
497
+ }
498
+ }
499
+ return writableArray
500
+ }
501
+
502
+ companion object {
503
+ const val NAME = "RNMSAL"
504
+ private const val AUTHORITY_TYPE_B2C = "B2C"
505
+ private const val AUTHORITY_TYPE_AAD = "AAD"
506
+
507
+ private val aadAuthorityPattern: Pattern =
508
+ Pattern.compile("https://login\\.microsoftonline\\.com/([^/]+)")
509
+ private val b2cAuthorityPattern: Pattern = Pattern.compile("https://([^/]+)/([^/]+)/.+")
510
+ }
511
+ }
@@ -0,0 +1,28 @@
1
+ package com.reactnativemsal
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfo
7
+ import com.facebook.react.module.model.ReactModuleInfoProvider
8
+
9
+ class RNMSALPackage : BaseReactPackage() {
10
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
11
+ return RNMSALModule(reactContext)
12
+ }
13
+
14
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
15
+ return ReactModuleInfoProvider {
16
+ mapOf(
17
+ RNMSALModule.NAME to ReactModuleInfo(
18
+ RNMSALModule.NAME,
19
+ RNMSALModule::class.java.name,
20
+ canOverrideExistingModule = false,
21
+ needsEagerInit = false,
22
+ isCxxModule = false,
23
+ isTurboModule = false
24
+ )
25
+ )
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,82 @@
1
+ package com.reactnativemsal
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.bridge.ReadableType
6
+ import org.json.JSONArray
7
+ import org.json.JSONException
8
+ import org.json.JSONObject
9
+
10
+ object ReadableMapUtils {
11
+ @Throws(JSONException::class)
12
+ fun toJsonObject(readableMap: ReadableMap): JSONObject {
13
+ val jsonObject = JSONObject()
14
+
15
+ val iterator = readableMap.keySetIterator()
16
+
17
+ while (iterator.hasNextKey()) {
18
+ val key = iterator.nextKey()
19
+ val type = readableMap.getType(key)
20
+
21
+ when (type) {
22
+ ReadableType.Null -> jsonObject.put(key, null)
23
+ ReadableType.Boolean -> jsonObject.put(key, readableMap.getBoolean(key))
24
+ ReadableType.Number -> jsonObject.put(key, readableMap.getDouble(key))
25
+ ReadableType.String -> jsonObject.put(key, readableMap.getString(key))
26
+ ReadableType.Map -> jsonObject.put(
27
+ key,
28
+ toJsonObject(
29
+ readableMap.getMap(key)!!
30
+ )
31
+ )
32
+
33
+ ReadableType.Array -> jsonObject.put(
34
+ key, toJsonArray(readableMap.getArray(key)!!)
35
+ )
36
+ }
37
+ }
38
+
39
+ return jsonObject
40
+ }
41
+
42
+ @Throws(JSONException::class)
43
+ fun toJsonArray(readableArray: ReadableArray): JSONArray {
44
+ val jsonArray = JSONArray()
45
+
46
+ for (i in 0..<readableArray.size()) {
47
+ val type = readableArray.getType(i)
48
+
49
+ when (type) {
50
+ ReadableType.Null -> jsonArray.put(i, null)
51
+ ReadableType.Boolean -> jsonArray.put(i, readableArray.getBoolean(i))
52
+ ReadableType.Number -> jsonArray.put(i, readableArray.getDouble(i))
53
+ ReadableType.String -> jsonArray.put(i, readableArray.getString(i))
54
+ ReadableType.Map -> jsonArray.put(
55
+ i,
56
+ toJsonObject(readableArray.getMap(i)!!)
57
+ )
58
+
59
+ ReadableType.Array -> jsonArray.put(
60
+ i, toJsonArray(readableArray.getArray(i)!!)
61
+ )
62
+ }
63
+ }
64
+
65
+ return jsonArray
66
+ }
67
+
68
+ fun getStringOrDefault(map: ReadableMap?, key: String, defaultValue: String): String {
69
+ return try {
70
+ getStringOrThrow(map, key)
71
+ } catch (_: Exception) {
72
+ defaultValue
73
+ }
74
+ }
75
+
76
+ fun getStringOrThrow(map: ReadableMap?, key: String): String {
77
+ requireNotNull(map) { "Map is null" }
78
+
79
+ return map.getString(key)
80
+ ?: throw NoSuchElementException("$key doesn't exist on map or is null")
81
+ }
82
+ }