@dynamic-labs-sdk/client 1.6.0 → 1.8.0
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.
- package/android/build.gradle +231 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +8 -0
- package/android/gradlew +251 -0
- package/android/gradlew.bat +94 -0
- package/android/settings.gradle +30 -0
- package/android/src/main/java/xyz/dynamic/client/DynamicClientPackage.kt +52 -0
- package/android/src/main/java/xyz/dynamic/client/keychain/KeyStoreKeyManager.kt +147 -0
- package/android/src/main/java/xyz/dynamic/client/keychain/KeychainModule.kt +85 -0
- package/android/src/main/java/xyz/dynamic/client/manifest/ReactNativeManifestModule.kt +25 -0
- package/android/src/main/java/xyz/dynamic/client/webview/DynamicWebViewClient.kt +83 -0
- package/android/src/main/java/xyz/dynamic/client/webview/DynamicWebViewHost.kt +518 -0
- package/android/src/main/java/xyz/dynamic/client/webview/DynamicWebViewModule.kt +147 -0
- package/android/src/test/java/xyz/dynamic/client/keychain/KeyStoreKeyManagerTest.kt +288 -0
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewClientTest.kt +196 -0
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewHostOpenTest.kt +347 -0
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewHostTest.kt +171 -0
- package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewModuleTest.kt +191 -0
- package/dist/{InvalidParamError-BIptAjbC.native.esm.js → InvalidParamError-Bc6OZBog.native.esm.js} +91 -829
- package/dist/InvalidParamError-Bc6OZBog.native.esm.js.map +1 -0
- package/dist/{InvalidParamError-BZ0LHaml.esm.js → InvalidParamError-IHYLdzFd.esm.js} +108 -381
- package/dist/InvalidParamError-IHYLdzFd.esm.js.map +1 -0
- package/dist/{InvalidParamError-DyXEKMWn.cjs → InvalidParamError-S56rFFDP.cjs} +108 -450
- package/dist/InvalidParamError-S56rFFDP.cjs.map +1 -0
- package/dist/{NotWaasWalletAccountError-nT4jrLbp.esm.js → NotWaasWalletAccountError-BP-bODbJ.native.esm.js} +4 -4
- package/dist/NotWaasWalletAccountError-BP-bODbJ.native.esm.js.map +1 -0
- package/dist/{NotWaasWalletAccountError-BbOA5rPd.native.esm.js → NotWaasWalletAccountError-BYEL2l-W.esm.js} +4 -4
- package/dist/NotWaasWalletAccountError-BYEL2l-W.esm.js.map +1 -0
- package/dist/{NotWaasWalletAccountError-CElqwdQO.cjs → NotWaasWalletAccountError-CbhIKUDM.cjs} +4 -4
- package/dist/NotWaasWalletAccountError-CbhIKUDM.cjs.map +1 -0
- package/dist/core.cjs +11 -11
- package/dist/core.cjs.map +1 -1
- package/dist/core.esm.js +5 -5
- package/dist/core.native.esm.js +7 -7
- package/dist/core.native.esm.js.map +1 -1
- package/dist/errors/WebViewLoadFailedError.d.ts +1 -1
- package/dist/errors/WebViewLoadFailedError.d.ts.map +1 -1
- package/dist/exports/index.d.ts +2 -0
- package/dist/exports/index.d.ts.map +1 -1
- package/dist/{getNetworkProviderFromNetworkId-DbI5MZhq.cjs → getNetworkProviderFromNetworkId-BDdcpGHH.cjs} +56 -55
- package/dist/{getNetworkProviderFromNetworkId-DbI5MZhq.cjs.map → getNetworkProviderFromNetworkId-BDdcpGHH.cjs.map} +1 -1
- package/dist/{getNetworkProviderFromNetworkId-BkAPIdRC.esm.js → getNetworkProviderFromNetworkId-D8rWYXlT.esm.js} +54 -54
- package/dist/{getNetworkProviderFromNetworkId-BkAPIdRC.esm.js.map → getNetworkProviderFromNetworkId-D8rWYXlT.esm.js.map} +1 -1
- package/dist/{getNetworkProviderFromNetworkId-B7DIqT2T.native.esm.js → getNetworkProviderFromNetworkId-DVmF7pH2.native.esm.js} +312 -41
- package/dist/getNetworkProviderFromNetworkId-DVmF7pH2.native.esm.js.map +1 -0
- package/dist/{getSignedSessionId-BF6n8gve.cjs → getSignedSessionId-BBaBCi9S.cjs} +3 -3
- package/dist/{getSignedSessionId-BF6n8gve.cjs.map → getSignedSessionId-BBaBCi9S.cjs.map} +1 -1
- package/dist/{getSignedSessionId-y3YtxFHZ.esm.js → getSignedSessionId-CND07Eca.esm.js} +3 -3
- package/dist/{getSignedSessionId-y3YtxFHZ.esm.js.map → getSignedSessionId-CND07Eca.esm.js.map} +1 -1
- package/dist/{getSignedSessionId-1TV-x__C.native.esm.js → getSignedSessionId-RwV7JPn8.native.esm.js} +3 -3
- package/dist/{getSignedSessionId-1TV-x__C.native.esm.js.map → getSignedSessionId-RwV7JPn8.native.esm.js.map} +1 -1
- package/dist/getVerifiedCredentialForWalletAccount-BVrGV8qf.native.esm.js +701 -0
- package/dist/getVerifiedCredentialForWalletAccount-BVrGV8qf.native.esm.js.map +1 -0
- package/dist/{getVerifiedCredentialForWalletAccount-etV50QRH.esm.js → getVerifiedCredentialForWalletAccount-BiOS65Tk.esm.js} +2 -2
- package/dist/{getVerifiedCredentialForWalletAccount-etV50QRH.esm.js.map → getVerifiedCredentialForWalletAccount-BiOS65Tk.esm.js.map} +1 -1
- package/dist/{getVerifiedCredentialForWalletAccount-BfJ29erz.cjs → getVerifiedCredentialForWalletAccount-FrxYWSuD.cjs} +3 -2
- package/dist/{getVerifiedCredentialForWalletAccount-BfJ29erz.cjs.map → getVerifiedCredentialForWalletAccount-FrxYWSuD.cjs.map} +1 -1
- package/dist/index.cjs +35 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +14 -14
- package/dist/index.esm.js.map +1 -1
- package/dist/index.native.esm.js +14 -14
- package/dist/index.native.esm.js.map +1 -1
- package/dist/isMfaRequiredForAction-BPL_dgIM.esm.js +318 -0
- package/dist/isMfaRequiredForAction-BPL_dgIM.esm.js.map +1 -0
- package/dist/isMfaRequiredForAction-BolTrtCO.cjs +405 -0
- package/dist/isMfaRequiredForAction-BolTrtCO.cjs.map +1 -0
- package/dist/{isMfaRequiredForAction-B2vKX31S.native.esm.js → isMfaRequiredForAction-DQDoPsi6.native.esm.js} +2 -2
- package/dist/{isMfaRequiredForAction-B2vKX31S.native.esm.js.map → isMfaRequiredForAction-DQDoPsi6.native.esm.js.map} +1 -1
- package/dist/modules/apiClient/apiClient.types.d.ts +0 -12
- package/dist/modules/apiClient/apiClient.types.d.ts.map +1 -1
- package/dist/modules/apiClient/createApiClient.d.ts.map +1 -1
- package/dist/modules/auth/logoutReason.d.ts +1 -1
- package/dist/modules/auth/logoutReason.d.ts.map +1 -1
- package/dist/modules/auth/logoutWithReason/logoutWithReason.d.ts.map +1 -1
- package/dist/modules/deviceRegistration/getRegisteredDevices/getRegisteredDevices.d.ts.map +1 -1
- package/dist/modules/user/refreshAuth/refreshAuth.d.ts.map +1 -1
- package/dist/modules/waas/backupWaasKeySharesToGoogleDrive/backupWaasKeySharesToGoogleDrive.d.ts +12 -1
- package/dist/modules/waas/backupWaasKeySharesToGoogleDrive/backupWaasKeySharesToGoogleDrive.d.ts.map +1 -1
- package/dist/modules/waas/createWaasClient/createWaasClient.d.ts.map +1 -1
- package/dist/modules/waas/createWaasClient/translateNativeOpenError.d.ts.map +1 -1
- package/dist/modules/waas/createWaasProvider/createWaasProvider.d.ts.map +1 -1
- package/dist/modules/waas/waas.types.d.ts +1 -0
- package/dist/modules/waas/waas.types.d.ts.map +1 -1
- package/dist/modules/wallets/walletProvider/events/onWalletProviderEvent/onWalletProviderEvent.d.ts +1 -2
- package/dist/modules/wallets/walletProvider/events/onWalletProviderEvent/onWalletProviderEvent.d.ts.map +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/waas.cjs +7 -4
- package/dist/waas.cjs.map +1 -1
- package/dist/waas.esm.js +6 -4
- package/dist/waas.esm.js.map +1 -1
- package/dist/waas.native.esm.js +6 -4
- package/dist/waas.native.esm.js.map +1 -1
- package/dist/waasCore.cjs +10 -5
- package/dist/waasCore.cjs.map +1 -1
- package/dist/waasCore.esm.js +10 -5
- package/dist/waasCore.esm.js.map +1 -1
- package/dist/waasCore.native.esm.js +10 -8
- package/dist/waasCore.native.esm.js.map +1 -1
- package/dynamic-labs-sdk-client.podspec +27 -0
- package/ios/DynamicClientKeychain.h +4 -0
- package/ios/DynamicClientKeychain.mm +101 -0
- package/ios/DynamicClientManifest.h +4 -0
- package/ios/DynamicClientManifest.mm +45 -0
- package/ios/DynamicClientWebView.h +5 -0
- package/ios/DynamicClientWebView.mm +143 -0
- package/ios/DynamicWebViewImpl.swift +603 -0
- package/ios/LiveSecKeyAPI.swift +86 -0
- package/ios/ReactNativeManifestImpl.swift +20 -0
- package/ios/SecKeyAPI.swift +57 -0
- package/ios/SecureEnclaveKeyManager.swift +191 -0
- package/package.json +16 -5
- package/react-native.config.cjs +14 -0
- package/src/turboModules/NativeDynamicWebView.native.spec.ts +61 -0
- package/src/turboModules/NativeDynamicWebView.ts +40 -0
- package/src/turboModules/NativeKeychain.native.spec.ts +47 -0
- package/src/turboModules/NativeKeychain.ts +19 -0
- package/src/turboModules/NativeReactNativeManifest.native.spec.ts +55 -0
- package/src/turboModules/NativeReactNativeManifest.ts +28 -0
- package/dist/InvalidParamError-BIptAjbC.native.esm.js.map +0 -1
- package/dist/InvalidParamError-BZ0LHaml.esm.js.map +0 -1
- package/dist/InvalidParamError-DyXEKMWn.cjs.map +0 -1
- package/dist/NotWaasWalletAccountError-BbOA5rPd.native.esm.js.map +0 -1
- package/dist/NotWaasWalletAccountError-CElqwdQO.cjs.map +0 -1
- package/dist/NotWaasWalletAccountError-nT4jrLbp.esm.js.map +0 -1
- package/dist/getNetworkProviderFromNetworkId-B7DIqT2T.native.esm.js.map +0 -1
- package/dist/getVerifiedCredentialForWalletAccount-CLyC2nOl.native.esm.js +0 -269
- package/dist/getVerifiedCredentialForWalletAccount-CLyC2nOl.native.esm.js.map +0 -1
- package/dist/isMfaRequiredForAction-Dy4-S3mr.esm.js +0 -79
- package/dist/isMfaRequiredForAction-Dy4-S3mr.esm.js.map +0 -1
- package/dist/isMfaRequiredForAction-K2Jh-3-j.cjs +0 -96
- package/dist/isMfaRequiredForAction-K2Jh-3-j.cjs.map +0 -1
- package/dist/modules/apiClient/utils/sessionExpiryLogoutMiddleware/createSessionExpiryLogoutMiddleware.d.ts +0 -27
- package/dist/modules/apiClient/utils/sessionExpiryLogoutMiddleware/createSessionExpiryLogoutMiddleware.d.ts.map +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
package xyz.dynamic.client.keychain
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.os.Build
|
|
6
|
+
import android.security.keystore.KeyGenParameterSpec
|
|
7
|
+
import android.security.keystore.KeyProperties
|
|
8
|
+
import android.security.keystore.StrongBoxUnavailableException
|
|
9
|
+
import java.security.KeyPairGenerator
|
|
10
|
+
import java.security.KeyStore
|
|
11
|
+
import java.security.Signature
|
|
12
|
+
import java.security.spec.ECGenParameterSpec
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Android KeyStore key manager backed by hardware TEE/StrongBox.
|
|
16
|
+
* Provides P-256 key generation, signing, and management.
|
|
17
|
+
* All public keys are returned in uncompressed SEC1 format (65 bytes: 04 || x || y).
|
|
18
|
+
* All binary data uses base64url encoding (RFC 4648 S5, no padding).
|
|
19
|
+
*/
|
|
20
|
+
class KeyStoreKeyManager {
|
|
21
|
+
|
|
22
|
+
fun isAvailable(context: Context?): Boolean {
|
|
23
|
+
return try {
|
|
24
|
+
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
|
|
25
|
+
keyStore.load(null)
|
|
26
|
+
val hasStrongBox = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && context != null) {
|
|
27
|
+
context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)
|
|
28
|
+
} else {
|
|
29
|
+
false
|
|
30
|
+
}
|
|
31
|
+
// Even without StrongBox, Android KeyStore provides TEE-backed keys on most devices
|
|
32
|
+
hasStrongBox || Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
|
33
|
+
} catch (e: Exception) {
|
|
34
|
+
false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fun hasKey(alias: String): Boolean {
|
|
39
|
+
val keyStore = loadKeyStore()
|
|
40
|
+
return keyStore.containsAlias(alias)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fun generateKeyPair(alias: String): String {
|
|
44
|
+
require(!hasKey(alias)) { "Key already exists for alias: $alias" }
|
|
45
|
+
|
|
46
|
+
val specBuilder = KeyGenParameterSpec.Builder(
|
|
47
|
+
alias,
|
|
48
|
+
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
|
|
49
|
+
)
|
|
50
|
+
.setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1"))
|
|
51
|
+
.setDigests(KeyProperties.DIGEST_SHA256)
|
|
52
|
+
|
|
53
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
54
|
+
specBuilder.setIsStrongBoxBacked(true)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
val keyPair = try {
|
|
58
|
+
val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE)
|
|
59
|
+
kpg.initialize(specBuilder.build())
|
|
60
|
+
kpg.generateKeyPair()
|
|
61
|
+
} catch (e: Exception) {
|
|
62
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && e is StrongBoxUnavailableException) {
|
|
63
|
+
// Fallback: retry without StrongBox on TEE
|
|
64
|
+
specBuilder.setIsStrongBoxBacked(false)
|
|
65
|
+
val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, ANDROID_KEYSTORE)
|
|
66
|
+
kpg.initialize(specBuilder.build())
|
|
67
|
+
kpg.generateKeyPair()
|
|
68
|
+
} else {
|
|
69
|
+
throw e
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
val publicKeyBytes = extractUncompressedPublicKey(keyPair.public.encoded)
|
|
74
|
+
return base64urlEncode(publicKeyBytes)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fun getPublicKey(alias: String): String? {
|
|
78
|
+
val keyStore = loadKeyStore()
|
|
79
|
+
val entry = keyStore.getEntry(alias, null)
|
|
80
|
+
|
|
81
|
+
if (entry == null || entry !is KeyStore.PrivateKeyEntry) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
val publicKeyBytes = extractUncompressedPublicKey(entry.certificate.publicKey.encoded)
|
|
86
|
+
return base64urlEncode(publicKeyBytes)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fun sign(alias: String, payload: ByteArray): String {
|
|
90
|
+
val keyStore = loadKeyStore()
|
|
91
|
+
val entry = keyStore.getEntry(alias, null)
|
|
92
|
+
|
|
93
|
+
require(entry != null && entry is KeyStore.PrivateKeyEntry) { "Key not found: $alias" }
|
|
94
|
+
|
|
95
|
+
val signature = Signature.getInstance("SHA256withECDSA")
|
|
96
|
+
signature.initSign(entry.privateKey)
|
|
97
|
+
signature.update(payload)
|
|
98
|
+
val signatureBytes = signature.sign()
|
|
99
|
+
|
|
100
|
+
return base64urlEncode(signatureBytes)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fun deleteKey(alias: String) {
|
|
104
|
+
val keyStore = loadKeyStore()
|
|
105
|
+
keyStore.deleteEntry(alias)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// region Private helpers
|
|
109
|
+
|
|
110
|
+
private fun loadKeyStore(): KeyStore {
|
|
111
|
+
val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
|
|
112
|
+
keyStore.load(null)
|
|
113
|
+
return keyStore
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Extract uncompressed SEC1 public key (65 bytes: 04 || x || y)
|
|
118
|
+
* from X.509 SubjectPublicKeyInfo DER encoding.
|
|
119
|
+
*/
|
|
120
|
+
private fun extractUncompressedPublicKey(x509Encoded: ByteArray): ByteArray {
|
|
121
|
+
val uncompressedPointLength = 65
|
|
122
|
+
return x509Encoded.copyOfRange(
|
|
123
|
+
x509Encoded.size - uncompressedPointLength,
|
|
124
|
+
x509Encoded.size
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private fun base64urlEncode(data: ByteArray): String {
|
|
129
|
+
return android.util.Base64.encodeToString(
|
|
130
|
+
data,
|
|
131
|
+
android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP or android.util.Base64.NO_PADDING
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
companion object {
|
|
136
|
+
private const val ANDROID_KEYSTORE = "AndroidKeyStore"
|
|
137
|
+
|
|
138
|
+
fun base64urlDecode(input: String): ByteArray {
|
|
139
|
+
return android.util.Base64.decode(
|
|
140
|
+
input,
|
|
141
|
+
android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP or android.util.Base64.NO_PADDING
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// endregion
|
|
147
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package xyz.dynamic.client.keychain
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.bridge.WritableMap
|
|
7
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
8
|
+
import xyz.dynamic.client.NativeKeychainSpec
|
|
9
|
+
|
|
10
|
+
@ReactModule(name = KeychainModule.NAME)
|
|
11
|
+
class KeychainModule(reactContext: ReactApplicationContext) :
|
|
12
|
+
NativeKeychainSpec(reactContext) {
|
|
13
|
+
|
|
14
|
+
private val keyManager = KeyStoreKeyManager()
|
|
15
|
+
|
|
16
|
+
companion object {
|
|
17
|
+
const val NAME = "DynamicClientKeychain"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
override fun getName(): String = NAME
|
|
21
|
+
|
|
22
|
+
override fun isAvailable(promise: Promise) {
|
|
23
|
+
try {
|
|
24
|
+
val context = reactApplicationContext
|
|
25
|
+
promise.resolve(keyManager.isAvailable(context))
|
|
26
|
+
} catch (e: Exception) {
|
|
27
|
+
promise.resolve(false)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
override fun hasKey(key: String, promise: Promise) {
|
|
32
|
+
try {
|
|
33
|
+
promise.resolve(keyManager.hasKey(key))
|
|
34
|
+
} catch (e: Exception) {
|
|
35
|
+
promise.reject("ERR_KEYCHAIN", "Failed to check key: ${e.message}", e)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override fun generateKeyPair(key: String, promise: Promise) {
|
|
40
|
+
try {
|
|
41
|
+
val publicKey = keyManager.generateKeyPair(key)
|
|
42
|
+
val result: WritableMap = Arguments.createMap()
|
|
43
|
+
result.putString("publicKey", publicKey)
|
|
44
|
+
promise.resolve(result)
|
|
45
|
+
} catch (e: Exception) {
|
|
46
|
+
promise.reject("ERR_KEYCHAIN", "Failed to generate key pair: ${e.message}", e)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
override fun getPublicKey(key: String, promise: Promise) {
|
|
51
|
+
try {
|
|
52
|
+
val publicKey = keyManager.getPublicKey(key)
|
|
53
|
+
if (publicKey == null) {
|
|
54
|
+
promise.resolve(null)
|
|
55
|
+
} else {
|
|
56
|
+
val result: WritableMap = Arguments.createMap()
|
|
57
|
+
result.putString("publicKey", publicKey)
|
|
58
|
+
promise.resolve(result)
|
|
59
|
+
}
|
|
60
|
+
} catch (e: Exception) {
|
|
61
|
+
promise.reject("ERR_KEYCHAIN", "Failed to get public key: ${e.message}", e)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun sign(key: String, payload: String, promise: Promise) {
|
|
66
|
+
try {
|
|
67
|
+
val payloadData = KeyStoreKeyManager.base64urlDecode(payload)
|
|
68
|
+
val signature = keyManager.sign(key, payloadData)
|
|
69
|
+
val result: WritableMap = Arguments.createMap()
|
|
70
|
+
result.putString("signature", signature)
|
|
71
|
+
promise.resolve(result)
|
|
72
|
+
} catch (e: Exception) {
|
|
73
|
+
promise.reject("ERR_KEYCHAIN", "Failed to sign: ${e.message}", e)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
override fun deleteKey(key: String, promise: Promise) {
|
|
78
|
+
try {
|
|
79
|
+
keyManager.deleteKey(key)
|
|
80
|
+
promise.resolve(null)
|
|
81
|
+
} catch (e: Exception) {
|
|
82
|
+
promise.reject("ERR_KEYCHAIN", "Failed to delete key: ${e.message}", e)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package xyz.dynamic.client.manifest
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
7
|
+
import xyz.dynamic.client.BuildConfig
|
|
8
|
+
import xyz.dynamic.client.NativeReactNativeManifestSpec
|
|
9
|
+
|
|
10
|
+
@ReactModule(name = ReactNativeManifestModule.NAME)
|
|
11
|
+
class ReactNativeManifestModule(reactContext: ReactApplicationContext) :
|
|
12
|
+
NativeReactNativeManifestSpec(reactContext) {
|
|
13
|
+
|
|
14
|
+
companion object {
|
|
15
|
+
const val NAME = "DynamicClientManifest"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun getName(): String = NAME
|
|
19
|
+
|
|
20
|
+
override fun getManifest(): WritableMap {
|
|
21
|
+
val manifest = Arguments.createMap()
|
|
22
|
+
manifest.putString("sdkVersion", BuildConfig.DYNAMIC_SDK_VERSION)
|
|
23
|
+
return manifest
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
package xyz.dynamic.client.webview
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.webkit.RenderProcessGoneDetail
|
|
6
|
+
import android.webkit.WebResourceError
|
|
7
|
+
import android.webkit.WebResourceRequest
|
|
8
|
+
import android.webkit.WebResourceResponse
|
|
9
|
+
import android.webkit.WebView
|
|
10
|
+
import android.webkit.WebViewClient
|
|
11
|
+
import androidx.annotation.RequiresApi
|
|
12
|
+
import androidx.webkit.WebViewFeature
|
|
13
|
+
|
|
14
|
+
internal class DynamicWebViewClient(
|
|
15
|
+
private val host: DynamicWebViewHost,
|
|
16
|
+
) : WebViewClient() {
|
|
17
|
+
|
|
18
|
+
// Pin navigation to the instance's origin. The WaaS page is a single-origin
|
|
19
|
+
// SPA, so any MAIN-FRAME navigation to a different origin (open redirect,
|
|
20
|
+
// injected link, scripted location change) is blocked — the bridge must never
|
|
21
|
+
// be re-exposed to an attacker origin. Subframe navigations are left alone.
|
|
22
|
+
// Not invoked for the initial loadUrl, which open() already origin-validated.
|
|
23
|
+
override fun shouldOverrideUrlLoading(
|
|
24
|
+
view: WebView?,
|
|
25
|
+
request: WebResourceRequest?,
|
|
26
|
+
): Boolean {
|
|
27
|
+
val req = request ?: return false
|
|
28
|
+
if (!req.isForMainFrame) return false
|
|
29
|
+
val expected = host.expectedOrigin ?: return false
|
|
30
|
+
// Returning true means "handled" → the WebView does not perform the load.
|
|
31
|
+
return originOf(req.url) != expected
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
|
35
|
+
// When DOCUMENT_START_SCRIPT is available the host injects the bridge glue
|
|
36
|
+
// before any page script runs (see DynamicWebViewHost). Only fall back to
|
|
37
|
+
// onPageStarted injection on older WebView. The script is idempotent
|
|
38
|
+
// (`__dynamicBridgeInstalled` guard), so a redundant inject is harmless.
|
|
39
|
+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
|
|
40
|
+
view?.evaluateJavascript(BRIDGE_USER_SCRIPT, null)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun onPageCommitVisible(view: WebView?, url: String?) {
|
|
45
|
+
// Android analog to iOS's `didCommit`: first paint after the response
|
|
46
|
+
// committed. Resolves the pending open promise — deferred host-side so a
|
|
47
|
+
// same-pass HTTP error can claim the promise first.
|
|
48
|
+
host.onCommitVisible()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@RequiresApi(Build.VERSION_CODES.M)
|
|
52
|
+
override fun onReceivedError(
|
|
53
|
+
view: WebView?,
|
|
54
|
+
request: WebResourceRequest?,
|
|
55
|
+
error: WebResourceError?,
|
|
56
|
+
) {
|
|
57
|
+
if (request?.isForMainFrame != true) return
|
|
58
|
+
host.onNavigationFailed(error?.description?.toString())
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@RequiresApi(Build.VERSION_CODES.M)
|
|
62
|
+
override fun onReceivedHttpError(
|
|
63
|
+
view: WebView?,
|
|
64
|
+
request: WebResourceRequest?,
|
|
65
|
+
errorResponse: WebResourceResponse?,
|
|
66
|
+
) {
|
|
67
|
+
if (request?.isForMainFrame != true) return
|
|
68
|
+
val status = errorResponse?.statusCode ?: return
|
|
69
|
+
if (status < 400) return
|
|
70
|
+
host.onHttpError(status)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
74
|
+
override fun onRenderProcessGone(
|
|
75
|
+
view: WebView?,
|
|
76
|
+
detail: RenderProcessGoneDetail?,
|
|
77
|
+
): Boolean {
|
|
78
|
+
host.onRenderProcessGone()
|
|
79
|
+
// Returning true tells the framework we've handled the crash. Returning
|
|
80
|
+
// false would crash the host process.
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
}
|