capacitor-microblink 0.0.1 → 0.3.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.
@@ -1,168 +1,216 @@
1
1
  import Foundation
2
2
  import Capacitor
3
- import MicroblinkPlatform
3
+ import Combine
4
+ import SwiftUI
5
+ import BlinkCard
6
+ import BlinkCardUX
4
7
 
5
8
  @objc(MicroblinkPlugin)
6
- public class MicroblinkPlugin: CAPPlugin, CAPBridgedPlugin, MicroblinkPlatformSDKDelegate {
9
+ public class MicroblinkPlugin: CAPPlugin, CAPBridgedPlugin {
7
10
  public let identifier = "MicroblinkPlugin"
8
11
  public let jsName = "Microblink"
9
12
  public let pluginMethods: [CAPPluginMethod] = [
10
- CAPPluginMethod(name: "startVerification", returnType: CAPPluginReturnPromise)
13
+ CAPPluginMethod(name: "initializeBlinkCard", returnType: CAPPluginReturnPromise),
14
+ CAPPluginMethod(name: "scanCard", returnType: CAPPluginReturnPromise)
11
15
  ]
12
- private var pendingCallId: String?
13
- private var sdk: MicroblinkPlatformSDK?
14
- private var pendingCardScanResult: [String: Any]?
15
16
 
16
- @objc public func startVerification(_ call: CAPPluginCall) {
17
- if pendingCallId != nil {
18
- call.reject("A verification flow is already running.")
19
- return
20
- }
21
- guard let workflowId = call.getString("workflowId"), !workflowId.isEmpty else {
22
- call.reject("Missing required field: workflowId.")
23
- return
24
- }
25
- guard let url = call.getString("url"), !url.isEmpty else {
26
- call.reject("Missing required field: url.")
27
- return
17
+ private enum PluginError: LocalizedError {
18
+ case sdkNotInitialized
19
+
20
+ var errorDescription: String? {
21
+ switch self {
22
+ case .sdkNotInitialized:
23
+ return "BlinkCard is not initialized. Call initializeBlinkCard first or provide licenseKey in scanCard."
24
+ }
28
25
  }
29
- guard let userId = call.getString("userId"), !userId.isEmpty else {
30
- call.reject("Missing required field: userId.")
26
+ }
27
+
28
+ private var pendingBlinkCardCallId: String?
29
+ private weak var blinkCardHostingController: UIViewController?
30
+ private var blinkCardResultObserver: AnyCancellable?
31
+
32
+ private var blinkCardSdk: BlinkCardSdk?
33
+ private var isBlinkCardInitialized = false
34
+
35
+ @objc public func initializeBlinkCard(_ call: CAPPluginCall) {
36
+ guard let licenseKey = call.getString("licenseKey"), !licenseKey.isEmpty else {
37
+ call.reject("Missing required field: licenseKey.")
31
38
  return
32
39
  }
33
- guard let isProcessingStoringAllowed = call.getBool("isProcessingStoringAllowed") else {
34
- call.reject("Missing required field: isProcessingStoringAllowed.")
35
- return
40
+
41
+ let licensee = normalized(call.getString("licensee"))
42
+
43
+ Task {
44
+ do {
45
+ let sdk = try await createBlinkCardSdk(licenseKey: licenseKey, licensee: licensee)
46
+ await MainActor.run {
47
+ self.blinkCardSdk = sdk
48
+ self.isBlinkCardInitialized = true
49
+ call.resolve(["initialized": true])
50
+ }
51
+ } catch {
52
+ await MainActor.run {
53
+ self.isBlinkCardInitialized = false
54
+ call.reject("Failed to initialize BlinkCard SDK: \(error.localizedDescription)")
55
+ }
56
+ }
36
57
  }
37
- guard let isTrainingAllowed = call.getBool("isTrainingAllowed") else {
38
- call.reject("Missing required field: isTrainingAllowed.")
58
+ }
59
+
60
+ @objc public func scanCard(_ call: CAPPluginCall) {
61
+ if pendingBlinkCardCallId != nil {
62
+ call.reject("A BlinkCard flow is already running.")
39
63
  return
40
64
  }
41
- guard let vc = bridge?.viewController else {
65
+
66
+ guard let viewController = bridge?.viewController else {
42
67
  call.reject("No view controller available")
43
68
  return
44
69
  }
45
70
 
46
- let givenOnMs = call.getDouble("givenOn") ?? Date().timeIntervalSince1970 * 1000
47
- let givenOn = Date(timeIntervalSince1970: givenOnMs / 1000)
48
- let consent = MicroblinkPlatformConsent(
49
- userId: userId,
50
- isProcessingStoringAllowed: isProcessingStoringAllowed,
51
- isTrainingAllowed: isTrainingAllowed,
52
- isGivenOn: givenOn,
53
- note: call.getString("note")
71
+ let providedLicenseKey = normalized(call.getString("licenseKey"))
72
+ let providedLicensee = normalized(call.getString("licensee"))
73
+
74
+ Task {
75
+ do {
76
+ let sdk: BlinkCardSdk
77
+ if let providedLicenseKey {
78
+ sdk = try await createBlinkCardSdk(licenseKey: providedLicenseKey, licensee: providedLicensee)
79
+ await MainActor.run {
80
+ self.blinkCardSdk = sdk
81
+ self.isBlinkCardInitialized = true
82
+ }
83
+ } else {
84
+ guard let existingSdk = await MainActor.run(body: { self.blinkCardSdk }), self.isBlinkCardInitialized else {
85
+ throw PluginError.sdkNotInitialized
86
+ }
87
+ sdk = existingSdk
88
+ }
89
+
90
+ try await startBlinkCardScan(call: call, sdk: sdk, presentingViewController: viewController)
91
+ } catch let error as PluginError {
92
+ await MainActor.run {
93
+ call.reject(error.localizedDescription)
94
+ }
95
+ } catch {
96
+ await MainActor.run {
97
+ call.reject("Failed to start BlinkCard scan: \(error.localizedDescription)")
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ private func startBlinkCardScan(call: CAPPluginCall, sdk: BlinkCardSdk, presentingViewController: UIViewController) async throws {
104
+ let extractionSettings = ExtractionSettings(
105
+ extractIban: call.getBool("extractIban") ?? true,
106
+ extractExpiryDate: call.getBool("extractExpiryDate") ?? true,
107
+ extractCardholderName: call.getBool("extractOwner") ?? true,
108
+ extractCvv: call.getBool("extractCvv") ?? true,
109
+ extractInvalidCardNumber: call.getBool("allowInvalidCardNumber") ?? false
54
110
  )
111
+ let scanningSettings = ScanningSettings(extractionSettings: extractionSettings)
112
+ let sessionSettings = BlinkCardSessionSettings(inputImageSource: .video, scanningSettings: scanningSettings)
55
113
 
56
- let serviceSettings = MicroblinkPlatformServiceSettings(
57
- workflowId: workflowId,
58
- url: url,
59
- consent: consent,
60
- additionalRequestHeaders: call.getObject("additionalRequestHeaders") as? [String: String]
114
+ let analyzer = try await BlinkCardAnalyzer(
115
+ sdk: sdk,
116
+ blinkCardSessionSettings: sessionSettings,
117
+ eventStream: BlinkCardEventStream()
61
118
  )
62
- if let startTransaction = call.getString("startTransactionPath"), !startTransaction.isEmpty {
63
- serviceSettings.startTransaction = startTransaction
64
- }
65
- if let cancelWorkflow = call.getString("cancelWorkflowPath"), !cancelWorkflow.isEmpty {
66
- serviceSettings.cancelWorkflow = cancelWorkflow
67
- }
68
- if let workflowInfo = call.getString("workflowInfoPath"), !workflowInfo.isEmpty {
69
- serviceSettings.getWorkflowInfo = workflowInfo
70
- }
71
119
 
72
- pendingCardScanResult = nil
73
- bridge?.saveCall(call)
74
- pendingCallId = call.callbackId
75
- sdk = MicroblinkPlatformSDK(serviceSettings: serviceSettings, delegate: self)
120
+ await MainActor.run {
121
+ let viewModel = BlinkCardUXModel(analyzer: analyzer)
122
+ let hostingController = UIHostingController(rootView: BlinkCardUXView(viewModel: viewModel))
123
+ hostingController.modalPresentationStyle = .fullScreen
76
124
 
77
- let sdkViewController = sdk?.startSDK()
78
- guard let sdkViewController else {
79
- clearPendingState()
80
- call.reject("Unable to start verification.")
81
- return
125
+ self.bridge?.saveCall(call)
126
+ self.pendingBlinkCardCallId = call.callbackId
127
+ self.blinkCardHostingController = hostingController
128
+
129
+ self.blinkCardResultObserver = viewModel.$result
130
+ .dropFirst()
131
+ .sink { [weak self] resultState in
132
+ self?.handleBlinkCardResult(resultState)
133
+ }
134
+
135
+ presentingViewController.present(hostingController, animated: true)
82
136
  }
83
- vc.present(sdkViewController, animated: true)
84
137
  }
85
138
 
86
- public func microblinkPlatformSDKDidFinish(
87
- viewController: UIViewController,
88
- result: MicroblinkPlatformResult
89
- ) {
90
- var payload: [String: Any] = [
91
- "canceled": false,
92
- "transactionId": result.transactionId,
93
- "status": mapStatus(result.status)
94
- ]
95
- if let pendingCardScanResult {
96
- payload["cardScanResult"] = pendingCardScanResult
139
+ private func handleBlinkCardResult(_ resultState: BlinkCardResultState?) {
140
+ guard let scanningResult = resultState?.scanningResult else {
141
+ finalizeBlinkCard(payload: ["canceled": true])
142
+ return
97
143
  }
98
- finalize(viewController: viewController, payload: payload)
99
- }
100
144
 
101
- public func microblinkPlatformSDKDidClose(
102
- viewController: UIViewController,
103
- cancelState: MicroblinkPlatformCancelState
104
- ) {
105
145
  var payload: [String: Any] = [
106
- "canceled": true,
107
- "transactionId": cancelState.transactionId as Any,
108
- "cancelReason": mapCancelReason(cancelState.cancelReason)
146
+ "canceled": false,
147
+ "resultState": "valid",
148
+ "processingStatus": "success",
149
+ "owner": scanningResult.cardholderName as Any,
150
+ "iban": scanningResult.iban as Any
109
151
  ]
110
- if let pendingCardScanResult {
111
- payload["cardScanResult"] = pendingCardScanResult
152
+
153
+ if let cardAccount = scanningResult.cardAccounts.first {
154
+ payload["cardNumber"] = cardAccount.cardNumber
155
+ payload["cardNumberValid"] = cardAccount.cardNumberValid
156
+ payload["cardNumberPrefix"] = cardAccount.cardNumberPrefix as Any
157
+ payload["cvv"] = cardAccount.cvv as Any
158
+ payload["expiryDate"] = mapBlinkCardDate(cardAccount.expiryDate) as Any
112
159
  }
113
- finalize(viewController: viewController, payload: payload)
114
- }
115
160
 
116
- public func microblinkPlatformSDKDidFinishCardScanStep(
117
- viewController: UIViewController,
118
- cardScanResult: MicroblinkPlatformResultCardScanResult
119
- ) {
120
- pendingCardScanResult = [
121
- "cardNumber": cardScanResult.cardNumber,
122
- "expiryDate": cardScanResult.expiryDate,
123
- "owner": cardScanResult.owner,
124
- "cvv": cardScanResult.cvv
125
- ]
161
+ finalizeBlinkCard(payload: payload)
126
162
  }
127
163
 
128
- private func mapStatus(_ status: MicroblinkPlatformResultStatus) -> String {
129
- switch status {
130
- case .accept:
131
- return "accept"
132
- case .review:
133
- return "review"
134
- case .reject:
135
- return "reject"
136
- @unknown default:
137
- return "review"
164
+ private func finalizeBlinkCard(payload: [String: Any]) {
165
+ DispatchQueue.main.async {
166
+ let pendingCallId = self.pendingBlinkCardCallId
167
+ self.blinkCardHostingController?.dismiss(animated: true)
168
+
169
+ guard let pendingCallId, let call = self.bridge?.savedCall(withID: pendingCallId) else {
170
+ self.clearPendingBlinkCardState()
171
+ return
172
+ }
173
+
174
+ self.clearPendingBlinkCardState()
175
+ call.resolve(payload)
176
+ self.bridge?.releaseCall(withID: pendingCallId)
138
177
  }
139
178
  }
140
179
 
141
- private func mapCancelReason(_ reason: MicroblinkPlatformCancelReason) -> String {
142
- switch reason {
143
- case .userCanceled:
144
- return "userCanceled"
145
- case .consentDenied:
146
- return "consentDenied"
147
- @unknown default:
148
- return "userCanceled"
149
- }
180
+ private func clearPendingBlinkCardState() {
181
+ pendingBlinkCardCallId = nil
182
+ blinkCardHostingController = nil
183
+ blinkCardResultObserver = nil
150
184
  }
151
185
 
152
- private func finalize(viewController: UIViewController, payload: [String: Any]) {
153
- viewController.dismiss(animated: true)
154
- guard let pendingCallId, let call = bridge?.savedCall(withID: pendingCallId) else {
155
- clearPendingState()
156
- return
186
+ private func normalized(_ value: String?) -> String? {
187
+ guard let value, !value.isEmpty else {
188
+ return nil
157
189
  }
158
- clearPendingState()
159
- call.resolve(payload)
160
- bridge?.releaseCall(withID: pendingCallId)
190
+ return value
161
191
  }
162
192
 
163
- private func clearPendingState() {
164
- pendingCallId = nil
165
- pendingCardScanResult = nil
166
- sdk = nil
193
+ private func mapBlinkCardDate(_ date: DateResult?) -> [String: Int]? {
194
+ guard let date,
195
+ let day = date.day,
196
+ let month = date.month,
197
+ let year = date.year else {
198
+ return nil
199
+ }
200
+ return [
201
+ "day": day,
202
+ "month": month,
203
+ "year": year
204
+ ]
205
+ }
206
+
207
+ private func createBlinkCardSdk(licenseKey: String, licensee: String?) async throws -> BlinkCardSdk {
208
+ await BlinkCardSdk.terminateBlinkCardSdk()
209
+ let settings = BlinkCardSdkSettings(
210
+ licenseKey: licenseKey,
211
+ licensee: licensee,
212
+ downloadResources: true
213
+ )
214
+ return try await BlinkCardSdk.createBlinkCardSdk(withSettings: settings)
167
215
  }
168
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capacitor-microblink",
3
- "version": "0.0.1",
3
+ "version": "0.3.0",
4
4
  "description": "Capacitor plugin for microblink",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -31,7 +31,7 @@
31
31
  ],
32
32
  "scripts": {
33
33
  "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
- "verify:ios": "xcodebuild -scheme CapacitorMicroblink -destination generic/platform=iOS",
34
+ "verify:ios": "xcodebuild -scheme CapacitorMicroblink -destination generic/platform=iOS -derivedDataPath /tmp/capacitor-microblink-deriveddata clean build",
35
35
  "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
36
  "verify:web": "npm run build",
37
37
  "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",