@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.
Files changed (135) hide show
  1. package/android/build.gradle +231 -0
  2. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  3. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  4. package/android/gradle.properties +8 -0
  5. package/android/gradlew +251 -0
  6. package/android/gradlew.bat +94 -0
  7. package/android/settings.gradle +30 -0
  8. package/android/src/main/java/xyz/dynamic/client/DynamicClientPackage.kt +52 -0
  9. package/android/src/main/java/xyz/dynamic/client/keychain/KeyStoreKeyManager.kt +147 -0
  10. package/android/src/main/java/xyz/dynamic/client/keychain/KeychainModule.kt +85 -0
  11. package/android/src/main/java/xyz/dynamic/client/manifest/ReactNativeManifestModule.kt +25 -0
  12. package/android/src/main/java/xyz/dynamic/client/webview/DynamicWebViewClient.kt +83 -0
  13. package/android/src/main/java/xyz/dynamic/client/webview/DynamicWebViewHost.kt +518 -0
  14. package/android/src/main/java/xyz/dynamic/client/webview/DynamicWebViewModule.kt +147 -0
  15. package/android/src/test/java/xyz/dynamic/client/keychain/KeyStoreKeyManagerTest.kt +288 -0
  16. package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewClientTest.kt +196 -0
  17. package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewHostOpenTest.kt +347 -0
  18. package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewHostTest.kt +171 -0
  19. package/android/src/test/java/xyz/dynamic/client/webview/DynamicWebViewModuleTest.kt +191 -0
  20. package/dist/{InvalidParamError-BIptAjbC.native.esm.js → InvalidParamError-Bc6OZBog.native.esm.js} +91 -829
  21. package/dist/InvalidParamError-Bc6OZBog.native.esm.js.map +1 -0
  22. package/dist/{InvalidParamError-BZ0LHaml.esm.js → InvalidParamError-IHYLdzFd.esm.js} +108 -381
  23. package/dist/InvalidParamError-IHYLdzFd.esm.js.map +1 -0
  24. package/dist/{InvalidParamError-DyXEKMWn.cjs → InvalidParamError-S56rFFDP.cjs} +108 -450
  25. package/dist/InvalidParamError-S56rFFDP.cjs.map +1 -0
  26. package/dist/{NotWaasWalletAccountError-nT4jrLbp.esm.js → NotWaasWalletAccountError-BP-bODbJ.native.esm.js} +4 -4
  27. package/dist/NotWaasWalletAccountError-BP-bODbJ.native.esm.js.map +1 -0
  28. package/dist/{NotWaasWalletAccountError-BbOA5rPd.native.esm.js → NotWaasWalletAccountError-BYEL2l-W.esm.js} +4 -4
  29. package/dist/NotWaasWalletAccountError-BYEL2l-W.esm.js.map +1 -0
  30. package/dist/{NotWaasWalletAccountError-CElqwdQO.cjs → NotWaasWalletAccountError-CbhIKUDM.cjs} +4 -4
  31. package/dist/NotWaasWalletAccountError-CbhIKUDM.cjs.map +1 -0
  32. package/dist/core.cjs +11 -11
  33. package/dist/core.cjs.map +1 -1
  34. package/dist/core.esm.js +5 -5
  35. package/dist/core.native.esm.js +7 -7
  36. package/dist/core.native.esm.js.map +1 -1
  37. package/dist/errors/WebViewLoadFailedError.d.ts +1 -1
  38. package/dist/errors/WebViewLoadFailedError.d.ts.map +1 -1
  39. package/dist/exports/index.d.ts +2 -0
  40. package/dist/exports/index.d.ts.map +1 -1
  41. package/dist/{getNetworkProviderFromNetworkId-DbI5MZhq.cjs → getNetworkProviderFromNetworkId-BDdcpGHH.cjs} +56 -55
  42. package/dist/{getNetworkProviderFromNetworkId-DbI5MZhq.cjs.map → getNetworkProviderFromNetworkId-BDdcpGHH.cjs.map} +1 -1
  43. package/dist/{getNetworkProviderFromNetworkId-BkAPIdRC.esm.js → getNetworkProviderFromNetworkId-D8rWYXlT.esm.js} +54 -54
  44. package/dist/{getNetworkProviderFromNetworkId-BkAPIdRC.esm.js.map → getNetworkProviderFromNetworkId-D8rWYXlT.esm.js.map} +1 -1
  45. package/dist/{getNetworkProviderFromNetworkId-B7DIqT2T.native.esm.js → getNetworkProviderFromNetworkId-DVmF7pH2.native.esm.js} +312 -41
  46. package/dist/getNetworkProviderFromNetworkId-DVmF7pH2.native.esm.js.map +1 -0
  47. package/dist/{getSignedSessionId-BF6n8gve.cjs → getSignedSessionId-BBaBCi9S.cjs} +3 -3
  48. package/dist/{getSignedSessionId-BF6n8gve.cjs.map → getSignedSessionId-BBaBCi9S.cjs.map} +1 -1
  49. package/dist/{getSignedSessionId-y3YtxFHZ.esm.js → getSignedSessionId-CND07Eca.esm.js} +3 -3
  50. package/dist/{getSignedSessionId-y3YtxFHZ.esm.js.map → getSignedSessionId-CND07Eca.esm.js.map} +1 -1
  51. package/dist/{getSignedSessionId-1TV-x__C.native.esm.js → getSignedSessionId-RwV7JPn8.native.esm.js} +3 -3
  52. package/dist/{getSignedSessionId-1TV-x__C.native.esm.js.map → getSignedSessionId-RwV7JPn8.native.esm.js.map} +1 -1
  53. package/dist/getVerifiedCredentialForWalletAccount-BVrGV8qf.native.esm.js +701 -0
  54. package/dist/getVerifiedCredentialForWalletAccount-BVrGV8qf.native.esm.js.map +1 -0
  55. package/dist/{getVerifiedCredentialForWalletAccount-etV50QRH.esm.js → getVerifiedCredentialForWalletAccount-BiOS65Tk.esm.js} +2 -2
  56. package/dist/{getVerifiedCredentialForWalletAccount-etV50QRH.esm.js.map → getVerifiedCredentialForWalletAccount-BiOS65Tk.esm.js.map} +1 -1
  57. package/dist/{getVerifiedCredentialForWalletAccount-BfJ29erz.cjs → getVerifiedCredentialForWalletAccount-FrxYWSuD.cjs} +3 -2
  58. package/dist/{getVerifiedCredentialForWalletAccount-BfJ29erz.cjs.map → getVerifiedCredentialForWalletAccount-FrxYWSuD.cjs.map} +1 -1
  59. package/dist/index.cjs +35 -35
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.esm.js +14 -14
  62. package/dist/index.esm.js.map +1 -1
  63. package/dist/index.native.esm.js +14 -14
  64. package/dist/index.native.esm.js.map +1 -1
  65. package/dist/isMfaRequiredForAction-BPL_dgIM.esm.js +318 -0
  66. package/dist/isMfaRequiredForAction-BPL_dgIM.esm.js.map +1 -0
  67. package/dist/isMfaRequiredForAction-BolTrtCO.cjs +405 -0
  68. package/dist/isMfaRequiredForAction-BolTrtCO.cjs.map +1 -0
  69. package/dist/{isMfaRequiredForAction-B2vKX31S.native.esm.js → isMfaRequiredForAction-DQDoPsi6.native.esm.js} +2 -2
  70. package/dist/{isMfaRequiredForAction-B2vKX31S.native.esm.js.map → isMfaRequiredForAction-DQDoPsi6.native.esm.js.map} +1 -1
  71. package/dist/modules/apiClient/apiClient.types.d.ts +0 -12
  72. package/dist/modules/apiClient/apiClient.types.d.ts.map +1 -1
  73. package/dist/modules/apiClient/createApiClient.d.ts.map +1 -1
  74. package/dist/modules/auth/logoutReason.d.ts +1 -1
  75. package/dist/modules/auth/logoutReason.d.ts.map +1 -1
  76. package/dist/modules/auth/logoutWithReason/logoutWithReason.d.ts.map +1 -1
  77. package/dist/modules/deviceRegistration/getRegisteredDevices/getRegisteredDevices.d.ts.map +1 -1
  78. package/dist/modules/user/refreshAuth/refreshAuth.d.ts.map +1 -1
  79. package/dist/modules/waas/backupWaasKeySharesToGoogleDrive/backupWaasKeySharesToGoogleDrive.d.ts +12 -1
  80. package/dist/modules/waas/backupWaasKeySharesToGoogleDrive/backupWaasKeySharesToGoogleDrive.d.ts.map +1 -1
  81. package/dist/modules/waas/createWaasClient/createWaasClient.d.ts.map +1 -1
  82. package/dist/modules/waas/createWaasClient/translateNativeOpenError.d.ts.map +1 -1
  83. package/dist/modules/waas/createWaasProvider/createWaasProvider.d.ts.map +1 -1
  84. package/dist/modules/waas/waas.types.d.ts +1 -0
  85. package/dist/modules/waas/waas.types.d.ts.map +1 -1
  86. package/dist/modules/wallets/walletProvider/events/onWalletProviderEvent/onWalletProviderEvent.d.ts +1 -2
  87. package/dist/modules/wallets/walletProvider/events/onWalletProviderEvent/onWalletProviderEvent.d.ts.map +1 -1
  88. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  89. package/dist/waas.cjs +7 -4
  90. package/dist/waas.cjs.map +1 -1
  91. package/dist/waas.esm.js +6 -4
  92. package/dist/waas.esm.js.map +1 -1
  93. package/dist/waas.native.esm.js +6 -4
  94. package/dist/waas.native.esm.js.map +1 -1
  95. package/dist/waasCore.cjs +10 -5
  96. package/dist/waasCore.cjs.map +1 -1
  97. package/dist/waasCore.esm.js +10 -5
  98. package/dist/waasCore.esm.js.map +1 -1
  99. package/dist/waasCore.native.esm.js +10 -8
  100. package/dist/waasCore.native.esm.js.map +1 -1
  101. package/dynamic-labs-sdk-client.podspec +27 -0
  102. package/ios/DynamicClientKeychain.h +4 -0
  103. package/ios/DynamicClientKeychain.mm +101 -0
  104. package/ios/DynamicClientManifest.h +4 -0
  105. package/ios/DynamicClientManifest.mm +45 -0
  106. package/ios/DynamicClientWebView.h +5 -0
  107. package/ios/DynamicClientWebView.mm +143 -0
  108. package/ios/DynamicWebViewImpl.swift +603 -0
  109. package/ios/LiveSecKeyAPI.swift +86 -0
  110. package/ios/ReactNativeManifestImpl.swift +20 -0
  111. package/ios/SecKeyAPI.swift +57 -0
  112. package/ios/SecureEnclaveKeyManager.swift +191 -0
  113. package/package.json +16 -5
  114. package/react-native.config.cjs +14 -0
  115. package/src/turboModules/NativeDynamicWebView.native.spec.ts +61 -0
  116. package/src/turboModules/NativeDynamicWebView.ts +40 -0
  117. package/src/turboModules/NativeKeychain.native.spec.ts +47 -0
  118. package/src/turboModules/NativeKeychain.ts +19 -0
  119. package/src/turboModules/NativeReactNativeManifest.native.spec.ts +55 -0
  120. package/src/turboModules/NativeReactNativeManifest.ts +28 -0
  121. package/dist/InvalidParamError-BIptAjbC.native.esm.js.map +0 -1
  122. package/dist/InvalidParamError-BZ0LHaml.esm.js.map +0 -1
  123. package/dist/InvalidParamError-DyXEKMWn.cjs.map +0 -1
  124. package/dist/NotWaasWalletAccountError-BbOA5rPd.native.esm.js.map +0 -1
  125. package/dist/NotWaasWalletAccountError-CElqwdQO.cjs.map +0 -1
  126. package/dist/NotWaasWalletAccountError-nT4jrLbp.esm.js.map +0 -1
  127. package/dist/getNetworkProviderFromNetworkId-B7DIqT2T.native.esm.js.map +0 -1
  128. package/dist/getVerifiedCredentialForWalletAccount-CLyC2nOl.native.esm.js +0 -269
  129. package/dist/getVerifiedCredentialForWalletAccount-CLyC2nOl.native.esm.js.map +0 -1
  130. package/dist/isMfaRequiredForAction-Dy4-S3mr.esm.js +0 -79
  131. package/dist/isMfaRequiredForAction-Dy4-S3mr.esm.js.map +0 -1
  132. package/dist/isMfaRequiredForAction-K2Jh-3-j.cjs +0 -96
  133. package/dist/isMfaRequiredForAction-K2Jh-3-j.cjs.map +0 -1
  134. package/dist/modules/apiClient/utils/sessionExpiryLogoutMiddleware/createSessionExpiryLogoutMiddleware.d.ts +0 -27
  135. package/dist/modules/apiClient/utils/sessionExpiryLogoutMiddleware/createSessionExpiryLogoutMiddleware.d.ts.map +0 -1
@@ -0,0 +1,20 @@
1
+ import Foundation
2
+
3
+ /// Swift implementation of the ReactNativeManifest module.
4
+ /// Returns the SDK manifest (version + any future metadata) baked in at
5
+ /// build time. The SDK version is passed through from the ObjC++ bridge
6
+ /// via the DYNAMIC_SDK_VERSION preprocessor macro.
7
+ @objc public class ReactNativeManifestImpl: NSObject {
8
+
9
+ private let sdkVersion: String
10
+
11
+ @objc public init(sdkVersion: String) {
12
+ self.sdkVersion = sdkVersion
13
+ }
14
+
15
+ @objc public func getManifest() -> [String: Any] {
16
+ return [
17
+ "sdkVersion": sdkVersion
18
+ ]
19
+ }
20
+ }
@@ -0,0 +1,57 @@
1
+ import Foundation
2
+ import Security
3
+
4
+ /// Abstracts the Security/CryptoKit framework C calls that
5
+ /// `SecureEnclaveKeyManager` depends on, so they can be substituted
6
+ /// in unit tests.
7
+ ///
8
+ /// Why this exists: the underlying calls (`SecKeyCreateRandomKey`,
9
+ /// `SecKeyCreateSignature`, etc.) require a real Secure Enclave and
10
+ /// therefore cannot run on the iOS Simulator where unit tests execute.
11
+ /// Tests inject a fake; production uses `LiveSecKeyAPI`.
12
+ ///
13
+ /// IMPORTANT: covering this seam with mocks gives line coverage, not
14
+ /// security confidence. A manual on-device smoke test of
15
+ /// `generateKeyPair` and `sign` remains required before any release.
16
+ protocol SecKeyAPI {
17
+ /// Returns true if the device has a Secure Enclave (real hardware
18
+ /// only; always false on Simulator).
19
+ func isSecureEnclaveAvailable() -> Bool
20
+
21
+ /// Wraps `SecItemCopyMatching`. Returns the status code and, on
22
+ /// success, the matching keychain item reference.
23
+ func itemCopyMatching(query: CFDictionary) -> (status: OSStatus, item: CFTypeRef?)
24
+
25
+ /// Wraps `SecItemDelete`. The original call site discards the
26
+ /// result, so this signature mirrors that.
27
+ func itemDelete(query: CFDictionary) -> OSStatus
28
+
29
+ /// Wraps `SecAccessControlCreateWithFlags`. Returns nil only on
30
+ /// invalid flag combinations, which never happen with the inputs
31
+ /// this codebase uses — the caller force-unwraps.
32
+ func createAccessControl(
33
+ protection: CFTypeRef,
34
+ flags: SecAccessControlCreateFlags
35
+ ) -> SecAccessControl?
36
+
37
+ /// Wraps `SecKeyCreateRandomKey`. Throws an `NSError` whose
38
+ /// `localizedDescription` is the underlying `CFError`'s string
39
+ /// representation; the caller re-wraps with its own domain/code.
40
+ func createRandomKey(attributes: CFDictionary) throws -> SecKey
41
+
42
+ /// Wraps `SecKeyCopyPublicKey`. Returns nil if the platform cannot
43
+ /// derive the public key from the given private key.
44
+ func copyPublicKey(_ key: SecKey) -> SecKey?
45
+
46
+ /// Wraps `SecKeyCreateSignature`. Returns the raw signature bytes
47
+ /// or throws if the underlying call fails.
48
+ func createSignature(
49
+ key: SecKey,
50
+ algorithm: SecKeyAlgorithm,
51
+ data: CFData
52
+ ) throws -> Data
53
+
54
+ /// Wraps `SecKeyCopyExternalRepresentation`. For Secure Enclave
55
+ /// P-256 keys this returns uncompressed SEC1 (04 || x || y).
56
+ func copyExternalRepresentation(_ key: SecKey) throws -> Data
57
+ }
@@ -0,0 +1,191 @@
1
+ import Foundation
2
+ import Security
3
+ import CryptoKit
4
+
5
+ /// Secure Enclave key manager providing P-256 key generation, signing, and management.
6
+ /// All public keys are returned in uncompressed SEC1 format (65 bytes: 04 || x || y).
7
+ /// All binary data uses base64url encoding (RFC 4648 §5, no padding).
8
+ @objc public class SecureEnclaveKeyManager: NSObject {
9
+
10
+ private let api: SecKeyAPI
11
+
12
+ // Designated init used by the ObjC++ bridge — wires in the real
13
+ // Security/CryptoKit calls via LiveSecKeyAPI.
14
+ @objc public override init() {
15
+ self.api = LiveSecKeyAPI()
16
+ super.init()
17
+ }
18
+
19
+ // Designated init used by unit tests to inject a fake SecKeyAPI.
20
+ // Not @objc-exposed because the protocol is Swift-only.
21
+ init(api: SecKeyAPI) {
22
+ self.api = api
23
+ super.init()
24
+ }
25
+
26
+ @objc public func isAvailable() -> Bool {
27
+ return api.isSecureEnclaveAvailable()
28
+ }
29
+
30
+ @objc public func hasKey(_ tag: String) -> Bool {
31
+ let query = baseQuery(for: tag, returning: true)
32
+ let result = api.itemCopyMatching(query: query as CFDictionary)
33
+ return result.status == errSecSuccess
34
+ }
35
+
36
+ @objc public func generateKeyPair(_ tag: String) throws -> String {
37
+ let tagData = tag.data(using: .utf8)!
38
+
39
+ let access = api.createAccessControl(
40
+ protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
41
+ flags: .privateKeyUsage
42
+ )!
43
+
44
+ let attributes: [String: Any] = [
45
+ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
46
+ kSecAttrKeySizeInBits as String: 256,
47
+ kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
48
+ kSecPrivateKeyAttrs as String: [
49
+ kSecAttrIsPermanent as String: true,
50
+ kSecAttrApplicationTag as String: tagData,
51
+ kSecAttrAccessControl as String: access,
52
+ ] as [String: Any],
53
+ ]
54
+
55
+ let privateKey: SecKey
56
+ do {
57
+ privateKey = try api.createRandomKey(attributes: attributes as CFDictionary)
58
+ } catch {
59
+ throw NSError(
60
+ domain: "SecureEnclaveKeyManager",
61
+ code: 1,
62
+ userInfo: [NSLocalizedDescriptionKey: (error as NSError).localizedDescription]
63
+ )
64
+ }
65
+
66
+ guard let publicKey = api.copyPublicKey(privateKey) else {
67
+ throw NSError(
68
+ domain: "SecureEnclaveKeyManager",
69
+ code: 2,
70
+ userInfo: [NSLocalizedDescriptionKey: "Failed to extract public key"]
71
+ )
72
+ }
73
+
74
+ let publicKeyData = try exportPublicKey(publicKey)
75
+ return base64urlEncode(publicKeyData)
76
+ }
77
+
78
+ /// Returns the base64url-encoded public key, or an empty string if the key does not exist.
79
+ /// The ObjC++ bridge maps empty string to null.
80
+ @objc public func getPublicKey(_ tag: String) throws -> String {
81
+ let query = baseQuery(for: tag, returning: true)
82
+ let result = api.itemCopyMatching(query: query as CFDictionary)
83
+
84
+ guard result.status == errSecSuccess, let ref = result.item else {
85
+ // Key not found — return empty string so the bridge can resolve null
86
+ return ""
87
+ }
88
+
89
+ let privateKey = ref as! SecKey
90
+ guard let publicKey = api.copyPublicKey(privateKey) else {
91
+ throw NSError(
92
+ domain: "SecureEnclaveKeyManager",
93
+ code: 2,
94
+ userInfo: [NSLocalizedDescriptionKey: "Failed to extract public key"]
95
+ )
96
+ }
97
+
98
+ let publicKeyData = try exportPublicKey(publicKey)
99
+ return base64urlEncode(publicKeyData)
100
+ }
101
+
102
+ @objc public func sign(_ tag: String, payload: String) throws -> String {
103
+ let query = baseQuery(for: tag, returning: true)
104
+ let result = api.itemCopyMatching(query: query as CFDictionary)
105
+
106
+ guard result.status == errSecSuccess, let ref = result.item else {
107
+ throw NSError(
108
+ domain: "SecureEnclaveKeyManager",
109
+ code: 3,
110
+ userInfo: [NSLocalizedDescriptionKey: "Key not found for tag: \(tag)"]
111
+ )
112
+ }
113
+
114
+ let privateKey = ref as! SecKey
115
+
116
+ guard let payloadData = base64urlDecode(payload) else {
117
+ throw NSError(
118
+ domain: "SecureEnclaveKeyManager",
119
+ code: 4,
120
+ userInfo: [NSLocalizedDescriptionKey: "Invalid base64url payload"]
121
+ )
122
+ }
123
+
124
+ let signature: Data
125
+ do {
126
+ signature = try api.createSignature(
127
+ key: privateKey,
128
+ algorithm: .ecdsaSignatureMessageX962SHA256,
129
+ data: payloadData as CFData
130
+ )
131
+ } catch {
132
+ throw NSError(
133
+ domain: "SecureEnclaveKeyManager",
134
+ code: 5,
135
+ userInfo: [NSLocalizedDescriptionKey: (error as NSError).localizedDescription]
136
+ )
137
+ }
138
+
139
+ return base64urlEncode(signature)
140
+ }
141
+
142
+ @objc public func deleteKey(_ tag: String) {
143
+ let query = baseQuery(for: tag, returning: false)
144
+ _ = api.itemDelete(query: query as CFDictionary)
145
+ }
146
+
147
+ // MARK: - Private helpers
148
+
149
+ private func baseQuery(for tag: String, returning returnRef: Bool) -> [String: Any] {
150
+ var query: [String: Any] = [
151
+ kSecClass as String: kSecClassKey,
152
+ kSecAttrApplicationTag as String: tag.data(using: .utf8)!,
153
+ kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
154
+ ]
155
+ if returnRef {
156
+ query[kSecReturnRef as String] = true
157
+ }
158
+ return query
159
+ }
160
+
161
+ private func exportPublicKey(_ publicKey: SecKey) throws -> Data {
162
+ do {
163
+ // SecKeyCopyExternalRepresentation returns uncompressed SEC1 (04 || x || y)
164
+ return try api.copyExternalRepresentation(publicKey)
165
+ } catch {
166
+ throw NSError(
167
+ domain: "SecureEnclaveKeyManager",
168
+ code: 2,
169
+ userInfo: [NSLocalizedDescriptionKey: (error as NSError).localizedDescription]
170
+ )
171
+ }
172
+ }
173
+
174
+ private func base64urlEncode(_ data: Data) -> String {
175
+ data.base64EncodedString()
176
+ .replacingOccurrences(of: "+", with: "-")
177
+ .replacingOccurrences(of: "/", with: "_")
178
+ .replacingOccurrences(of: "=", with: "")
179
+ }
180
+
181
+ private func base64urlDecode(_ string: String) -> Data? {
182
+ var base64 = string
183
+ .replacingOccurrences(of: "-", with: "+")
184
+ .replacingOccurrences(of: "_", with: "/")
185
+ let remainder = base64.count % 4
186
+ if remainder > 0 {
187
+ base64 += String(repeating: "=", count: 4 - remainder)
188
+ }
189
+ return Data(base64Encoded: base64)
190
+ }
191
+ }
package/package.json CHANGED
@@ -1,38 +1,47 @@
1
1
  {
2
2
  "name": "@dynamic-labs-sdk/client",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.esm.js",
7
7
  "types": "./dist/exports/index.d.ts",
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "ios",
11
+ "android",
12
+ "dynamic-labs-sdk-client.podspec",
13
+ "react-native.config.cjs",
14
+ "src/turboModules"
10
15
  ],
11
16
  "exports": {
12
17
  "./package.json": "./package.json",
13
18
  ".": {
19
+ "react-native": "./dist/index.native.esm.js",
14
20
  "types": "./dist/exports/index.d.ts",
15
21
  "import": "./dist/index.esm.js",
16
22
  "default": "./dist/index.cjs"
17
23
  },
18
24
  "./core": {
25
+ "react-native": "./dist/core.native.esm.js",
19
26
  "types": "./dist/exports/core.d.ts",
20
27
  "import": "./dist/core.esm.js",
21
28
  "default": "./dist/core.cjs"
22
29
  },
23
30
  "./waas": {
31
+ "react-native": "./dist/waas.native.esm.js",
24
32
  "types": "./dist/exports/waas.d.ts",
25
33
  "import": "./dist/waas.esm.js",
26
34
  "default": "./dist/waas.cjs"
27
35
  },
28
36
  "./waas/core": {
37
+ "react-native": "./dist/waasCore.native.esm.js",
29
38
  "types": "./dist/exports/waasCore.d.ts",
30
39
  "import": "./dist/waasCore.esm.js",
31
40
  "default": "./dist/waasCore.cjs"
32
41
  }
33
42
  },
34
43
  "dependencies": {
35
- "@dynamic-labs-wallet/browser-wallet-client": "1.0.7",
44
+ "@dynamic-labs-wallet/browser-wallet-client": "1.0.16",
36
45
  "@dynamic-labs-wallet/forward-mpc-client": "0.10.1",
37
46
  "@dynamic-labs/sdk-api-core": "0.0.1015",
38
47
  "@simplewebauthn/browser": "13.1.0",
@@ -40,7 +49,7 @@
40
49
  "buffer": "6.0.3",
41
50
  "eventemitter3": "5.0.1",
42
51
  "zod": "4.0.5",
43
- "@dynamic-labs-sdk/assert-package-version": "1.6.0"
52
+ "@dynamic-labs-sdk/assert-package-version": "1.8.0"
44
53
  },
45
54
  "devDependencies": {
46
55
  "@inrupt/jest-jsdom-polyfills": "4.0.5",
@@ -84,11 +93,13 @@
84
93
  },
85
94
  "scripts": {
86
95
  "build": "rm -rf dist && tsdown && pnpm exec tsc --emitDeclarationOnly -p ./tsconfig.lib.json && pnpm exec tsc --noEmit -p ./tsconfig.lib.native.json",
87
- "lint": "eslint .",
96
+ "lint": "eslint . && pnpm --filter @tools/check-native-module-names run check .",
88
97
  "lint:fix": "eslint . --fix",
89
98
  "test": "vitest run",
90
99
  "test:watch": "vitest",
91
100
  "test-with-coverage": "vitest run --coverage",
101
+ "test:xcode": "bash scripts/run-xcode-tests.sh",
102
+ "test:android": "bash scripts/run-android-tests.sh",
92
103
  "check-versions": "pnpm --filter @tools/sync-package-versions run sync --check-only --package-filter client"
93
104
  }
94
105
  }
@@ -0,0 +1,14 @@
1
+ module.exports = {
2
+ dependency: {
3
+ platforms: {
4
+ ios: {
5
+ podspecPath: `${__dirname}/dynamic-labs-sdk-client.podspec`,
6
+ },
7
+ android: {
8
+ sourceDir: `${__dirname}/android`,
9
+ packageImportPath: 'import xyz.dynamic.client.DynamicClientPackage;',
10
+ packageInstance: 'new DynamicClientPackage()',
11
+ },
12
+ },
13
+ },
14
+ };
@@ -0,0 +1,61 @@
1
+ /// <reference types="vitest/globals" />
2
+
3
+ const turboModuleGetMock = vi.fn();
4
+
5
+ vi.mock('react-native', () => ({
6
+ TurboModuleRegistry: {
7
+ get get() {
8
+ return turboModuleGetMock;
9
+ },
10
+ },
11
+ }));
12
+
13
+ describe('NativeDynamicWebView', () => {
14
+ beforeEach(() => {
15
+ vi.resetModules();
16
+ });
17
+
18
+ describe('when the TurboModuleRegistry resolves the native module', () => {
19
+ const resolvedModule = {
20
+ addListener: vi.fn(),
21
+ close: vi.fn(),
22
+ open: vi.fn(),
23
+ removeListeners: vi.fn(),
24
+ sendMessage: vi.fn(),
25
+ };
26
+
27
+ beforeEach(() => {
28
+ turboModuleGetMock.mockReturnValue(resolvedModule);
29
+ });
30
+
31
+ it('should expose the resolved module as the default export', async () => {
32
+ const imported = await import('./NativeDynamicWebView');
33
+
34
+ expect(imported.default).toBe(resolvedModule);
35
+ });
36
+
37
+ it("should look up the native module under the 'DynamicClientWebView' key that matches the iOS and Android registration", async () => {
38
+ await import('./NativeDynamicWebView');
39
+
40
+ expect(turboModuleGetMock).toHaveBeenCalledWith('DynamicClientWebView');
41
+ });
42
+
43
+ it('should resolve the native module exactly once at import time', async () => {
44
+ await import('./NativeDynamicWebView');
45
+
46
+ expect(turboModuleGetMock).toHaveBeenCalledOnce();
47
+ });
48
+ });
49
+
50
+ describe('when the TurboModuleRegistry cannot resolve the native module', () => {
51
+ beforeEach(() => {
52
+ turboModuleGetMock.mockReturnValue(null);
53
+ });
54
+
55
+ it('should expose null as the default export so consumers can surface a linking error', async () => {
56
+ const imported = await import('./NativeDynamicWebView');
57
+
58
+ expect(imported.default).toBeNull();
59
+ });
60
+ });
61
+ });
@@ -0,0 +1,40 @@
1
+ // This file is a TurboModule spec consumed by React Native codegen.
2
+ // The `interface`, `default export`, and the `react-native` import are hard
3
+ // requirements of the codegen parser and intentionally deviate from the
4
+ // project's conventions. Codegen discovers specs by the `Native*` filename
5
+ // prefix, so this file cannot be renamed to `.native.ts`.
6
+
7
+ import { type TurboModule, TurboModuleRegistry } from 'react-native';
8
+ import type { UnsafeObject } from 'react-native/Libraries/Types/CodegenTypes';
9
+
10
+ export interface Spec extends TurboModule {
11
+ addListener(eventName: string): void;
12
+ close(instanceId: string): void;
13
+ /**
14
+ * Creates a WKWebView for the given instanceId and begins loading `url`.
15
+ *
16
+ * Resolves on `didCommit` — i.e. the first byte of a 2xx/3xx HTTP
17
+ * response has been received and content parsing has started. Page-ready
18
+ * (e.g. the hosted iframe's `iframe-ready-*` handshake) is a separate
19
+ * concern and must be observed by the caller via the message bus; this
20
+ * Promise does not wait for it.
21
+ *
22
+ * Rejects with a `code` of `webview_<reason>` where `<reason>` is one of:
23
+ * - `invalid_url` — URL string was not parseable
24
+ * - `no_host_view` — no foreground window to attach the WebView
25
+ * - `http_error` — response status was >= 400 (userInfo contains `DynamicWebViewStatusCode`)
26
+ * - `navigation_failed` — DNS, TLS, connection failure, or post-commit abort
27
+ * - `cancelled` — `close(instanceId)` was called while this open was pending
28
+ * - `instance_busy` — `open` was called for an instanceId that already has an active WebView
29
+ * - `content_process_terminated` — the WKWebView WebContent process died during load
30
+ * - `unsupported` — the device's Android System WebView is too old to support the secure bridge (Android only)
31
+ *
32
+ * rejection codes into typed `WebViewLoadFailedError` instances.
33
+ */
34
+ open(instanceId: string, url: string): Promise<void>;
35
+ removeListeners(count: number): void;
36
+ sendMessage(instanceId: string, data: UnsafeObject): void;
37
+ }
38
+
39
+ // eslint-disable-next-line import/no-default-export
40
+ export default TurboModuleRegistry.get<Spec>('DynamicClientWebView');
@@ -0,0 +1,47 @@
1
+ /// <reference types="vitest/globals" />
2
+
3
+ const turboModuleGetEnforcingMock = vi.fn();
4
+
5
+ vi.mock('react-native', () => ({
6
+ TurboModuleRegistry: {
7
+ get getEnforcing() {
8
+ return turboModuleGetEnforcingMock;
9
+ },
10
+ },
11
+ }));
12
+
13
+ describe('NativeKeychain', () => {
14
+ const resolvedModule = {
15
+ deleteKey: vi.fn(),
16
+ generateKeyPair: vi.fn(),
17
+ getPublicKey: vi.fn(),
18
+ hasKey: vi.fn(),
19
+ isAvailable: vi.fn(),
20
+ sign: vi.fn(),
21
+ };
22
+
23
+ beforeEach(() => {
24
+ vi.resetModules();
25
+ turboModuleGetEnforcingMock.mockReturnValue(resolvedModule);
26
+ });
27
+
28
+ it('should expose the resolved module as the default export', async () => {
29
+ const imported = await import('./NativeKeychain');
30
+
31
+ expect(imported.default).toBe(resolvedModule);
32
+ });
33
+
34
+ it("should look up the native module under the 'DynamicClientKeychain' key that matches the iOS and Android registration", async () => {
35
+ await import('./NativeKeychain');
36
+
37
+ expect(turboModuleGetEnforcingMock).toHaveBeenCalledWith(
38
+ 'DynamicClientKeychain'
39
+ );
40
+ });
41
+
42
+ it('should resolve the native module exactly once at import time', async () => {
43
+ await import('./NativeKeychain');
44
+
45
+ expect(turboModuleGetEnforcingMock).toHaveBeenCalledOnce();
46
+ });
47
+ });
@@ -0,0 +1,19 @@
1
+ // This file is a TurboModule spec consumed by React Native codegen.
2
+ // The `interface`, `default export`, and `react-native` import are hard
3
+ // requirements of the codegen parser and intentionally deviate from the
4
+ // project's conventions. Codegen discovers specs by the `Native*` filename
5
+ // prefix, so this file cannot be renamed to `.native.ts`.
6
+
7
+ import { type TurboModule, TurboModuleRegistry } from 'react-native';
8
+
9
+ export interface Spec extends TurboModule {
10
+ deleteKey(key: string): Promise<void>;
11
+ generateKeyPair(key: string): Promise<{ publicKey: string }>;
12
+ getPublicKey(key: string): Promise<{ publicKey: string } | null>;
13
+ hasKey(key: string): Promise<boolean>;
14
+ isAvailable(): Promise<boolean>;
15
+ sign(key: string, payload: string): Promise<{ signature: string }>;
16
+ }
17
+
18
+ // eslint-disable-next-line import/no-default-export
19
+ export default TurboModuleRegistry.getEnforcing<Spec>('DynamicClientKeychain');
@@ -0,0 +1,55 @@
1
+ /// <reference types="vitest/globals" />
2
+
3
+ const turboModuleGetMock = vi.fn();
4
+
5
+ vi.mock('react-native', () => ({
6
+ TurboModuleRegistry: {
7
+ get get() {
8
+ return turboModuleGetMock;
9
+ },
10
+ },
11
+ }));
12
+
13
+ describe('NativeReactNativeManifest', () => {
14
+ beforeEach(() => {
15
+ vi.resetModules();
16
+ });
17
+
18
+ describe('when the TurboModuleRegistry resolves the native module', () => {
19
+ const resolvedModule = { getManifest: vi.fn() };
20
+
21
+ beforeEach(() => {
22
+ turboModuleGetMock.mockReturnValue(resolvedModule);
23
+ });
24
+
25
+ it('should expose the resolved module as the default export', async () => {
26
+ const imported = await import('./NativeReactNativeManifest');
27
+
28
+ expect(imported.default).toBe(resolvedModule);
29
+ });
30
+
31
+ it("should look up the native module under the 'DynamicClientManifest' key that matches the iOS and Android registration", async () => {
32
+ await import('./NativeReactNativeManifest');
33
+
34
+ expect(turboModuleGetMock).toHaveBeenCalledWith('DynamicClientManifest');
35
+ });
36
+
37
+ it('should resolve the native module exactly once at import time', async () => {
38
+ await import('./NativeReactNativeManifest');
39
+
40
+ expect(turboModuleGetMock).toHaveBeenCalledOnce();
41
+ });
42
+ });
43
+
44
+ describe('when the TurboModuleRegistry cannot resolve the native module', () => {
45
+ beforeEach(() => {
46
+ turboModuleGetMock.mockReturnValue(null);
47
+ });
48
+
49
+ it('should expose null as the default export so consumers can surface a linking error', async () => {
50
+ const imported = await import('./NativeReactNativeManifest');
51
+
52
+ expect(imported.default).toBeNull();
53
+ });
54
+ });
55
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * React Native TurboModule spec for the `ReactNativeManifest` native module.
3
+ *
4
+ * Consumed by React Native codegen to emit the Android/iOS native interfaces.
5
+ * The filename must stay prefixed with `Native` — codegen's parser discovers
6
+ * spec files by that convention and will silently skip anything else, leaving
7
+ * the native module uncompiled. Do not rename this file.
8
+ *
9
+ * The `interface` keyword, the `default export`, and the locally-declared
10
+ * `ReactNativeManifest` type are hard requirements of the codegen parser and
11
+ * intentionally deviate from project conventions: codegen parses this file
12
+ * statically and does NOT follow imported type aliases, so the return shape
13
+ * must be defined inline here. The canonical type lives in
14
+ * `nativeManifest.types.ts`; the `assertSameShape` line below is a TS-only
15
+ * check that fails the build if the two definitions drift.
16
+ */
17
+ import { type TurboModule, TurboModuleRegistry } from 'react-native';
18
+
19
+ export type ReactNativeManifest = {
20
+ sdkVersion: string;
21
+ };
22
+
23
+ export interface Spec extends TurboModule {
24
+ getManifest(): ReactNativeManifest;
25
+ }
26
+
27
+ // eslint-disable-next-line import/no-default-export -- codegen requires a default export of the TurboModule spec
28
+ export default TurboModuleRegistry.get<Spec>('DynamicClientManifest');