package io.tryrook.rook.sdk import android.annotation.SuppressLint import android.content.Context import com.getcapacitor.JSArray import com.getcapacitor.JSObject import com.getcapacitor.PluginCall import com.rookmotion.rook.sdk.RookBackgroundSyncManager import com.rookmotion.rook.sdk.RookConfigurationManager import com.rookmotion.rook.sdk.RookPermissionsManager import com.rookmotion.rook.sdk.RookStepsCounter import com.rookmotion.rook.sdk.RookStepsManager import com.rookmotion.rook.sdk.RookSyncManager import com.rookmotion.rook.sdk.RookWriteManager import com.rookmotion.rook.sdk.domain.analytics.HCRookAnalytics import com.rookmotion.rook.sdk.domain.analytics.HCRookFramework import com.rookmotion.rook.sdk.domain.annotation.ExperimentalRookApi import com.rookmotion.rook.sdk.domain.enums.BackgroundReadStatus import com.rookmotion.rook.sdk.domain.enums.RequestPermissionsStatus import com.rookmotion.rook.sdk.domain.environment.RookEnvironment import com.rookmotion.rook.sdk.domain.model.HCActivityEvent import com.rookmotion.rook.sdk.domain.model.HCBodySummary import com.rookmotion.rook.sdk.domain.model.HCHeartRate import com.rookmotion.rook.sdk.domain.model.HCPhysicalSummary import com.rookmotion.rook.sdk.domain.model.HCSleepSummary import com.rookmotion.rook.sdk.domain.model.RookConfiguration import com.rookmotion.rook.sdk.domain.model.SyncStatusWithData import com.rookmotion.rook.sdk.domain.model.SyncType import io.tryrook.api.sources.RookApiSources import io.tryrook.api.sources.domain.enums.DataSourceType import io.tryrook.api.sources.domain.environment.ApiEnvironment import io.tryrook.api.sources.domain.model.ApiConfiguration import io.tryrook.api.sources.domain.model.AuthorizedDataSourceV2 import io.tryrook.sdk.samsung.RookSamsung import io.tryrook.sdk.samsung.domain.analytics.SHRookAnalytics import io.tryrook.sdk.samsung.domain.analytics.SHRookFramework import io.tryrook.sdk.samsung.domain.enums.SamsungHealthPermission import io.tryrook.sdk.samsung.domain.environment.SHEnvironment import io.tryrook.sdk.samsung.domain.model.SHActivityEvent import io.tryrook.sdk.samsung.domain.model.SHBodySummary import io.tryrook.sdk.samsung.domain.model.SHConfiguration import io.tryrook.sdk.samsung.domain.model.SHHeartRate import io.tryrook.sdk.samsung.domain.model.SHPhysicalSummary import io.tryrook.sdk.samsung.domain.model.SHSleepSummary import io.tryrook.sdk.samsung.domain.model.SHSyncStatusWithData import io.tryrook.sdk.samsung.domain.model.SHSyncSummariesResult import io.tryrook.sdk.samsung.domain.model.SHSyncType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.lang.Exception import java.time.LocalDate import kotlin.Boolean import kotlin.Result import kotlin.collections.List import kotlin.collections.Set private const val INIT_ERROR_MESSAGE = "Please first init Rook" private const val MISSING_USER_IR = "Please ender a user id" class RookImplementation() { private var rookConfigurationManager: RookConfigurationManager? = null private var rookPermissionsManager: RookPermissionsManager? = null private var scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private var uploadManager: RookSyncManager? = null private var environmentConfig: RookEnvironment = RookEnvironment.SANDBOX private var uuid: String? = null private var pass: String? = null private var stepsManager: RookStepsManager? = null private var areLogsEnable: Boolean = false private var isBackgroundEnable: Boolean = false private var backgroundManager: RookBackgroundSyncManager? = null private var samsungManager: RookSamsung? = null private var rookWriteManager: RookWriteManager? = null private var rookAPIDataSource: RookApiSources? = null private var rookStepCounter: RookStepsCounter? = null fun initRookConfigurationManager(context: Context) { rookConfigurationManager = RookConfigurationManager(context) uploadManager = RookSyncManager(context) stepsManager = RookStepsManager(context) rookPermissionsManager = RookPermissionsManager(context) backgroundManager = RookBackgroundSyncManager(context) samsungManager = RookSamsung(context) rookWriteManager = RookWriteManager(context) rookAPIDataSource = RookApiSources(context) rookStepCounter = RookStepsCounter(context = context) } // MARK: - Configuration @SuppressLint("MissingPermission") fun initRook(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null || rookAPIDataSource == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val environment = call.getString("environment", "sandbox") val clientUUID = call.getString("clientUUID", "") val secret = call.getString("secret", "") val packageName = call.getString("packageName") val enableLogs = call.getBoolean("enableLogs") == true val enableBackgroundSync = call.getBoolean("enableBackgroundSync") == true isBackgroundEnable = enableBackgroundSync areLogsEnable = enableLogs var rookEnvironment = RookEnvironment.SANDBOX var samsungEnvironment = SHEnvironment.SANDBOX var apiEnvironment: ApiEnvironment = ApiEnvironment.SANDBOX if (environment == "production") { rookEnvironment = RookEnvironment.PRODUCTION samsungEnvironment = SHEnvironment.PRODUCTION apiEnvironment = ApiEnvironment.PRODUCTION } environmentConfig = rookEnvironment uuid = clientUUID pass = secret if (clientUUID != null && secret != null) { val rookConfiguration = RookConfiguration( clientUUID = clientUUID, secret = secret, environment = rookEnvironment, packageName = packageName ) rookConfigurationManager!!.setConfiguration(rookConfiguration) if (enableLogs) rookConfigurationManager!!.enableLocalLogs() HCRookAnalytics.setFramework(HCRookFramework.IONIC) val apiConfiguration = ApiConfiguration( clientUUID = clientUUID, secret = secret, environment = apiEnvironment, packageName = packageName ) rookAPIDataSource!!.initRook(configuration = apiConfiguration) val initRookResult: Result = rookConfigurationManager!!.initRook() val initSamsungResult: Result = initRookSamsungHealth( clientUUID = clientUUID, secret = secret, environment = samsungEnvironment, packageName = packageName ) handelInitResult(initRookResult, initSamsungResult, call) } else { call.reject(INIT_ERROR_MESSAGE) } } } suspend fun initRookSamsungHealth( clientUUID: String, secret: String, environment: SHEnvironment, packageName: String?): Result { val configuration = SHConfiguration( clientUUID = clientUUID, secret = secret, environment = environment, packageName = packageName ) SHRookAnalytics.setFramework(SHRookFramework.IONIC) return samsungManager!!.initRook(configuration = configuration) } private suspend fun handelInitResult( rookResult: Result, samsungResult: Result, call: PluginCall) { if (rookResult.isSuccess) { attemptToEnableBackgroundSync(isBackgroundEnable, areLogsEnable) val isStepsEnable: Boolean = stepsManager!!.isBackgroundAndroidStepsActive() if (!isStepsEnable) { stepsManager!!.enableBackgroundAndroidSteps() } } if (samsungResult.isSuccess) { samsungManager!!.schedule(areLogsEnable) } if (rookResult.isSuccess && samsungResult.isSuccess) { validateUser() val ret = JSObject() ret.put("result", true) call.resolve(ret) } else if (rookResult.isSuccess && samsungResult.isFailure) { val error = samsungResult.exceptionOrNull()?.message call.reject(error) } else if (rookResult.isFailure && samsungResult.isSuccess) { val error = rookResult.exceptionOrNull()?.message call.reject(error) } else { val error = rookResult.exceptionOrNull()?.message + samsungResult.exceptionOrNull()?.message call.reject(error) } } private suspend fun validateUser(): Result { val userId = rookConfigurationManager!!.getUserID() val samsungUserId = samsungManager!!.getUserID() return if (userId != null && samsungUserId == null) { samsungManager!!.updateUserID(userId) } else if (userId != null && samsungUserId != null && userId != samsungUserId) { samsungManager!!.updateUserID(userId) } else { Result.success(Unit) } } fun getDiagnostic(call: PluginCall) { scope.launch { val source: String? = call.getString("androidSource") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "HEALTH_CONNECT" -> { getHCDiagnostic(call) } "SAMSUNG" -> { getSHDiagnostic(call) } } } } private suspend fun getHCDiagnostic(call: PluginCall) { if (rookConfigurationManager != null) { rookConfigurationManager!!.getDiagnosticState().fold({ val ret = JSObject() val resultDiagnostic = HCDiagnosticMapper.map(it) ret.put("result", resultDiagnostic) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject(INIT_ERROR_MESSAGE) } } private suspend fun getSHDiagnostic(call: PluginCall) { if (samsungManager != null) { samsungManager!!.getDiagnosticState().fold({ val ret = JSObject() val resultDiagnostic = SHDiagnosticMapper.map(it) ret.put("result", resultDiagnostic) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject(INIT_ERROR_MESSAGE) } } fun updateUserId(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val userId = call.getString("userId", "") if (userId != null) { val samsungResult: Result = updateUserIdSamsung(userId) val rookResult: Result = rookConfigurationManager!!.updateUserID(userId) if (samsungResult.isSuccess && rookResult.isSuccess) { val ret = JSObject() ret.put("result", true) call.resolve(ret) } else { call.reject(samsungResult.exceptionOrNull()?.message + rookResult.exceptionOrNull()?.message) } } else { call.reject(MISSING_USER_IR) } } } private suspend fun updateUserIdSamsung(id: String): Result { return samsungManager!!.updateUserID(id) } fun deleteUserFromRook(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val samsungResult: Result = samsungManager!!.deleteUserFromRook() val rookResult: Result = rookConfigurationManager!!.deleteUserFromRook() if (samsungResult.isSuccess && rookResult.isSuccess) { val ret = JSObject() ret.put("result", true) call.resolve(ret) } else { call.reject(samsungResult.exceptionOrNull()?.message + rookResult.exceptionOrNull()?.message) } } } fun getUserId(call: PluginCall) { scope.launch { try { if (rookConfigurationManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val ret = JSObject() ret.put("userId", rookConfigurationManager!!.getUserID()) call.resolve(ret) } catch (e: Exception) { call.reject("We couldn't get the user id", e) } } } fun syncUserTimeZone(call: PluginCall) { scope.launch { if (rookConfigurationManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } rookConfigurationManager!!.syncUserTimeZone() .fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } } fun checkAvailability(call: PluginCall) { try { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) return } val ret = JSObject() ret.put("result", rookPermissionsManager!!.checkHealthConnectAvailability().toString()) call.resolve(ret) } catch (e: Exception) { call.reject("We couldn't check the availability",e) } } fun checkSamsungHealthAvailability(call: PluginCall) { scope.launch { if (samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } samsungManager!!.checkSamsungHealthAvailability().fold({ val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) }) } } fun requestSamsungHealthPermissions(call: PluginCall) { scope.launch { if (samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val permissions: JSArray? = call.getArray("types") if (permissions != null) { val samsungPermissions: Set = mapPermission(permissions) samsungManager!!.requestSamsungHealthPermissions(samsungPermissions).fold({ val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject("empty permissions") } } } private fun mapPermission(permissions: JSArray): Set { val samsungPermissions: Set = (0 until permissions.length()).mapNotNull { val permissionString: String = permissions[it].toString() try { SamsungHealthPermission.valueOf(permissionString) } catch (_: IllegalArgumentException) { null } }.toSet() return samsungPermissions } fun checkSamsungHealthPermissionPartially(call: PluginCall) { scope.launch { if (samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val permissions: JSArray? = call.getArray("permissions") if (permissions != null) { val samsungPermissions: Set = mapPermission(permissions) samsungManager!!.checkSamsungHealthPermissionsPartially(samsungPermissions).fold( { val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) } ) } else { call.reject("Missing permissions parameter") } } } fun checkSamsungHealthPermission(call: PluginCall) { scope.launch { if (samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val permissions: JSArray? = call.getArray("permissions") if (permissions != null) { val samsungPermissions: Set = mapPermission(permissions) samsungManager!!.checkSamsungHealthPermissions(samsungPermissions).fold( { val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) } ) } else { call.reject("Missing permissions parameter") } } } // MARK: - Permissions fun requestHealthConnectPermissions(call: PluginCall) { scope.launch { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } rookPermissionsManager!!.requestHealthConnectPermissions() .fold({ val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) }) } } fun requestAndroidPermissions(call: PluginCall) { scope.launch { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val resultStatus: RequestPermissionsStatus = rookPermissionsManager!!.requestAndroidPermissions() val ret = JSObject() ret.put("result", resultStatus.toString()) call.resolve(ret) } } fun revokeHealthConnectPermissions(call: PluginCall) { scope.launch { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } rookPermissionsManager!!.revokeHealthConnectPermissions().fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } } fun openHealthConnectSettings(call: PluginCall) { scope.launch { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } rookPermissionsManager!!.openHealthConnectSettings() .fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } } fun checkAndroidPermissions(call: PluginCall) { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val result: Boolean = rookPermissionsManager!!.checkAndroidPermissions() val ret = JSObject() ret.put("result", result) call.resolve(ret) } fun checkHealthPermissionsPartially(call: PluginCall) { scope.launch { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val result: Result = rookPermissionsManager!!.checkHealthConnectPermissionsPartially() result.onSuccess { val ret = JSObject() ret.put("result", it) call.resolve(ret) }.onFailure { call.reject(it.message) } } } fun checkHealthConnectPermissions(call: PluginCall) { scope.launch { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } rookPermissionsManager!!.checkHealthConnectPermissions(). fold({ val ret = JSObject() ret.put("result", it) call.resolve(ret) }, { call.reject(it.message) }) } } fun checkExactAlarmPermissions(call: PluginCall) { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val result = rookPermissionsManager!!.checkExactAlarmPermissions() val ret = JSObject() ret.put("result", result) call.resolve(ret) } fun requestExactAlarmPermissions(call: PluginCall) { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val result = rookPermissionsManager!!.requestExactAlarmPermissions() val ret = JSObject() ret.put("result", result.toString()) call.resolve(ret) } fun checkBatteryOptimizationsDisabled(call: PluginCall) { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val result = rookPermissionsManager!!.checkBatteryOptimizationsDisabled() val ret = JSObject() ret.put("result", result) call.resolve(ret) } fun checkHistoryReadStatus(call: PluginCall) { scope.launch { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } rookPermissionsManager!!.checkHistoryReadStatus(). fold({ val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) }) } } fun requestDisableBatteryOptimizations(call: PluginCall) { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val result = rookPermissionsManager!!.requestDisableBatteryOptimizations() val ret = JSObject() ret.put("result", result.toString()) call.resolve(ret) } fun requiresOemAutoStartSetup(call: PluginCall) { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val result = rookPermissionsManager!!.requiresOemAutoStartSetup() val ret = JSObject() ret.put("result", result) call.resolve(ret) } fun openOemAutoStartSetup(call: PluginCall) { if (rookConfigurationManager == null && rookPermissionsManager == null) { call.reject(INIT_ERROR_MESSAGE) } rookPermissionsManager!!.openOemAutoStartSetup().fold({ val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) }) } // MARK: - Health Connect Background fun checkBackgroundReadStatus(call: PluginCall) { scope.launch { if (rookConfigurationManager != null && rookPermissionsManager != null) { rookPermissionsManager!!.checkBackgroundReadStatus().fold({ val ret = JSObject() ret.put("result", it.toString()) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject(INIT_ERROR_MESSAGE) return@launch } } } fun isHealthConnectBackgroundScheduled(call: PluginCall) { scope.launch { if (rookConfigurationManager != null && backgroundManager != null) { backgroundManager!!.isScheduled().fold({ val ret = JSObject() ret.put("result", it) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject(INIT_ERROR_MESSAGE) return@launch } } } // MARK: - Background fun cancelBackground(call: PluginCall) { scope.launch { if (rookConfigurationManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } backgroundManager!!.cancel() val ret = JSObject() ret.put("result", true) call.resolve(ret) } } fun scheduleBackground(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || backgroundManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val result: Unit = backgroundManager!!.schedule(areLogsEnable) val ret = JSObject() ret.put("result", result) call.resolve(ret) } } fun scheduleSamsungHealthBackGround(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val result: Unit = samsungManager!!.schedule(areLogsEnable) val ret = JSObject() ret.put("result", result) call.resolve(ret) } } fun cancelSamsungHealthBackGround(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } samsungManager!!.cancel() val ret = JSObject() ret.put("result", true) call.resolve(ret) } } fun isSamsungHealthBackgroundScheduled(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } samsungManager!!.isScheduled().fold ({ val ret = JSObject() ret.put("result", it) call.resolve(ret) }, { call.reject(it.message) }) } } @SuppressLint("MissingPermission") private suspend fun attemptToEnableBackgroundSync(enableBackgroundSync: Boolean, enableLogs: Boolean) { if (!enableBackgroundSync || rookPermissionsManager == null || backgroundManager == null || samsungManager == null) { return } val hasBackground = rookPermissionsManager!!.checkBackgroundReadStatus().getOrDefault( defaultValue = BackgroundReadStatus.UNAVAILABLE, ) == BackgroundReadStatus.PERMISSION_GRANTED if (hasBackground) { backgroundManager!!.schedule(enableLogs) samsungManager!!.schedule(enableLogs) } val isStepsEnable: Boolean = stepsManager!!.isBackgroundAndroidStepsActive() if (!isStepsEnable) { stepsManager!!.enableBackgroundAndroidSteps() } } // Sync Summary fun sync(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val stringDate: String? = call.getString("date") val types: JSArray? = call.getArray("types") val source: String? = call.getString("dataSource") val status: StatusToUpload = getSyncState(stringDate, types, source) when (status) { is StatusToUpload.ByDate -> { syncAllSummariesByDate( date = status.date, sources = status.sourceToSync, call = call ) } is StatusToUpload.SyncAll -> { syncAllSummaries(sources = status.sourceToSync, call = call) } is StatusToUpload.SyncByDateAndType -> { syncAllSummariesByDateAndType( date = status.date, types = status.types, sources = status.sourceToSync, call = call) } } } } fun getSleepSummary(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val stringDate: String? = call.getString("date") if (stringDate != null) { val date: LocalDate = RookDateTime.stringToLocalDate(stringDate) val source: String? = call.getString("dataSource") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "HEALTH_CONNECT" -> { uploadManager!!.getSleepSummary(date = date) .fold({ when (it) { SyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for sleep summary") } is SyncStatusWithData.Synced> -> { val jsonArray = JSArray() for (summary in it.data) { val jsonSummary: JSObject = SleepToJsonObjetMapper.map(summary = summary) jsonArray.put(jsonSummary) } val ret = JSObject() ret.put("result", jsonArray) call.resolve(ret) } } },{ call.reject(it.message) }) } "SAMSUNG" -> { samsungManager!!.getSleepSummary(date = date).fold({ when (it) { SHSyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for sleep summary") } is SHSyncStatusWithData.Synced> -> { val jsonArray = JSArray() for (summary in it.data) { val jsonSummary: JSObject = SHSleepSummaryMapper.map(summary = summary) jsonArray.put(jsonSummary) } val ret = JSObject() ret.put("result", jsonArray) call.resolve(ret) } } },{ call.reject(it.message) }) } } } else { call.reject("Invalid date") return@launch } } } fun getPhysicalSummary(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val stringDate: String? = call.getString("date") if (stringDate != null) { val date: LocalDate = RookDateTime.stringToLocalDate(stringDate) val source: String? = call.getString("dataSource") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "HEALTH_CONNECT" -> { uploadManager!!.getPhysicalSummary(date = date) .fold({ when (it) { SyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for physical summary") } is SyncStatusWithData.Synced -> { val physicalSummary: HCPhysicalSummary = it.data val jsonSummary: JSObject = PhysicalToJsonObjectMapper.map(physicalSummary) val ret = JSObject() ret.put("result", jsonSummary) call.resolve(ret) } } },{ call.reject(it.message) }) } "SAMSUNG" -> { samsungManager!!.getPhysicalSummary(date = date).fold({ when (it) { SHSyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for physical summary") } is SHSyncStatusWithData.Synced -> { val physicalSummary: SHPhysicalSummary = it.data val jsonSummary: JSObject = SHPhysicalSummaryMapper.map(physicalSummary) val ret = JSObject() ret.put("result", jsonSummary) call.resolve(ret) } } }, { call.reject(it.message) }) } } } else { call.reject("Invalid date") return@launch } } } fun getBodySummary(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val stringDate: String? = call.getString("date") if (stringDate != null) { val date: LocalDate = RookDateTime.stringToLocalDate(stringDate) val source: String? = call.getString("dataSource") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "HEALTH_CONNECT" -> { uploadManager!!.getBodySummary(date = date) .fold({ when (it) { SyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for body summary") } is SyncStatusWithData.Synced -> { val bodySummary: HCBodySummary = it.data val jsonSummary: JSObject = BodyToJsonObjectMapper.map(bodySummary) val ret = JSObject() ret.put("result", jsonSummary) call.resolve(ret) } } },{ call.reject(it.message) }) } "SAMSUNG" -> { samsungManager!!.getBodySummary(date = date).fold({ when (it) { SHSyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for body summary") } is SHSyncStatusWithData.Synced -> { val bodySummary: SHBodySummary = it.data val jsonSummary: JSObject = SHBodySummaryMapper.map(bodySummary) val ret = JSObject() ret.put("result", jsonSummary) call.resolve(ret) } } }, { call.reject(it.message) }) } } } else { call.reject("Invalid date") return@launch } } } fun getActivityEvents(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val stringDate: String? = call.getString("date") if (stringDate != null) { val date: LocalDate = RookDateTime.stringToLocalDate(stringDate) val source: String? = call.getString("dataSource") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "HEALTH_CONNECT" -> { getHCActivityEvents(date = date, call = call) } "SAMSUNG" -> { getSHActivityEvents(date = date, call = call) } } } else { call.reject("Invalid date") return@launch } } } private suspend fun getHCActivityEvents(date: LocalDate, call: PluginCall) { uploadManager!!.getActivityEvents(date).fold({ when (it) { SyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for physical events") } is SyncStatusWithData.Synced> -> { val jsonArray = JSArray() for (event in it.data) { val jsonSummary: JSObject = HCActivityMapper.map(event = event) jsonArray.put(jsonSummary) } val ret = JSObject() ret.put("result", jsonArray) call.resolve(ret) } } }, { call.reject(it.message) }) } private suspend fun getSHActivityEvents(date: LocalDate, call: PluginCall) { samsungManager!!.getActivityEvents(date).fold({ when (it) { SHSyncStatusWithData.RecordsNotFound -> { call.reject("Records not found for physical events") } is SHSyncStatusWithData.Synced> -> { val jsonArray = JSArray() for (event in it.data) { val jsonSummary: JSObject = SHActivityMapper.map(event = event) jsonArray.put(jsonSummary) } val ret = JSObject() ret.put("result", jsonArray) call.resolve(ret) } } }, { call.reject(it.message) }) } fun syncEvents(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val stringDate: String = call.getString("date", "")!! val date: LocalDate = RookDateTime.stringToLocalDate(stringDate) val stringType: String = call.getString("type", "")!! val source: String? = call.getString("dataSource") var sourceToSync = "HEALTH_CONNECT" val resultList: MutableList> = mutableListOf() if (source != null) { sourceToSync = source } when (sourceToSync) { "HEALTH_CONNECT" -> { val eventType: SyncType.Event? = getEventType(stringType) if (eventType != null) { uploadManager!!.syncEvents(date, eventType).fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } } "SAMSUNG" -> { val eventType: SHSyncType.Event? = getSHEventType(stringType) if (eventType != null) { val result = samsungManager!!.syncEvents(date, eventType) result.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } } "ALL" -> { val eventType: SyncType.Event? = getEventType(stringType) if (eventType != null) { uploadManager!!.syncEvents(date, eventType).fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } val eventTypeSH: SHSyncType.Event? = getSHEventType(stringType) if (eventTypeSH != null) { val result = samsungManager!!.syncEvents(date, eventTypeSH) result.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } } } handleResult(resultList, call) } } private fun getEventType(stringType: String): SyncType.Event? { return when (stringType) { "activity" -> SyncType.Event.ACTIVITY "heart_rate" -> SyncType.Event.HEART_RATE "oxygenation" -> SyncType.Event.OXYGENATION "temperature" -> SyncType.Event.TEMPERATURE "blood_pressure" -> SyncType.Event.BLOOD_PRESSURE "blood_glucose" -> SyncType.Event.BLOOD_GLUCOSE "calories" -> SyncType.Event.CALORIES "steps" -> SyncType.Event.STEPS "body_metrics" -> SyncType.Event.BODY_METRICS "hydration" -> SyncType.Event.HYDRATION "nutrition" -> SyncType.Event.NUTRITION else -> null } } private fun getSHEventType(stringType: String): SHSyncType.Event? { return when (stringType) { "activity" -> SHSyncType.Event.ACTIVITY "heart_rate" -> SHSyncType.Event.HEART_RATE "oxygenation" -> SHSyncType.Event.OXYGENATION "blood_pressure" -> SHSyncType.Event.BLOOD_PRESSURE "blood_glucose" -> SHSyncType.Event.BLOOD_GLUCOSE "calories" -> SHSyncType.Event.CALORIES "steps" -> SHSyncType.Event.STEPS "body_metrics" -> SHSyncType.Event.BODY_METRICS "hydration" -> SHSyncType.Event.HYDRATION "nutrition" -> SHSyncType.Event.NUTRITION else -> null } } private suspend fun syncAllSummaries(sources: String, call: PluginCall) { val resultList: MutableList> = mutableListOf() when (sources) { "HEALTH_CONNECT" -> { val healthConnectResult: Result = uploadManager!!.sync(areLogsEnable) resultList.add(healthConnectResult) } "SAMSUNG" -> { val samsungResult: Result = samsungManager!!.sync(areLogsEnable) resultList.add(samsungResult) } "ALL" -> { val healthConnectResult: Result = uploadManager!!.sync(areLogsEnable) val samsungResult: Result = samsungManager!!.sync(areLogsEnable) resultList.add(healthConnectResult) resultList.add(samsungResult) } else -> { val healthConnectResult: Result = uploadManager!!.sync(areLogsEnable) val samsungResult: Result = samsungManager!!.sync(areLogsEnable) resultList.add(healthConnectResult) resultList.add(samsungResult) } } handleResult(resultList, call) } private suspend fun syncAllSummariesByDate(date: LocalDate, sources: String, call: PluginCall) { val resultList: MutableList> = mutableListOf() when (sources) { "HEALTH_CONNECT" -> { uploadManager!!.sync(date).fold({ resultList.add(Result.success(true)) },{ for (error in it) { resultList.add(Result.failure(error)) } }) } "SAMSUNG" -> { val samsungResult: SHSyncSummariesResult = samsungManager!!.sync(date) samsungResult.fold({ resultList.add(Result.success(true)) }, { for (error in it) { resultList.add(Result.failure(error)) } }) } "ALL" -> { uploadManager!!.sync(date).fold({ resultList.add(Result.success(true)) },{ for (error in it) { resultList.add(Result.failure(error)) } }) val samsungResult = samsungManager!!.sync(date) samsungResult.fold({ resultList.add(Result.success(true)) }, { for (error in it) { resultList.add(Result.failure(error)) } }) } else -> { uploadManager!!.sync(date).fold({ resultList.add(Result.success(true)) },{ for (error in it) { resultList.add(Result.failure(error)) } }) val samsungResult = samsungManager!!.sync(date) samsungResult.fold({ resultList.add(Result.success(true)) }, { for (error in it) { resultList.add(Result.failure(error)) } }) } } handleResult(resultList, call) } private suspend fun syncAllSummariesByDateAndType( date: LocalDate, types: List, sources: String, call: PluginCall) { val resultList: MutableList> = mutableListOf() when (sources) { "HEALTH_CONNECT" -> { if (types.contains("sleep")) { val healthConnectSleep = uploadManager!!.sync(date, SyncType.Summary.SLEEP) healthConnectSleep.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } if (types.contains("body")) { val bodyHealthConnect = uploadManager!!.sync(date, SyncType.Summary.BODY) bodyHealthConnect.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } if (types.contains("physical")) { val physicalHealthConnect = uploadManager!!.sync(date, SyncType.Summary.SLEEP) physicalHealthConnect.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } } "SAMSUNG" -> { if (types.contains("sleep")) { val sleepSamsung = samsungManager!!.sync(date, SHSyncType.Summary.SLEEP) sleepSamsung.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } if (types.contains("body")) { val bodySamsung = samsungManager!!.sync(date, SHSyncType.Summary.BODY) bodySamsung.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } if (types.contains("physical")) { val physicalSamsung = samsungManager!!.sync(date, SHSyncType.Summary.PHYSICAL) physicalSamsung.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } } "ALL" -> { if (types.contains("sleep")) { val sleepSamsung = samsungManager!!.sync(date, SHSyncType.Summary.SLEEP) sleepSamsung.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) val healthConnectSleep = uploadManager!!.sync(date, SyncType.Summary.SLEEP) healthConnectSleep.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } if (types.contains("body")) { val bodySamsung = samsungManager!!.sync(date, SHSyncType.Summary.BODY) bodySamsung.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) val bodyHealthConnect = uploadManager!!.sync(date, SyncType.Summary.BODY) bodyHealthConnect.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } if (types.contains("physical")) { val physicalSamsung = samsungManager!!.sync(date, SHSyncType.Summary.PHYSICAL) physicalSamsung.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) val physicalHealthConnect = uploadManager!!.sync(date, SyncType.Summary.SLEEP) physicalHealthConnect.fold({ resultList.add(Result.success(true)) }, { resultList.add(Result.failure(it)) }) } } } handleResult(resultList, call) } private fun handleResult(results: List>, call: PluginCall) { var response = true var errorMessage = "" for (result in results) { result.fold({ response = response && it }, { response = false errorMessage += it.message }) } if (errorMessage.isEmpty()) { val ret = JSObject() ret.put("result", true) call.resolve(ret) } else { call.reject(errorMessage) } } private fun getSyncState(stringDate: String?, types: JSArray?, source: String?): StatusToUpload { var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } if (stringDate == null) { return StatusToUpload.SyncAll(sourceToSync) } else if (types == null) { return StatusToUpload.ByDate(RookDateTime.stringToLocalDate(stringDate), sourceToSync) } else { val stringTypes: List = (0 until types.length()).map { types.getString(it) } return StatusToUpload.SyncByDateAndType(RookDateTime.stringToLocalDate(stringDate), stringTypes, sourceToSync) } } fun reSyncFailedSummaries(call: PluginCall) { scope.launch { if (rookConfigurationManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } uploadManager!!.sync(areLogsEnable) .fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } } // MARK: - Events fun syncPendingEvents(call: PluginCall) { scope.launch { if (rookConfigurationManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val ret = JSObject() ret.put("result", true) call.resolve(ret) } } fun syncTodayAndroidStepsCount(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || stepsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } stepsManager!!.syncTodayAndroidStepsCount() .fold({ val ret = JSObject() val steps: Int = it.toInt() ret.put("stepCount", steps) call.resolve(ret) }, { call.reject(it.message) }) } } fun syncTodayStepsCount(call: PluginCall) { val source: String? = call.getString("source") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "SAMSUNG" -> { getTodaySamsungStepCount(call) } "HEALTH_CONNECT" -> { syncHealthConnectTodayStepsCount(call) } else -> { syncHealthConnectTodayStepsCount(call) } } } fun getTodayCaloriesCount(call: PluginCall) { val source: String? = call.getString("source") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "SAMSUNG" -> { getTodaySamsungCaloriesCount(call) } "HEALTH_CONNECT" -> { getTodayHealthConnectCaloriesCount(call) } else -> { getTodayHealthConnectCaloriesCount(call) } } } fun getTodayHeartRate(call: PluginCall) { val source: String? = call.getString("source") var sourceToSync = "HEALTH_CONNECT" if (source != null) { sourceToSync = source } when (sourceToSync) { "SAMSUNG" -> { getTodaySamsungHeartRate(call) } "HEALTH_CONNECT" -> { getTodayHealthConnectHeartRate(call) } else -> { getTodayHealthConnectHeartRate(call) } } } fun syncHealthConnectTodayStepsCount(call: PluginCall) { scope.launch { if (rookConfigurationManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } uploadManager!!.getTodayStepsCount() .fold({ when (it) { SyncStatusWithData.RecordsNotFound -> { call.reject("Empty Data") } is SyncStatusWithData.Synced -> { val ret = JSObject() val steps: Int = it.data ret.put("stepCount", steps) call.resolve(ret) } } }, { call.reject(it.message) }) } } fun getTodaySamsungStepCount(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } samsungManager!!.getTodayStepsCount().fold({ when (it) { SHSyncStatusWithData.RecordsNotFound -> { call.reject("Records not found") } is SHSyncStatusWithData.Synced -> { val ret = JSObject() val steps: Int = it.data ret.put("stepCount", steps) call.resolve(ret) } } }, { call.reject(it.message) }) } } fun getTodaySamsungCaloriesCount(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } samsungManager!!.getTodayCaloriesCount().fold({ when (it) { SHSyncStatusWithData.RecordsNotFound -> { call.reject("Records not found") } is SHSyncStatusWithData.Synced -> { val ret = JSObject() ret.put("basal", it.data.basal) ret.put("active", it.data.active) call.resolve(ret) } } }, { call.reject(it.message) }) } } fun getTodayHealthConnectCaloriesCount(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } uploadManager!!.getTodayCaloriesCount().fold({ syncStatus -> when (syncStatus) { SyncStatusWithData.RecordsNotFound -> { call.reject("No calories found") } is SyncStatusWithData.Synced -> { val ret = JSObject() ret.put("basal", syncStatus.data.basal) ret.put("active", syncStatus.data.active) call.resolve(ret) } }}, { exception -> call.reject(exception.message) }) } } // MARK: - Heart Rate fun getTodayHealthConnectHeartRate(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || uploadManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } else { uploadManager!!.getTodayHeartRate().fold({ when (it) { SyncStatusWithData.RecordsNotFound -> { call.reject("RecordsNotFound") } is SyncStatusWithData.Synced -> { val jsonEvent: JSObject = HCHeartRateMapper.map(it.data) val ret = JSObject() ret.put("result", jsonEvent) call.resolve(ret) } } }, { call.reject(it.message) }) } } } fun getTodaySamsungHeartRate(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || samsungManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } else { samsungManager!!.getTodayHeartRate().fold({ when (it) { SHSyncStatusWithData.RecordsNotFound -> { call.reject("RecordsNotFound") } is SHSyncStatusWithData.Synced -> { val jsonEvent: JSObject = SHHeartRateMapper.map(it.data) val ret = JSObject() ret.put("result", jsonEvent) call.resolve(ret) } } }, { call.reject(it.message) }) } } } // MARK: - Write Nutrition fun writeNutritionEvent(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || rookWriteManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } else { val eventObj = call.getObject("event") eventObj.getDouble("") if (eventObj != null) { rookWriteManager!!.writeNutritionEvent( event = NutritionEventDTOMapper.map(eventObj) ).fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject("Missing 'event' object") return@launch } } } } // MARK: - Steps fun enableBackgroundAndroidSteps(call: PluginCall) { if (rookConfigurationManager == null || stepsManager == null) { call.reject(INIT_ERROR_MESSAGE) return } stepsManager!!.enableBackgroundAndroidSteps().fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } fun disableBackgroundAndroidSteps(call: PluginCall) { if (rookConfigurationManager == null || stepsManager == null) { call.reject(INIT_ERROR_MESSAGE) return } stepsManager!!.disableBackgroundAndroidSteps().fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } fun isBackgroundAndroidStepsActive(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || stepsManager == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val response = stepsManager!!.isBackgroundAndroidStepsActive() val ret = JSObject() ret.put("result", response) call.resolve(ret) } } fun getDataSourcesAuthorized(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || rookAPIDataSource == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val idStored: String? = rookConfigurationManager!!.getUserID() val id: String? = call.getString("userId") ?: idStored if (id != null) { rookAPIDataSource!!.getAuthorizedDataSourcesV2(id).fold({ val sources: JSArray = mapAuthSources(it) val ret = JSObject() ret.put("result", sources) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject(MISSING_USER_IR) return@launch } } } fun getDataSourceAuthorizer(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || rookAPIDataSource == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val source: String? = call.getString("source", "") val redirectUrl: String? = call.getString("redirectUrl") val idStored: String? = rookConfigurationManager!!.getUserID() val id: String? = call.getString("userId") ?: idStored if (source != null && id != null) { rookAPIDataSource!!.getDataSourceAuthorizer(id, source, redirectUrl).fold({ val ret = JSObject() ret.put("dataSource", it.dataSource) ret.put("authorized", it.authorized) ret.put("authorizationUrl", it.authorizationUrl) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject("missing source") } } } fun revokeDataSource(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || rookAPIDataSource == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val dataSourceName: String? = call.getString("dataSource") val id: String? = call.getString("userId", "") if (dataSourceName != null && id != null) { val dataSource: DataSourceType = DataSourceType.valueOf(dataSourceName.uppercase()) val response = rookAPIDataSource!!.revokeDataSource(id, dataSource) response.fold({ val ret = JSObject() ret.put("result", true) call.resolve(ret) }, { call.reject(it.message) }) } else { call.reject("Invalid data source") } } } fun isRookStepsCounterAvailable(call: PluginCall) { if (rookConfigurationManager == null || stepsManager == null) { call.reject(INIT_ERROR_MESSAGE) } val response = rookStepCounter!!.isStepsCounterAvailable() val ret = JSObject() ret.put("result", response) call.resolve(ret) } fun isRookStepsCounterActive(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || rookStepCounter == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } val response = rookStepCounter!!.isStepsCounterActive() val ret = JSObject() ret.put("result", response) call.resolve(ret) } } fun enableRookStepsCounter(call: PluginCall) { if (rookConfigurationManager == null || rookStepCounter == null) { call.reject(INIT_ERROR_MESSAGE) } val response = rookStepCounter!!.enableStepsCounter() val ret = JSObject() ret.put("result", response.isSuccess) call.resolve(ret) } fun disableRookStepsCounter(call: PluginCall) { if (rookConfigurationManager == null || rookStepCounter == null) { call.reject(INIT_ERROR_MESSAGE) } val response = rookStepCounter!!.disableStepsCounter() val ret = JSObject() ret.put("result", response.isSuccess) call.resolve(ret) } fun getRookTodayStepsCount(call: PluginCall) { scope.launch { if (rookConfigurationManager == null || rookStepCounter == null) { call.reject(INIT_ERROR_MESSAGE) return@launch } rookStepCounter!!.getTodayStepsCount().fold({ val ret = JSObject() val steps: Long = it ret.put("stepCount", steps) call.resolve(ret) }, { call.reject(it.message) }) } } private fun mapAuthSources(sources: List): JSArray { val jsonArray = JSArray() for (source in sources) { val jsonObject = JSObject() jsonObject.put("source", source.name) jsonObject.put("status", source.authorized) jsonObject.put("imageURL", source.imageUrl) jsonArray.put(jsonObject) } return jsonArray } }