@hawcx/react-native-sdk 1.0.8 → 1.1.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 (134) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/HawcxReactNative.podspec +2 -2
  3. package/README.md +327 -109
  4. package/android/build.gradle +2 -2
  5. package/android/src/main/java/com/hawcx/reactnative/HawcxEventDispatcher.kt +4 -0
  6. package/android/src/main/java/com/hawcx/reactnative/HawcxReactNativeModule.kt +324 -1
  7. package/android/src/main/java/com/hawcx/reactnative/v6/HawcxV6Bridge.kt +402 -0
  8. package/ios/Frameworks/HawcxFramework.xcframework/Info.plist +5 -5
  9. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/HawcxFramework +0 -0
  10. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Info.plist +0 -0
  11. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.abi.json +22145 -2
  12. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.private.swiftinterface +628 -0
  13. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  14. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios.swiftinterface +628 -0
  15. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/HawcxFramework +0 -0
  16. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Info.plist +0 -0
  17. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.abi.json +22145 -2
  18. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +628 -0
  19. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  20. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/arm64-apple-ios-simulator.swiftinterface +628 -0
  21. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.abi.json +22145 -2
  22. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +628 -0
  23. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  24. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/Modules/HawcxFramework.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +628 -0
  25. package/ios/Frameworks/HawcxFramework.xcframework/ios-arm64_x86_64-simulator/HawcxFramework.framework/_CodeSignature/CodeResources +21 -21
  26. package/ios/HawcxReactNative.m +56 -0
  27. package/ios/HawcxReactNative.swift +380 -1
  28. package/ios/HawcxV6BridgeSupport.swift +468 -0
  29. package/lib/commonjs/index.js +326 -3
  30. package/lib/commonjs/index.js.map +1 -1
  31. package/lib/commonjs/v6Normalization.js +325 -0
  32. package/lib/commonjs/v6Normalization.js.map +1 -0
  33. package/lib/commonjs/v6State.js +186 -0
  34. package/lib/commonjs/v6State.js.map +1 -0
  35. package/lib/commonjs/v6Types.js +2 -0
  36. package/lib/commonjs/v6Types.js.map +1 -0
  37. package/lib/commonjs/v6WebLogin.js +101 -0
  38. package/lib/commonjs/v6WebLogin.js.map +1 -0
  39. package/lib/module/index.js +287 -1
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/v6Normalization.js +318 -0
  42. package/lib/module/v6Normalization.js.map +1 -0
  43. package/lib/module/v6State.js +173 -0
  44. package/lib/module/v6State.js.map +1 -0
  45. package/lib/module/v6Types.js +2 -0
  46. package/lib/module/v6Types.js.map +1 -0
  47. package/lib/module/v6WebLogin.js +92 -0
  48. package/lib/module/v6WebLogin.js.map +1 -0
  49. package/lib/typescript/index.d.ts +83 -0
  50. package/lib/typescript/index.d.ts.map +1 -1
  51. package/lib/typescript/v6Normalization.d.ts +3 -0
  52. package/lib/typescript/v6Normalization.d.ts.map +1 -0
  53. package/lib/typescript/v6State.d.ts +13 -0
  54. package/lib/typescript/v6State.d.ts.map +1 -0
  55. package/lib/typescript/v6Types.d.ts +157 -0
  56. package/lib/typescript/v6Types.d.ts.map +1 -0
  57. package/lib/typescript/v6WebLogin.d.ts +32 -0
  58. package/lib/typescript/v6WebLogin.d.ts.map +1 -0
  59. package/package.json +21 -9
  60. package/src/index.ts +477 -0
  61. package/src/v6Normalization.ts +356 -0
  62. package/src/v6State.ts +238 -0
  63. package/src/v6Types.ts +194 -0
  64. package/src/v6WebLogin.ts +154 -0
  65. package/android/.settings/org.eclipse.buildship.core.prefs +0 -2
  66. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  67. package/android/gradle/wrapper/gradle-wrapper.properties +0 -6
  68. package/android/gradlew +0 -185
  69. package/android/gradlew.bat +0 -89
  70. package/android/libs/hawcx-5.1.4.aar +0 -0
  71. package/docs/RELEASE.md +0 -129
  72. package/example/README.md +0 -59
  73. package/example/android/app/build.gradle +0 -126
  74. package/example/android/app/debug.keystore +0 -0
  75. package/example/android/app/proguard-rules.pro +0 -10
  76. package/example/android/app/src/debug/AndroidManifest.xml +0 -9
  77. package/example/android/app/src/main/AndroidManifest.xml +0 -27
  78. package/example/android/app/src/main/java/com/hawcx/example/MainActivity.kt +0 -22
  79. package/example/android/app/src/main/java/com/hawcx/example/MainApplication.kt +0 -45
  80. package/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +0 -36
  81. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  82. package/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  83. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  84. package/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  85. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  86. package/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  87. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  88. package/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  89. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  90. package/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  91. package/example/android/app/src/main/res/values/strings.xml +0 -3
  92. package/example/android/app/src/main/res/values/styles.xml +0 -9
  93. package/example/android/build.gradle +0 -35
  94. package/example/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  95. package/example/android/gradle/wrapper/gradle-wrapper.properties +0 -7
  96. package/example/android/gradle.properties +0 -41
  97. package/example/android/gradlew +0 -249
  98. package/example/android/gradlew.bat +0 -92
  99. package/example/android/local.properties +0 -2
  100. package/example/android/settings.gradle +0 -38
  101. package/example/app.json +0 -4
  102. package/example/babel.config.js +0 -3
  103. package/example/e2e/README.md +0 -17
  104. package/example/e2e/hawcx-login.yaml +0 -14
  105. package/example/index.js +0 -5
  106. package/example/ios/.xcode.env +0 -11
  107. package/example/ios/HawcxExampleApp/AppDelegate.h +0 -6
  108. package/example/ios/HawcxExampleApp/AppDelegate.mm +0 -31
  109. package/example/ios/HawcxExampleApp/Images.xcassets/AppIcon.appiconset/Contents.json +0 -53
  110. package/example/ios/HawcxExampleApp/Images.xcassets/Contents.json +0 -6
  111. package/example/ios/HawcxExampleApp/Info.plist +0 -55
  112. package/example/ios/HawcxExampleApp/LaunchScreen.storyboard +0 -47
  113. package/example/ios/HawcxExampleApp/PrivacyInfo.xcprivacy +0 -37
  114. package/example/ios/HawcxExampleApp/main.m +0 -10
  115. package/example/ios/HawcxExampleApp.xcodeproj/project.pbxproj +0 -704
  116. package/example/ios/HawcxExampleApp.xcodeproj/project.xcworkspace/xcuserdata/agambhullar.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  117. package/example/ios/HawcxExampleApp.xcodeproj/xcshareddata/xcschemes/HawcxExampleApp.xcscheme +0 -90
  118. package/example/ios/HawcxExampleApp.xcodeproj/xcuserdata/agambhullar.xcuserdatad/xcschemes/xcschememanagement.plist +0 -16
  119. package/example/ios/HawcxExampleApp.xcworkspace/contents.xcworkspacedata +0 -10
  120. package/example/ios/HawcxExampleAppTests/HawcxExampleAppTests.m +0 -66
  121. package/example/ios/HawcxExampleAppTests/Info.plist +0 -24
  122. package/example/ios/Podfile +0 -79
  123. package/example/ios/Podfile.lock +0 -1290
  124. package/example/metro.config.js +0 -16
  125. package/example/package-lock.json +0 -13220
  126. package/example/package.json +0 -30
  127. package/example/src/App.tsx +0 -755
  128. package/example/src/hawcx.config.ts +0 -25
  129. package/example/tsconfig.json +0 -8
  130. package/ios/Frameworks/.keep +0 -0
  131. package/lib/typescript/__tests__/index.test.d.ts +0 -2
  132. package/lib/typescript/__tests__/index.test.d.ts.map +0 -1
  133. package/react_mobile_sdk_plan.md +0 -242
  134. package/src/__tests__/index.test.ts +0 -206
@@ -0,0 +1,468 @@
1
+ import Foundation
2
+ import HawcxFramework
3
+
4
+ #if canImport(React)
5
+
6
+ internal let HAWCX_V6_FLOW_EVENT_NAME = "hawcx.v6.flow.event"
7
+
8
+ internal struct HawcxV6StartOptions {
9
+ let flowType: HawcxV1FlowType
10
+ let identifier: String
11
+ let startToken: String?
12
+ let inviteCode: String?
13
+ let codeChallenge: String?
14
+
15
+ static func from(options: NSDictionary) throws -> HawcxV6StartOptions {
16
+ let identifier = (options["identifier"] as? String)?
17
+ .trimmingCharacters(in: .whitespacesAndNewlines)
18
+ .nilIfEmpty
19
+ guard let identifier else {
20
+ throw HawcxV6BridgeError.invalidInput("identifier is required")
21
+ }
22
+
23
+ let flowTypeRaw = (options["flowType"] as? String)?
24
+ .trimmingCharacters(in: .whitespacesAndNewlines)
25
+ .nilIfEmpty ?? HawcxV1FlowType.signin.rawValue
26
+ guard let flowType = HawcxV1FlowType(rawValue: flowTypeRaw) else {
27
+ throw HawcxV6BridgeError.invalidInput("flowType must be one of signin, signup, or account_manage")
28
+ }
29
+
30
+ return HawcxV6StartOptions(
31
+ flowType: flowType,
32
+ identifier: identifier,
33
+ startToken: (options["startToken"] as? String)?
34
+ .trimmingCharacters(in: .whitespacesAndNewlines)
35
+ .nilIfEmpty,
36
+ inviteCode: (options["inviteCode"] as? String)?
37
+ .trimmingCharacters(in: .whitespacesAndNewlines)
38
+ .nilIfEmpty,
39
+ codeChallenge: (options["codeChallenge"] as? String)?
40
+ .trimmingCharacters(in: .whitespacesAndNewlines)
41
+ .nilIfEmpty
42
+ )
43
+ }
44
+ }
45
+
46
+ internal struct HawcxV6InitializeOptions {
47
+ let relyingParty: String?
48
+ let autoPollApprovals: Bool
49
+
50
+ static func from(config: NSDictionary) -> HawcxV6InitializeOptions {
51
+ let relyingParty = (config["relyingParty"] as? String)?
52
+ .trimmingCharacters(in: .whitespacesAndNewlines)
53
+ .nilIfEmpty
54
+ let autoPollApprovals = (config["autoPollApprovals"] as? NSNumber)?.boolValue ?? true
55
+ return HawcxV6InitializeOptions(
56
+ relyingParty: relyingParty,
57
+ autoPollApprovals: autoPollApprovals
58
+ )
59
+ }
60
+ }
61
+
62
+ internal final class HawcxV6Bridge {
63
+ private weak var emitter: HawcxReactNative?
64
+ private(set) var sdk: HawcxV1SDK?
65
+
66
+ init(emitter: HawcxReactNative) {
67
+ self.emitter = emitter
68
+ }
69
+
70
+ func configure(configId: String, baseURL: URL, options: HawcxV6InitializeOptions) {
71
+ dispose(resetFlow: true)
72
+
73
+ let sdk = HawcxV1SDK(
74
+ configId: configId,
75
+ baseURL: baseURL,
76
+ relyingParty: options.relyingParty,
77
+ autoPollApprovals: options.autoPollApprovals
78
+ )
79
+
80
+ sdk.flow.onUpdate = { [weak self] update in
81
+ self?.emitter?.emitV6FlowEvent(HawcxV6FlowEventCodec.encode(update: update))
82
+ }
83
+
84
+ self.sdk = sdk
85
+ }
86
+
87
+ func start(options: HawcxV6StartOptions) throws {
88
+ guard let sdk else {
89
+ throw HawcxV6BridgeError.notInitialized
90
+ }
91
+ sdk.start(
92
+ flowType: options.flowType,
93
+ identifier: options.identifier,
94
+ startToken: options.startToken,
95
+ inviteCode: options.inviteCode,
96
+ codeChallenge: options.codeChallenge
97
+ )
98
+ }
99
+
100
+ func selectMethod(_ methodId: String) throws {
101
+ guard let sdk else {
102
+ throw HawcxV6BridgeError.notInitialized
103
+ }
104
+ sdk.flow.selectMethod(methodId)
105
+ }
106
+
107
+ func submitCode(_ code: String) throws {
108
+ guard let sdk else {
109
+ throw HawcxV6BridgeError.notInitialized
110
+ }
111
+ sdk.flow.submitCode(code)
112
+ }
113
+
114
+ func submitTotp(_ code: String) throws {
115
+ guard let sdk else {
116
+ throw HawcxV6BridgeError.notInitialized
117
+ }
118
+ sdk.flow.submitTotp(code)
119
+ }
120
+
121
+ func submitPhone(_ phone: String) throws {
122
+ guard let sdk else {
123
+ throw HawcxV6BridgeError.notInitialized
124
+ }
125
+ sdk.flow.submitPhone(phone)
126
+ }
127
+
128
+ @discardableResult
129
+ func resend() throws -> Bool {
130
+ guard let sdk else {
131
+ throw HawcxV6BridgeError.notInitialized
132
+ }
133
+ return sdk.flow.resend()
134
+ }
135
+
136
+ func poll() throws {
137
+ guard let sdk else {
138
+ throw HawcxV6BridgeError.notInitialized
139
+ }
140
+ sdk.flow.poll()
141
+ }
142
+
143
+ func cancel() throws {
144
+ guard let sdk else {
145
+ throw HawcxV6BridgeError.notInitialized
146
+ }
147
+ sdk.flow.cancel()
148
+ }
149
+
150
+ func approveQr(
151
+ rawPayload: String,
152
+ identifier: String,
153
+ rememberDevice: Bool,
154
+ completion: @escaping (Result<HawcxV6QrApprovalResult, HawcxV1QrApprovalError>) -> Void
155
+ ) throws {
156
+ guard let sdk else {
157
+ throw HawcxV6BridgeError.notInitialized
158
+ }
159
+
160
+ guard let payload = HawcxV1QrPayloadParser.parse(rawPayload) else {
161
+ throw HawcxV6BridgeError.invalidInput("rawPayload must be a valid Hawcx QR payload")
162
+ }
163
+
164
+ let trimmedIdentifier = identifier.trimmingCharacters(in: .whitespacesAndNewlines)
165
+ guard !trimmedIdentifier.isEmpty else {
166
+ throw HawcxV6BridgeError.invalidInput("identifier is required")
167
+ }
168
+
169
+ let service = HawcxV1QrApprovalService(
170
+ config: sdk.client.config,
171
+ signer: sdk.crypto
172
+ )
173
+
174
+ service.approve(payload: payload, identifier: trimmedIdentifier, rememberDevice: rememberDevice) { result in
175
+ switch result {
176
+ case .success(.approved):
177
+ completion(.success(.approved(payloadType: payload.type.rawValue)))
178
+ case .success(.bound(let userid)):
179
+ completion(.success(.bound(payloadType: payload.type.rawValue, userId: userid)))
180
+ case .failure(let error):
181
+ completion(.failure(error))
182
+ }
183
+ }
184
+ }
185
+
186
+ func handleRedirect(urlString: String) throws {
187
+ guard let sdk else {
188
+ throw HawcxV6BridgeError.notInitialized
189
+ }
190
+ guard let url = URL(string: urlString),
191
+ let callback = HawcxV1OAuthCallbackParser.parse(url) else {
192
+ throw HawcxV6BridgeError.invalidInput("url must be a valid V6 OAuth callback URL")
193
+ }
194
+ sdk.flow.oauthCallback(code: callback.code, state: callback.state)
195
+ }
196
+
197
+ func dispose(resetFlow: Bool) {
198
+ guard let sdk else { return }
199
+ sdk.flow.onUpdate = nil
200
+ if resetFlow {
201
+ sdk.reset()
202
+ }
203
+ self.sdk = nil
204
+ }
205
+ }
206
+
207
+ internal enum HawcxV6QrApprovalResult {
208
+ case approved(payloadType: String)
209
+ case bound(payloadType: String, userId: String?)
210
+ }
211
+
212
+ internal enum HawcxV6FlowEventCodec {
213
+ static func encode(update: HawcxV1FlowUpdate) -> [String: Any] {
214
+ switch update {
215
+ case .idle:
216
+ return ["type": "idle"]
217
+ case .loading(let session):
218
+ var payload: [String: Any] = [:]
219
+ if let session, !session.isEmpty {
220
+ payload["session"] = session
221
+ }
222
+ return [
223
+ "type": "loading",
224
+ "payload": payload
225
+ ]
226
+ case .prompt(let context, let prompt):
227
+ var payload = encodePromptContext(context)
228
+ payload["prompt"] = encodePrompt(prompt)
229
+ return [
230
+ "type": "prompt",
231
+ "payload": payload
232
+ ]
233
+ case .completed(let session, let authCode, let expiresAt, let codeVerifier, let meta):
234
+ var payload: [String: Any] = [
235
+ "session": session,
236
+ "authCode": authCode,
237
+ "expiresAt": expiresAt,
238
+ "traceId": meta.traceId
239
+ ]
240
+ if let codeVerifier, !codeVerifier.isEmpty {
241
+ payload["codeVerifier"] = codeVerifier
242
+ }
243
+ return [
244
+ "type": "completed",
245
+ "payload": payload
246
+ ]
247
+ case .error(let session, let code, let action, let message, let retryable, let details, let meta):
248
+ var payload: [String: Any] = [
249
+ "code": code,
250
+ "message": message,
251
+ "retryable": retryable
252
+ ]
253
+ if let session, !session.isEmpty {
254
+ payload["session"] = session
255
+ }
256
+ if let actionRawValue = actionRawValue(action) {
257
+ payload["action"] = actionRawValue
258
+ }
259
+ if let traceId = meta?.traceId, !traceId.isEmpty {
260
+ payload["traceId"] = traceId
261
+ }
262
+ if let details {
263
+ payload["details"] = encodeErrorDetails(details)
264
+ }
265
+ return [
266
+ "type": "error",
267
+ "payload": payload
268
+ ]
269
+ }
270
+ }
271
+
272
+ private static func encodePromptContext(_ context: HawcxV1PromptContext) -> [String: Any] {
273
+ var payload: [String: Any] = [
274
+ "session": context.session,
275
+ "traceId": context.meta.traceId,
276
+ "expiresAt": context.meta.expiresAt
277
+ ]
278
+ if let stepInfo = context.stepInfo {
279
+ payload["step"] = encodeStepInfo(stepInfo)
280
+ }
281
+ if let risk = context.risk {
282
+ payload["risk"] = encodeRiskInfo(risk)
283
+ }
284
+ if let codeChannel = context.codeChannel, !codeChannel.isEmpty {
285
+ payload["codeChannel"] = codeChannel
286
+ }
287
+ return payload
288
+ }
289
+
290
+ private static func encodePrompt(_ prompt: HawcxV1UserPrompt) -> [String: Any] {
291
+ switch prompt {
292
+ case let .selectMethod(methods, phase):
293
+ var payload: [String: Any] = [
294
+ "type": "select_method",
295
+ "methods": methods.map(encodeMethod)
296
+ ]
297
+ if let phase, !phase.isEmpty {
298
+ payload["phase"] = phase
299
+ }
300
+ return payload
301
+ case let .enterCode(destination, codeLength, codeFormat, codeExpiresAt, resendAt):
302
+ var payload: [String: Any] = [
303
+ "type": "enter_code",
304
+ "destination": destination
305
+ ]
306
+ if let codeLength {
307
+ payload["codeLength"] = codeLength
308
+ }
309
+ if let codeFormat, !codeFormat.isEmpty {
310
+ payload["codeFormat"] = codeFormat
311
+ }
312
+ if let codeExpiresAt, !codeExpiresAt.isEmpty {
313
+ payload["codeExpiresAt"] = codeExpiresAt
314
+ }
315
+ if let resendAt, !resendAt.isEmpty {
316
+ payload["resendAt"] = resendAt
317
+ }
318
+ return payload
319
+ case .enterTotp:
320
+ return ["type": "enter_totp"]
321
+ case let .setupTotp(secret, otpauthUrl, period):
322
+ var payload: [String: Any] = [
323
+ "type": "setup_totp",
324
+ "secret": secret,
325
+ "otpauthUrl": otpauthUrl
326
+ ]
327
+ if let period {
328
+ payload["period"] = period
329
+ }
330
+ return payload
331
+ case let .setupSms(existingPhone):
332
+ var payload: [String: Any] = [
333
+ "type": "setup_sms"
334
+ ]
335
+ if let existingPhone, !existingPhone.isEmpty {
336
+ payload["existingPhone"] = existingPhone
337
+ }
338
+ return payload
339
+ case let .redirect(url, returnScheme):
340
+ var payload: [String: Any] = [
341
+ "type": "redirect",
342
+ "url": url
343
+ ]
344
+ if let returnScheme, !returnScheme.isEmpty {
345
+ payload["returnScheme"] = returnScheme
346
+ }
347
+ return payload
348
+ case let .awaitApproval(qrData, expiresAt, pollInterval):
349
+ var payload: [String: Any] = [
350
+ "type": "await_approval",
351
+ "expiresAt": expiresAt,
352
+ "pollInterval": pollInterval
353
+ ]
354
+ if let qrData, !qrData.isEmpty {
355
+ payload["qrData"] = qrData
356
+ }
357
+ return payload
358
+ }
359
+ }
360
+
361
+ private static func encodeMethod(_ method: HawcxV1Method) -> [String: Any] {
362
+ var payload: [String: Any] = [
363
+ "id": method.id,
364
+ "label": method.label
365
+ ]
366
+ if let icon = method.icon, !icon.isEmpty {
367
+ payload["icon"] = icon
368
+ }
369
+ return payload
370
+ }
371
+
372
+ private static func encodeStepInfo(_ step: HawcxV1StepInfo) -> [String: Any] {
373
+ var payload: [String: Any] = ["id": step.id]
374
+ if let label = step.label, !label.isEmpty {
375
+ payload["label"] = label
376
+ }
377
+ return payload
378
+ }
379
+
380
+ private static func encodeRiskInfo(_ risk: HawcxV1RiskInfo) -> [String: Any] {
381
+ var payload: [String: Any] = [
382
+ "detected": risk.detected,
383
+ "reasons": risk.reasons
384
+ ]
385
+ if let message = risk.message, !message.isEmpty {
386
+ payload["message"] = message
387
+ }
388
+ if let riskScore = risk.riskScore {
389
+ payload["riskScore"] = riskScore
390
+ }
391
+ if let location = risk.location {
392
+ payload["location"] = encodeRiskLocation(location)
393
+ }
394
+ return payload
395
+ }
396
+
397
+ private static func encodeRiskLocation(_ location: HawcxV1RiskLocation) -> [String: Any] {
398
+ var payload: [String: Any] = [:]
399
+ if let city = location.city, !city.isEmpty {
400
+ payload["city"] = city
401
+ }
402
+ if let country = location.country, !country.isEmpty {
403
+ payload["country"] = country
404
+ }
405
+ return payload
406
+ }
407
+
408
+ private static func encodeErrorDetails(_ details: HawcxV1ErrorDetails) -> [String: Any] {
409
+ var payload: [String: Any] = [:]
410
+ if let retryAfterSeconds = details.retryAfterSeconds {
411
+ payload["retryAfterSeconds"] = retryAfterSeconds
412
+ }
413
+ if let retryAt = details.retryAt, !retryAt.isEmpty {
414
+ payload["retryAt"] = retryAt
415
+ }
416
+ if let attemptsRemaining = details.attemptsRemaining {
417
+ payload["attemptsRemaining"] = attemptsRemaining
418
+ }
419
+ if let errors = details.errors, !errors.isEmpty {
420
+ payload["errors"] = errors.map { ["field": $0.field, "message": $0.message] }
421
+ }
422
+ return payload
423
+ }
424
+
425
+ private static func actionRawValue(_ action: HawcxV1ErrorAction?) -> String? {
426
+ guard let action else { return nil }
427
+ switch action {
428
+ case .retryInput:
429
+ return "retry_input"
430
+ case .restartFlow:
431
+ return "restart_flow"
432
+ case .wait:
433
+ return "wait"
434
+ case .retryRequest:
435
+ return "retry_request"
436
+ case .abort:
437
+ return "abort"
438
+ case .resendCode:
439
+ return "resend_code"
440
+ case .selectMethod:
441
+ return "select_method"
442
+ case .unknown(let raw):
443
+ return raw
444
+ }
445
+ }
446
+ }
447
+
448
+ internal enum HawcxV6BridgeError: LocalizedError {
449
+ case notInitialized
450
+ case invalidInput(String)
451
+
452
+ var errorDescription: String? {
453
+ switch self {
454
+ case .notInitialized:
455
+ return "initialize must be called before using V6 bridge methods"
456
+ case let .invalidInput(message):
457
+ return message
458
+ }
459
+ }
460
+ }
461
+
462
+ private extension String {
463
+ var nilIfEmpty: String? {
464
+ isEmpty ? nil : self
465
+ }
466
+ }
467
+
468
+ #endif