-
Notifications
You must be signed in to change notification settings - Fork 247
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(amplify_auth_cognito): Auth Devices API (#735)
* Add platform code * Clean up * Add licenses * Fix error handling * Add logging and update threading logic * Add iOS unit tests * Clean up * Add tests to project * Fix unit tests
- Loading branch information
Showing
26 changed files
with
1,037 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
...o/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/base/AtomicResult.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
package com.amazonaws.amplify.amplify_auth_cognito.base | ||
|
||
import io.flutter.Log | ||
import io.flutter.plugin.common.MethodChannel | ||
import kotlinx.coroutines.* | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
|
||
/** | ||
* Thread-safe [MethodChannel.Result] wrapper which prevents multiple replies and automatically posts | ||
* results to the main thread. | ||
*/ | ||
class AtomicResult(private val result: MethodChannel.Result, private val operation: String) : | ||
MethodChannel.Result { | ||
private companion object { | ||
/** | ||
* Scope for performing result handling. | ||
* Method channel results must be sent on the main (UI) thread. | ||
*/ | ||
val scope = MainScope() | ||
} | ||
|
||
/** | ||
* Whether a response has been sent. | ||
*/ | ||
private val isSent = AtomicBoolean(false) | ||
|
||
override fun success(value: Any?) { | ||
scope.launch { | ||
if (isSent.getAndSet(true)) { | ||
Log.w( | ||
"AtomicResult(${operation})", | ||
"Attempted to send success value after initial reply" | ||
) | ||
return@launch | ||
} | ||
result.success(value) | ||
} | ||
} | ||
|
||
override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) { | ||
scope.launch { | ||
if (isSent.getAndSet(true)) { | ||
Log.w( | ||
"AtomicResult(${operation})", | ||
""" | ||
Attempted to send error value after initial reply: | ||
| PlatformException{code=${errorCode}, message=${errorMessage}, details=${errorDetails}} | ||
""".trimMargin() | ||
) | ||
return@launch | ||
} | ||
result.error(errorCode, errorMessage, errorDetails) | ||
} | ||
} | ||
|
||
override fun notImplemented() { | ||
scope.launch { | ||
if (isSent.getAndSet(true)) { | ||
Log.w( | ||
"AtomicResult(${operation})", | ||
"Attempted to send notImplemented value after initial reply" | ||
) | ||
return@launch | ||
} | ||
result.notImplemented() | ||
} | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
...gnito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/device/Device.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
package com.amazonaws.amplify.amplify_auth_cognito.device | ||
|
||
import com.amazonaws.mobile.client.results.Device | ||
import java.time.Instant | ||
import java.util.* | ||
|
||
/** | ||
* Attribute key for retrieving a [Device] instance's name. | ||
*/ | ||
const val deviceNameKey = "device_name" | ||
|
||
/** | ||
* The device's name, if set. | ||
*/ | ||
val Device.deviceName: String? | ||
get() = attributes?.get(deviceNameKey) | ||
|
||
/** | ||
* Converts this device to a JSON-representable format. | ||
*/ | ||
fun Device.toJson(): Map<String, Any?> = mapOf( | ||
"id" to deviceKey, | ||
"name" to deviceName, | ||
"attributes" to attributes, | ||
"createdDate" to createDate?.time, | ||
"lastModifiedDate" to lastModifiedDate?.time, | ||
"lastAuthenticatedDate" to lastAuthenticatedDate?.time | ||
) |
119 changes: 119 additions & 0 deletions
119
...ndroid/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/device/DeviceHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
package com.amazonaws.amplify.amplify_auth_cognito.device | ||
|
||
import com.amazonaws.amplify.amplify_auth_cognito.AuthErrorHandler | ||
import com.amazonaws.amplify.amplify_auth_cognito.base.AtomicResult | ||
import com.amazonaws.mobile.client.AWSMobileClient | ||
import com.amazonaws.mobile.client.Callback | ||
import com.amazonaws.mobile.client.results.ListDevicesResult | ||
import com.amplifyframework.auth.AuthDevice | ||
import com.amplifyframework.auth.cognito.util.CognitoAuthExceptionConverter | ||
import com.amplifyframework.core.Amplify | ||
import io.flutter.plugin.common.MethodCall | ||
import io.flutter.plugin.common.MethodChannel | ||
import kotlinx.coroutines.* | ||
|
||
/** | ||
* Handles method calls for the Devices API. | ||
*/ | ||
class DeviceHandler(private val errorHandler: AuthErrorHandler) : | ||
MethodChannel.MethodCallHandler { | ||
companion object { | ||
/** | ||
* Methods this handler supports. | ||
*/ | ||
private val methods = listOf("rememberDevice", "forgetDevice", "fetchDevices") | ||
|
||
/** | ||
* Whether this class can handle [method]. | ||
*/ | ||
fun canHandle(method: String): Boolean = methods.contains(method) | ||
} | ||
|
||
/** | ||
* Scope for running asynchronous tasks. | ||
*/ | ||
private val scope = CoroutineScope(Dispatchers.IO) + CoroutineName("DeviceHandler") | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
override fun onMethodCall(call: MethodCall, _result: MethodChannel.Result) { | ||
val result = AtomicResult(_result, call.method) | ||
when (call.method) { | ||
"fetchDevices" -> fetchDevices(result) | ||
"rememberDevice" -> rememberDevice(result) | ||
"forgetDevice" -> { | ||
val deviceJson = | ||
(call.arguments as? Map<*, *> ?: emptyMap<String, Any?>()) as Map<String, Any?> | ||
var device: AuthDevice? = null | ||
if (deviceJson.isNotEmpty()) { | ||
val id by deviceJson | ||
device = AuthDevice.fromId(id as String) | ||
} | ||
forgetDevice(result, device) | ||
} | ||
} | ||
} | ||
|
||
private fun fetchDevices(result: MethodChannel.Result) { | ||
try { | ||
val cognitoAuthPlugin = Amplify.Auth.getPlugin("awsCognitoAuthPlugin") | ||
val awsMobileClient = cognitoAuthPlugin.escapeHatch as AWSMobileClient | ||
scope.launch { | ||
awsMobileClient.deviceOperations.list(object : Callback<ListDevicesResult> { | ||
override fun onResult(listDevicesResult: ListDevicesResult) { | ||
result.success(listDevicesResult.devices.map { it.toJson() }) | ||
} | ||
|
||
override fun onError(exception: java.lang.Exception) { | ||
errorHandler.handleAuthError( | ||
result, CognitoAuthExceptionConverter.lookup( | ||
exception, "Fetching devices failed." | ||
) | ||
) | ||
} | ||
}) | ||
} | ||
} catch (e: Exception) { | ||
errorHandler.handleAuthError(result, e) | ||
} | ||
} | ||
|
||
private fun rememberDevice(result: MethodChannel.Result) { | ||
scope.launch { | ||
Amplify.Auth.rememberDevice( | ||
{ result.success(null) }, | ||
{ errorHandler.handleAuthError(result, it) } | ||
) | ||
} | ||
} | ||
|
||
private fun forgetDevice(result: MethodChannel.Result, device: AuthDevice? = null) { | ||
scope.launch { | ||
if (device != null) { | ||
Amplify.Auth.forgetDevice( | ||
device, | ||
{ result.success(null) }, | ||
{ errorHandler.handleAuthError(result, it) } | ||
) | ||
} else { | ||
Amplify.Auth.forgetDevice( | ||
{ result.success(null) }, | ||
{ errorHandler.handleAuthError(result, it) } | ||
) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.