Skip to content

Commit e1d88f9

Browse files
committed
feat: add android support
1 parent 8065ea4 commit e1d88f9

File tree

3 files changed

+415
-0
lines changed

3 files changed

+415
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package com.amazonaws.amplify.rtnpasskeys
2+
3+
import androidx.annotation.ChecksSdkIntAtLeast
4+
import androidx.credentials.CreateCredentialResponse
5+
import androidx.credentials.CreatePublicKeyCredentialRequest
6+
import androidx.credentials.CreatePublicKeyCredentialResponse
7+
import androidx.credentials.CredentialManager
8+
import androidx.credentials.GetCredentialRequest
9+
import androidx.credentials.GetCredentialResponse
10+
import androidx.credentials.GetPublicKeyCredentialOption
11+
import androidx.credentials.PublicKeyCredential
12+
import androidx.credentials.exceptions.CreateCredentialCancellationException
13+
import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
14+
import androidx.credentials.exceptions.CreateCredentialUnsupportedException
15+
import androidx.credentials.exceptions.GetCredentialCancellationException
16+
import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
17+
import androidx.credentials.exceptions.GetCredentialUnsupportedException
18+
import androidx.credentials.exceptions.domerrors.DataError
19+
import androidx.credentials.exceptions.domerrors.InvalidStateError
20+
import androidx.credentials.exceptions.domerrors.NotAllowedError
21+
import androidx.credentials.exceptions.publickeycredential.CreatePublicKeyCredentialDomException
22+
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException
23+
24+
import com.facebook.fbreact.specs.NativeAmplifyRtnPasskeysSpec
25+
import com.facebook.react.bridge.JSONArguments
26+
import com.facebook.react.bridge.Promise
27+
import com.facebook.react.bridge.ReactApplicationContext
28+
import com.facebook.react.bridge.ReadableMap
29+
import com.facebook.react.module.annotations.ReactModule
30+
import kotlinx.coroutines.CoroutineDispatcher
31+
32+
import kotlinx.coroutines.CoroutineScope
33+
import kotlinx.coroutines.Dispatchers
34+
import kotlinx.coroutines.launch
35+
36+
import org.json.JSONObject
37+
38+
@ReactModule(name = AmplifyRtnPasskeysModule.NAME)
39+
class AmplifyRtnPasskeysModule(
40+
reactContext: ReactApplicationContext,
41+
dispatcher: CoroutineDispatcher = Dispatchers.Default
42+
) :
43+
NativeAmplifyRtnPasskeysSpec(reactContext) {
44+
45+
private val moduleScope = CoroutineScope(dispatcher)
46+
47+
override fun getName(): String {
48+
return NAME
49+
}
50+
51+
companion object {
52+
const val NAME = "AmplifyRtnPasskeys"
53+
}
54+
55+
@ChecksSdkIntAtLeast(api = android.os.Build.VERSION_CODES.P)
56+
override fun getIsPasskeySupported(): Boolean {
57+
// Requires Android SDK >= 28 (PIE)
58+
return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P
59+
}
60+
61+
override fun createPasskey(input: ReadableMap, promise: Promise) {
62+
if (!isPasskeySupported) {
63+
return promise.reject(
64+
"NOT_SUPPORTED",
65+
CreateCredentialUnsupportedException("CreatePasskeyNotSupported")
66+
)
67+
}
68+
69+
val credentialManager = CredentialManager.create(reactApplicationContext.applicationContext)
70+
71+
val requestJson = JSONObject(input.toHashMap()).toString()
72+
val request =
73+
CreatePublicKeyCredentialRequest(requestJson = requestJson)
74+
75+
moduleScope.launch {
76+
try {
77+
val result: CreateCredentialResponse =
78+
credentialManager.createCredential(
79+
context = currentActivity ?: reactApplicationContext,
80+
request = request
81+
)
82+
83+
val publicKeyResult =
84+
result as? CreatePublicKeyCredentialResponse
85+
?: throw Exception("CreatePasskeyFailed")
86+
87+
val jsonObject = JSONObject(publicKeyResult.registrationResponseJson)
88+
89+
promise.resolve(JSONArguments.fromJSONObject(jsonObject))
90+
} catch (e: Exception) {
91+
val errorCode = handlePasskeyFailure(e)
92+
promise.reject(errorCode, e)
93+
}
94+
}
95+
}
96+
97+
override fun getPasskey(input: ReadableMap, promise: Promise) {
98+
if (!isPasskeySupported) {
99+
return promise.reject(
100+
"NOT_SUPPORTED",
101+
GetCredentialUnsupportedException("GetPasskeyNotSupported")
102+
)
103+
}
104+
105+
val credentialManager = CredentialManager.create(reactApplicationContext.applicationContext)
106+
107+
val requestJson = JSONObject(input.toHashMap()).toString()
108+
val options =
109+
GetPublicKeyCredentialOption(requestJson = requestJson)
110+
val request = GetCredentialRequest(credentialOptions = listOf(options))
111+
112+
moduleScope.launch {
113+
try {
114+
val result: GetCredentialResponse =
115+
credentialManager.getCredential(
116+
context = currentActivity ?: reactApplicationContext,
117+
request = request
118+
)
119+
120+
val publicKeyResult =
121+
result.credential as? PublicKeyCredential ?: throw Exception("GetPasskeyFailed")
122+
123+
val jsonObject = JSONObject(publicKeyResult.authenticationResponseJson)
124+
125+
promise.resolve(JSONArguments.fromJSONObject(jsonObject))
126+
} catch (e: Exception) {
127+
val errorCode = handlePasskeyFailure(e)
128+
promise.reject(errorCode, e)
129+
}
130+
}
131+
}
132+
133+
private fun handlePasskeyFailure(e: Exception): String {
134+
return when (e) {
135+
is CreatePublicKeyCredentialDomException -> {
136+
when (e.domError) {
137+
is NotAllowedError -> "CANCELED"
138+
is InvalidStateError -> "DUPLICATE"
139+
is DataError -> "RELYING_PARTY_MISMATCH"
140+
else -> "FAILED"
141+
}
142+
}
143+
144+
is GetPublicKeyCredentialDomException -> {
145+
when (e.domError) {
146+
is NotAllowedError -> "CANCELED"
147+
is DataError -> "RELYING_PARTY_MISMATCH"
148+
else -> "FAILED"
149+
}
150+
}
151+
152+
is CreateCredentialCancellationException,
153+
is GetCredentialCancellationException -> "CANCELED"
154+
155+
is CreateCredentialUnsupportedException,
156+
is CreateCredentialProviderConfigurationException,
157+
is GetCredentialUnsupportedException,
158+
is GetCredentialProviderConfigurationException -> "NOT_SUPPORTED"
159+
160+
else -> "FAILED"
161+
}
162+
}
163+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.amazonaws.amplify.rtnpasskeys
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 AmplifyRtnPasskeysPackage : BaseReactPackage() {
10+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
11+
return if (name == AmplifyRtnPasskeysModule.NAME) {
12+
AmplifyRtnPasskeysModule(reactContext)
13+
} else {
14+
null
15+
}
16+
}
17+
18+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
19+
return ReactModuleInfoProvider {
20+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
21+
moduleInfos[AmplifyRtnPasskeysModule.NAME] = ReactModuleInfo(
22+
AmplifyRtnPasskeysModule.NAME,
23+
AmplifyRtnPasskeysModule.NAME,
24+
false, // canOverrideExistingModule
25+
false, // needsEagerInit
26+
false, // isCxxModule
27+
true // isTurboModule
28+
)
29+
moduleInfos
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)