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.
- package/CapacitorMicroblink.podspec +1 -0
- package/LICENSE +21 -0
- package/Package.swift +3 -2
- package/README.md +65 -42
- package/android/build.gradle +10 -1
- package/android/src/main/java/com/otto/microblink/BlinkCardInitBridge.kt +29 -0
- package/android/src/main/java/com/otto/microblink/MicroblinkPlugin.java +174 -163
- package/dist/docs.json +126 -78
- package/dist/esm/definitions.d.ts +36 -24
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +3 -2
- package/dist/esm/web.js +5 -2
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +5 -2
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +5 -2
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/MicroblinkPlugin/MicroblinkPlugin.swift +171 -123
- package/package.json +2 -2
|
@@ -1,168 +1,216 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import Capacitor
|
|
3
|
-
import
|
|
3
|
+
import Combine
|
|
4
|
+
import SwiftUI
|
|
5
|
+
import BlinkCard
|
|
6
|
+
import BlinkCardUX
|
|
4
7
|
|
|
5
8
|
@objc(MicroblinkPlugin)
|
|
6
|
-
public class MicroblinkPlugin: CAPPlugin, CAPBridgedPlugin
|
|
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: "
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
30
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
47
|
-
let
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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":
|
|
107
|
-
"
|
|
108
|
-
"
|
|
146
|
+
"canceled": false,
|
|
147
|
+
"resultState": "valid",
|
|
148
|
+
"processingStatus": "success",
|
|
149
|
+
"owner": scanningResult.cardholderName as Any,
|
|
150
|
+
"iban": scanningResult.iban as Any
|
|
109
151
|
]
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
clearPendingState()
|
|
156
|
-
return
|
|
186
|
+
private func normalized(_ value: String?) -> String? {
|
|
187
|
+
guard let value, !value.isEmpty else {
|
|
188
|
+
return nil
|
|
157
189
|
}
|
|
158
|
-
|
|
159
|
-
call.resolve(payload)
|
|
160
|
-
bridge?.releaseCall(withID: pendingCallId)
|
|
190
|
+
return value
|
|
161
191
|
}
|
|
162
192
|
|
|
163
|
-
private func
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
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",
|