Skip to content

Commit 6fad159

Browse files
committed
feat: add timeoutMs argument for getVisitorId and getVisitorData methods
1 parent 454392d commit 6fad159

File tree

10 files changed

+136
-41
lines changed

10 files changed

+136
-41
lines changed

android/src/main/kotlin/com/fingerprintjs/flutter/fpjs_pro/fpjs_pro_plugin/FpjsProPlugin.kt

+40-14
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.fingerprintjs.android.fpjs_pro.UnsupportedVersion
2424
import com.fingerprintjs.android.fpjs_pro.InstallationMethodRestricted
2525
import com.fingerprintjs.android.fpjs_pro.ResponseCannotBeParsed
2626
import com.fingerprintjs.android.fpjs_pro.NetworkError
27+
import com.fingerprintjs.android.fpjs_pro.ClientTimeout
2728
import com.fingerprintjs.android.fpjs_pro.UnknownError
2829

2930
import io.flutter.embedding.engine.plugins.FlutterPlugin
@@ -70,7 +71,8 @@ class FpjsProPlugin: FlutterPlugin, MethodCallHandler {
7071
GET_VISITOR_ID -> {
7172
val tags = call.argument<Map<String, Any>>("tags") ?: emptyMap()
7273
val linkedId = call.argument<String>("linkedId") ?: ""
73-
getVisitorId(linkedId, tags, { visitorId ->
74+
val timeoutMillis = call.argument<Int>("timeoutMs")
75+
getVisitorId(timeoutMillis, linkedId, tags, { visitorId ->
7476
result.success(visitorId)
7577
}, { errorCode, errorMessage ->
7678
result.error(errorCode, errorMessage, null)
@@ -79,7 +81,8 @@ class FpjsProPlugin: FlutterPlugin, MethodCallHandler {
7981
GET_VISITOR_DATA -> {
8082
val tags = call.argument<Map<String, Any>>("tags") ?: emptyMap()
8183
val linkedId = call.argument<String>("linkedId") ?: ""
82-
getVisitorData(linkedId, tags, { getVisitorData ->
84+
val timeoutMillis = call.argument<Int>("timeoutMs")
85+
getVisitorData(timeoutMillis, linkedId, tags, { getVisitorData ->
8386
result.success(getVisitorData)
8487
}, { errorCode, errorMessage ->
8588
result.error(errorCode, errorMessage, null)
@@ -110,31 +113,53 @@ class FpjsProPlugin: FlutterPlugin, MethodCallHandler {
110113
}
111114

112115
private fun getVisitorId(
116+
timeoutMillis: Int?,
113117
linkedId: String,
114118
tags: Map<String, Any>,
115119
listener: (String) -> Unit,
116120
errorListener: (String, String) -> (Unit)
117121
) {
118-
fpjsClient.getVisitorId(
119-
tags,
120-
linkedId,
121-
listener = {result -> listener(result.visitorId)},
122-
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
123-
)
122+
if (timeoutMillis != null) {
123+
fpjsClient.getVisitorId(
124+
timeoutMillis,
125+
tags,
126+
linkedId,
127+
listener = { result -> listener(result.visitorId) },
128+
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString()) }
129+
)
130+
} else {
131+
fpjsClient.getVisitorId(
132+
tags,
133+
linkedId,
134+
listener = { result -> listener(result.visitorId) },
135+
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString()) }
136+
)
137+
}
124138
}
125139

126140
private fun getVisitorData(
141+
timeoutMillis: Int?,
127142
linkedId: String,
128143
tags: Map<String, Any>,
129144
listener: (List<Any>) -> Unit,
130145
errorListener: (String, String) -> (Unit)
131146
) {
132-
fpjsClient.getVisitorId(
133-
tags,
134-
linkedId,
135-
listener = {result -> listener(listOf(result.requestId, result.confidenceScore.score, result.asJson, result.sealedResult ?: ""))},
136-
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
137-
)
147+
if (timeoutMillis != null) {
148+
fpjsClient.getVisitorId(
149+
timeoutMillis,
150+
tags,
151+
linkedId,
152+
listener = {result -> listener(listOf(result.requestId, result.confidenceScore.score, result.asJson, result.sealedResult ?: ""))},
153+
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
154+
)
155+
} else {
156+
fpjsClient.getVisitorId(
157+
tags,
158+
linkedId,
159+
listener = {result -> listener(listOf(result.requestId, result.confidenceScore.score, result.asJson, result.sealedResult ?: ""))},
160+
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
161+
)
162+
}
138163
}
139164
}
140165

@@ -170,6 +195,7 @@ private fun getErrorCode(error: Error): String {
170195
is InstallationMethodRestricted -> "InstallationMethodRestricted"
171196
is ResponseCannotBeParsed -> "ResponseCannotBeParsed"
172197
is NetworkError -> "NetworkError"
198+
is ClientTimeout -> "ClientTimeout"
173199
else -> "UnknownError"
174200
}
175201
return errorType

example/lib/main.dart

+17
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ class _MyAppState extends State<MyApp> {
136136
FpjsProPlugin.getVisitorId(linkedId: 'checkIdWithTag', tags: tags),
137137
() async => FpjsProPlugin.getVisitorData(
138138
linkedId: 'checkDataWithTag', tags: tags),
139+
() async => FpjsProPlugin.getVisitorId(timeoutMs: 5000),
140+
() async => FpjsProPlugin.getVisitorData(timeoutMs: 5000),
141+
];
142+
143+
var timeoutChecks = [
144+
() async => FpjsProPlugin.getVisitorId(timeoutMs: 5),
145+
() async => FpjsProPlugin.getVisitorData(timeoutMs: 5)
139146
];
140147

141148
for (var check in checks) {
@@ -144,6 +151,16 @@ class _MyAppState extends State<MyApp> {
144151
_checksResult += '.';
145152
});
146153
}
154+
for (var check in timeoutChecks) {
155+
try {
156+
await check();
157+
throw Exception('Expected timeout error');
158+
} on FingerprintProError catch (e) {
159+
setState(() {
160+
_checksResult += '!';
161+
});
162+
}
163+
}
147164
setState(() {
148165
_checksResult = 'Success!';
149166
});

ios/Classes/FPJSError+Flutter.swift

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ extension FPJSError {
1919
return ("JsonParsingError", jsonParsingError.localizedDescription)
2020
case .invalidResponseType:
2121
return ("InvalidResponseType", description)
22+
case .clientTimeout:
23+
return ("ClientTimeout", description)
2224
case .unknownError:
2325
fallthrough
2426
@unknown default:

ios/Classes/SwiftFpjsProPlugin.swift

+18-6
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ public class SwiftFpjsProPlugin: NSObject, FlutterPlugin {
3030
}
3131
} else if (call.method == "getVisitorId") {
3232
let metadata = prepareMetadata(args["linkedId"] as? String, tags: args["tags"])
33-
getVisitorId(metadata, result)
33+
getVisitorId(metadata, result, args["timeoutMs"] as? Double)
3434
} else if (call.method == "getVisitorData") {
3535
let metadata = prepareMetadata(args["linkedId"] as? String, tags: args["tags"])
36-
getVisitorData(metadata, result)
36+
getVisitorData(metadata, result, args["timeoutMs"] as? Double)
3737
}
3838
}
3939

@@ -79,29 +79,35 @@ public class SwiftFpjsProPlugin: NSObject, FlutterPlugin {
7979
fpjsClient = FingerprintProFactory.getInstance(configuration)
8080
}
8181

82-
private func getVisitorId(_ metadata: Metadata?, _ result: @escaping FlutterResult) {
82+
private func getVisitorId(_ metadata: Metadata?, _ result: @escaping FlutterResult, _ timeout: Double? = nil) {
8383
guard let client = fpjsClient else {
8484
result(FlutterError.init(code: "undefinedFpClient", message: "You need to call init method first", details: nil))
8585
return
8686
}
8787

88-
client.getVisitorId(metadata) { visitorIdResult in
88+
let completionHandler: FingerprintPro.VisitorIdBlock = { visitorIdResult in
8989
switch visitorIdResult {
9090
case .success(let visitorId):
9191
result(visitorId)
9292
case .failure(let error):
9393
self.processNativeLibraryError(error, result: result)
9494
}
9595
}
96+
97+
if let timeout = timeout {
98+
client.getVisitorId(metadata, timeout: timeout / 1000, completion: completionHandler)
99+
} else {
100+
client.getVisitorId(metadata, completion: completionHandler)
101+
}
96102
}
97103

98-
private func getVisitorData(_ metadata: Metadata?, _ result: @escaping FlutterResult) {
104+
private func getVisitorData(_ metadata: Metadata?, _ result: @escaping FlutterResult, _ timeout: Double? = nil) {
99105
guard let client = fpjsClient else {
100106
result(FlutterError(code: "undefinedFpClient", message: "You need to call init method first", details: nil))
101107
return
102108
}
103109

104-
client.getVisitorIdResponse(metadata) { visitorIdResponseResult in
110+
let completionHandler: FingerprintPro.VisitorIdResponseBlock = { visitorIdResponseResult in
105111
switch visitorIdResponseResult {
106112
case .success(let visitorDataResponse):
107113
result([
@@ -114,6 +120,12 @@ public class SwiftFpjsProPlugin: NSObject, FlutterPlugin {
114120
self.processNativeLibraryError(error, result: result)
115121
}
116122
}
123+
124+
if let timeout = timeout {
125+
client.getVisitorIdResponse(metadata, timeout: timeout / 1000, completion: completionHandler)
126+
} else {
127+
client.getVisitorIdResponse(metadata, completion: completionHandler)
128+
}
117129
}
118130

119131
private func processNativeLibraryError(_ error: FPJSError, result: @escaping FlutterResult) {

lib/error.dart

+8
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ class UnknownError extends FingerprintProError {
158158
UnknownError(String? message) : super('UnknownError', message);
159159
}
160160

161+
/// ClientTimeout error
162+
class ClientTimeoutError extends FingerprintProError {
163+
ClientTimeoutError(String? message) : super('ClientTimeoutError', message);
164+
}
165+
161166
/// Casts error from generic platform type to FingerprintProError
162167
FingerprintProError unwrapError(PlatformException error) {
163168
switch (error.code) {
@@ -238,6 +243,9 @@ FingerprintProError unwrapError(PlatformException error) {
238243
return CspBlockError(error.message);
239244
case 'IntegrationFailureError':
240245
return IntegrationFailureError(error.message);
246+
case 'ClientTimeout':
247+
case 'ClientTimeoutError':
248+
return ClientTimeoutError(error.message);
241249
default:
242250
return UnknownError(error.message);
243251
}

lib/fpjs_pro_plugin.dart

+12-10
Original file line numberDiff line numberDiff line change
@@ -44,39 +44,41 @@ class FpjsProPlugin {
4444
}
4545

4646
/// Returns the visitorId generated by the native Fingerprint Pro client
47-
/// Support [tags](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
48-
/// Support [linkedId](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
47+
/// Support [tags](https://dev.fingerprint.com/reference/get-function#tag)
48+
/// Support [linkedId](https://dev.fingerprint.com/reference/get-function#linkedid)
49+
/// Support [timeoutMs](https://dev.fingerprint.com/reference/get-function#timeout)
4950
/// Throws a [FingerprintProError] if identification request fails for any reason
5051
static Future<String?> getVisitorId(
51-
{Map<String, dynamic>? tags, String? linkedId}) async {
52+
{Map<String, dynamic>? tags, String? linkedId, int? timeoutMs}) async {
5253
if (!_isInitialized) {
5354
throw Exception(
5455
'You need to initialize the FPJS Client first by calling the "initFpjs" method');
5556
}
5657

5758
try {
58-
final String? visitorId = await _channel
59-
.invokeMethod('getVisitorId', {'linkedId': linkedId, 'tags': tags});
59+
final String? visitorId = await _channel.invokeMethod('getVisitorId',
60+
{'linkedId': linkedId, 'tags': tags, 'timeoutMs': timeoutMs});
6061
return visitorId;
6162
} on PlatformException catch (exception) {
6263
throw unwrapError(exception);
6364
}
6465
}
6566

6667
/// Returns the visitor data generated by the native Fingerprint Pro client
67-
/// Support [tags](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
68-
/// Support [linkedId](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
68+
/// Support [tags](https://dev.fingerprint.com/reference/get-function#tag)
69+
/// Support [linkedId](https://dev.fingerprint.com/reference/get-function#linkedid)
70+
/// Support [timeoutMs](https://dev.fingerprint.com/reference/get-function#timeout)
6971
/// Throws a [FingerprintProError] if identification request fails for any reason
7072
static Future<T> getVisitorData<T extends FingerprintJSProResponse>(
71-
{Map<String, dynamic>? tags, String? linkedId}) async {
73+
{Map<String, dynamic>? tags, String? linkedId, int? timeoutMs}) async {
7274
if (!_isInitialized) {
7375
throw Exception(
7476
'You need to initialize the FPJS Client first by calling the "initFpjs" method');
7577
}
7678

7779
try {
78-
final visitorDataTuple = await _channel
79-
.invokeMethod('getVisitorData', {'linkedId': linkedId, 'tags': tags});
80+
final visitorDataTuple = await _channel.invokeMethod('getVisitorData',
81+
{'linkedId': linkedId, 'tags': tags, 'timeoutMs': timeoutMs});
8082

8183
final String requestId = visitorDataTuple[0];
8284
final num confidence = visitorDataTuple[1];

lib/fpjs_pro_plugin_web.dart

+15-7
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@ class FpjsProPluginWeb {
4242
case 'getVisitorId':
4343
return getVisitorId(
4444
linkedId: call.arguments['linkedId'],
45-
tags: getTags(call.arguments['tags']));
45+
tags: getTags(call.arguments['tags']),
46+
timeoutMs: call.arguments['timeoutMs']);
4647
case 'getVisitorData':
4748
return getVisitorData(
4849
linkedId: call.arguments['linkedId'],
49-
tags: getTags(call.arguments['tags']));
50+
tags: getTags(call.arguments['tags']),
51+
timeoutMs: call.arguments['timeoutMs']);
5052
default:
5153
throw PlatformException(
5254
code: 'Unimplemented',
@@ -96,17 +98,19 @@ class FpjsProPluginWeb {
9698
/// Returns the visitorId generated by the native Fingerprint Pro client
9799
/// Support [tags](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
98100
/// Support [linkedId](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
101+
/// Support [timeoutMs](https://dev.fingerprint.com/reference/get-function#timeout)
99102
/// Throws a [FingerprintProError] if identification request fails for any reason
100-
static Future<String?> getVisitorId({Object? tags, String? linkedId}) async {
103+
static Future<String?> getVisitorId(
104+
{Object? tags, String? linkedId, int? timeoutMs}) async {
101105
if (!_isInitialized) {
102106
throw Exception(
103107
'You need to initialize the FPJS Client first by calling the "initFpjs" method');
104108
}
105109

106110
try {
107111
FingerprintJSAgent fp = await (_fpPromise as Future<FingerprintJSAgent>);
108-
var result = await promiseToFuture(
109-
fp.get(FingerprintJSGetOptions(linkedId: linkedId, tag: tags)));
112+
var result = await promiseToFuture(fp.get(FingerprintJSGetOptions(
113+
linkedId: linkedId, tag: tags, timeout: timeoutMs)));
110114
return result.visitorId;
111115
} catch (e) {
112116
if (e is WebException) {
@@ -120,17 +124,21 @@ class FpjsProPluginWeb {
120124
/// Returns the visitor data generated by the native Fingerprint Pro client
121125
/// Support [tags](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
122126
/// Support [linkedId](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
127+
/// Support [timeoutMs](https://dev.fingerprint.com/reference/get-function#timeout)
123128
/// Throws a [FingerprintProError] if identification request fails for any reason
124129
static Future<List<Object>> getVisitorData(
125-
{Object? tags, String? linkedId}) async {
130+
{Object? tags, String? linkedId, int? timeoutMs}) async {
126131
if (!_isInitialized) {
127132
throw Exception(
128133
'You need to initialize the FPJS Client first by calling the "initFpjs" method');
129134
}
130135
try {
131136
FingerprintJSAgent fp = await (_fpPromise as Future<FingerprintJSAgent>);
132137
final getOptions = FingerprintJSGetOptions(
133-
linkedId: linkedId, tag: tags, extendedResult: _isExtendedResult);
138+
linkedId: linkedId,
139+
tag: tags,
140+
timeout: timeoutMs,
141+
extendedResult: _isExtendedResult);
134142
final IdentificationResult result =
135143
await promiseToFuture(fp.get(getOptions));
136144

lib/js_agent_interop.dart

+9-1
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,16 @@ class FingerprintJSGetOptions {
251251
/// Adds details about the visitor to the result
252252
external bool extendedResult;
253253

254+
/// Controls client-side timeout. Client timeout controls total time (both client-side and server-side) that any
255+
/// identification event is allowed to run. It doesn't include time when the page is in background (not visible).
256+
/// The value is in milliseconds.
257+
external int? get timeout;
258+
254259
external factory FingerprintJSGetOptions(
255-
{Object? tag, String? linkedId, bool extendedResult = false});
260+
{Object? tag,
261+
String? linkedId,
262+
int? timeout,
263+
bool extendedResult = false});
256264
}
257265

258266
/// Interop for JS Agent exceptions

lib/web_error.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ FingerprintProError unwrapWebError(WebException error) {
2929
return FailedError(message);
3030
}
3131
if (message == FingerprintJS.ERROR_CLIENT_TIMEOUT) {
32-
return RequestTimeoutError(message);
32+
return ClientTimeoutError(message);
3333
}
3434
if (message == FingerprintJS.ERROR_SERVER_TIMEOUT) {
3535
return RequestTimeoutError(message);

0 commit comments

Comments
 (0)