Skip to content

Commit

Permalink
Merge pull request #81 from fingerprintjs/add-timeout-argument
Browse files Browse the repository at this point in the history
Add `timeoutMs` argument for `getVisitorId` and `getVisitorData` methods
  • Loading branch information
ilfa authored Dec 2, 2024
2 parents 454392d + 822504a commit 4bc4cc1
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.fingerprintjs.android.fpjs_pro.UnsupportedVersion
import com.fingerprintjs.android.fpjs_pro.InstallationMethodRestricted
import com.fingerprintjs.android.fpjs_pro.ResponseCannotBeParsed
import com.fingerprintjs.android.fpjs_pro.NetworkError
import com.fingerprintjs.android.fpjs_pro.ClientTimeout
import com.fingerprintjs.android.fpjs_pro.UnknownError

import io.flutter.embedding.engine.plugins.FlutterPlugin
Expand Down Expand Up @@ -70,7 +71,8 @@ class FpjsProPlugin: FlutterPlugin, MethodCallHandler {
GET_VISITOR_ID -> {
val tags = call.argument<Map<String, Any>>("tags") ?: emptyMap()
val linkedId = call.argument<String>("linkedId") ?: ""
getVisitorId(linkedId, tags, { visitorId ->
val timeoutMillis = call.argument<Int>("timeoutMs")
getVisitorId(timeoutMillis, linkedId, tags, { visitorId ->
result.success(visitorId)
}, { errorCode, errorMessage ->
result.error(errorCode, errorMessage, null)
Expand All @@ -79,7 +81,8 @@ class FpjsProPlugin: FlutterPlugin, MethodCallHandler {
GET_VISITOR_DATA -> {
val tags = call.argument<Map<String, Any>>("tags") ?: emptyMap()
val linkedId = call.argument<String>("linkedId") ?: ""
getVisitorData(linkedId, tags, { getVisitorData ->
val timeoutMillis = call.argument<Int>("timeoutMs")
getVisitorData(timeoutMillis, linkedId, tags, { getVisitorData ->
result.success(getVisitorData)
}, { errorCode, errorMessage ->
result.error(errorCode, errorMessage, null)
Expand Down Expand Up @@ -110,31 +113,53 @@ class FpjsProPlugin: FlutterPlugin, MethodCallHandler {
}

private fun getVisitorId(
timeoutMillis: Int?,
linkedId: String,
tags: Map<String, Any>,
listener: (String) -> Unit,
errorListener: (String, String) -> (Unit)
) {
fpjsClient.getVisitorId(
tags,
linkedId,
listener = {result -> listener(result.visitorId)},
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
)
if (timeoutMillis != null) {
fpjsClient.getVisitorId(
timeoutMillis,
tags,
linkedId,
listener = { result -> listener(result.visitorId) },
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString()) }
)
} else {
fpjsClient.getVisitorId(
tags,
linkedId,
listener = { result -> listener(result.visitorId) },
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString()) }
)
}
}

private fun getVisitorData(
timeoutMillis: Int?,
linkedId: String,
tags: Map<String, Any>,
listener: (List<Any>) -> Unit,
errorListener: (String, String) -> (Unit)
) {
fpjsClient.getVisitorId(
tags,
linkedId,
listener = {result -> listener(listOf(result.requestId, result.confidenceScore.score, result.asJson, result.sealedResult ?: ""))},
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
)
if (timeoutMillis != null) {
fpjsClient.getVisitorId(
timeoutMillis,
tags,
linkedId,
listener = {result -> listener(listOf(result.requestId, result.confidenceScore.score, result.asJson, result.sealedResult ?: ""))},
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
)
} else {
fpjsClient.getVisitorId(
tags,
linkedId,
listener = {result -> listener(listOf(result.requestId, result.confidenceScore.score, result.asJson, result.sealedResult ?: ""))},
errorListener = { error -> errorListener(getErrorCode(error), error.description.toString())}
)
}
}
}

Expand Down Expand Up @@ -170,6 +195,7 @@ private fun getErrorCode(error: Error): String {
is InstallationMethodRestricted -> "InstallationMethodRestricted"
is ResponseCannotBeParsed -> "ResponseCannotBeParsed"
is NetworkError -> "NetworkError"
is ClientTimeout -> "ClientTimeout"
else -> "UnknownError"
}
return errorType
Expand Down
17 changes: 17 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,13 @@ class _MyAppState extends State<MyApp> {
FpjsProPlugin.getVisitorId(linkedId: 'checkIdWithTag', tags: tags),
() async => FpjsProPlugin.getVisitorData(
linkedId: 'checkDataWithTag', tags: tags),
() async => FpjsProPlugin.getVisitorId(timeoutMs: 5000),
() async => FpjsProPlugin.getVisitorData(timeoutMs: 5000),
];

var timeoutChecks = [
() async => FpjsProPlugin.getVisitorId(timeoutMs: 5),
() async => FpjsProPlugin.getVisitorData(timeoutMs: 5)
];

for (var check in checks) {
Expand All @@ -144,6 +151,16 @@ class _MyAppState extends State<MyApp> {
_checksResult += '.';
});
}
for (var check in timeoutChecks) {
try {
await check();
throw Exception('Expected timeout error');
} on FingerprintProError {
setState(() {
_checksResult += '!';
});
}
}
setState(() {
_checksResult = 'Success!';
});
Expand Down
2 changes: 2 additions & 0 deletions ios/Classes/FPJSError+Flutter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extension FPJSError {
return ("JsonParsingError", jsonParsingError.localizedDescription)
case .invalidResponseType:
return ("InvalidResponseType", description)
case .clientTimeout:
return ("ClientTimeout", description)
case .unknownError:
fallthrough
@unknown default:
Expand Down
24 changes: 18 additions & 6 deletions ios/Classes/SwiftFpjsProPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public class SwiftFpjsProPlugin: NSObject, FlutterPlugin {
}
} else if (call.method == "getVisitorId") {
let metadata = prepareMetadata(args["linkedId"] as? String, tags: args["tags"])
getVisitorId(metadata, result)
getVisitorId(metadata, result, args["timeoutMs"] as? Double)
} else if (call.method == "getVisitorData") {
let metadata = prepareMetadata(args["linkedId"] as? String, tags: args["tags"])
getVisitorData(metadata, result)
getVisitorData(metadata, result, args["timeoutMs"] as? Double)
}
}

Expand Down Expand Up @@ -79,29 +79,35 @@ public class SwiftFpjsProPlugin: NSObject, FlutterPlugin {
fpjsClient = FingerprintProFactory.getInstance(configuration)
}

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

client.getVisitorId(metadata) { visitorIdResult in
let completionHandler: FingerprintPro.VisitorIdBlock = { visitorIdResult in
switch visitorIdResult {
case .success(let visitorId):
result(visitorId)
case .failure(let error):
self.processNativeLibraryError(error, result: result)
}
}

if let timeout = timeout {
client.getVisitorId(metadata, timeout: timeout / 1000, completion: completionHandler)
} else {
client.getVisitorId(metadata, completion: completionHandler)
}
}

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

client.getVisitorIdResponse(metadata) { visitorIdResponseResult in
let completionHandler: FingerprintPro.VisitorIdResponseBlock = { visitorIdResponseResult in
switch visitorIdResponseResult {
case .success(let visitorDataResponse):
result([
Expand All @@ -114,6 +120,12 @@ public class SwiftFpjsProPlugin: NSObject, FlutterPlugin {
self.processNativeLibraryError(error, result: result)
}
}

if let timeout = timeout {
client.getVisitorIdResponse(metadata, timeout: timeout / 1000, completion: completionHandler)
} else {
client.getVisitorIdResponse(metadata, completion: completionHandler)
}
}

private func processNativeLibraryError(_ error: FPJSError, result: @escaping FlutterResult) {
Expand Down
8 changes: 8 additions & 0 deletions lib/error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ class UnknownError extends FingerprintProError {
UnknownError(String? message) : super('UnknownError', message);
}

/// ClientTimeout error
class ClientTimeoutError extends FingerprintProError {
ClientTimeoutError(String? message) : super('ClientTimeoutError', message);
}

/// Casts error from generic platform type to FingerprintProError
FingerprintProError unwrapError(PlatformException error) {
switch (error.code) {
Expand Down Expand Up @@ -238,6 +243,9 @@ FingerprintProError unwrapError(PlatformException error) {
return CspBlockError(error.message);
case 'IntegrationFailureError':
return IntegrationFailureError(error.message);
case 'ClientTimeout':
case 'ClientTimeoutError':
return ClientTimeoutError(error.message);
default:
return UnknownError(error.message);
}
Expand Down
22 changes: 12 additions & 10 deletions lib/fpjs_pro_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,41 @@ class FpjsProPlugin {
}

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

try {
final String? visitorId = await _channel
.invokeMethod('getVisitorId', {'linkedId': linkedId, 'tags': tags});
final String? visitorId = await _channel.invokeMethod('getVisitorId',
{'linkedId': linkedId, 'tags': tags, 'timeoutMs': timeoutMs});
return visitorId;
} on PlatformException catch (exception) {
throw unwrapError(exception);
}
}

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

try {
final visitorDataTuple = await _channel
.invokeMethod('getVisitorData', {'linkedId': linkedId, 'tags': tags});
final visitorDataTuple = await _channel.invokeMethod('getVisitorData',
{'linkedId': linkedId, 'tags': tags, 'timeoutMs': timeoutMs});

final String requestId = visitorDataTuple[0];
final num confidence = visitorDataTuple[1];
Expand Down
22 changes: 15 additions & 7 deletions lib/fpjs_pro_plugin_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ class FpjsProPluginWeb {
case 'getVisitorId':
return getVisitorId(
linkedId: call.arguments['linkedId'],
tags: getTags(call.arguments['tags']));
tags: getTags(call.arguments['tags']),
timeoutMs: call.arguments['timeoutMs']);
case 'getVisitorData':
return getVisitorData(
linkedId: call.arguments['linkedId'],
tags: getTags(call.arguments['tags']));
tags: getTags(call.arguments['tags']),
timeoutMs: call.arguments['timeoutMs']);
default:
throw PlatformException(
code: 'Unimplemented',
Expand Down Expand Up @@ -96,17 +98,19 @@ class FpjsProPluginWeb {
/// Returns the visitorId generated by the native Fingerprint Pro client
/// Support [tags](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
/// Support [linkedId](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
/// Support [timeoutMs](https://dev.fingerprint.com/reference/get-function#timeout)
/// Throws a [FingerprintProError] if identification request fails for any reason
static Future<String?> getVisitorId({Object? tags, String? linkedId}) async {
static Future<String?> getVisitorId(
{Object? tags, String? linkedId, int? timeoutMs}) async {
if (!_isInitialized) {
throw Exception(
'You need to initialize the FPJS Client first by calling the "initFpjs" method');
}

try {
FingerprintJSAgent fp = await (_fpPromise as Future<FingerprintJSAgent>);
var result = await promiseToFuture(
fp.get(FingerprintJSGetOptions(linkedId: linkedId, tag: tags)));
var result = await promiseToFuture(fp.get(FingerprintJSGetOptions(
linkedId: linkedId, tag: tags, timeout: timeoutMs)));
return result.visitorId;
} catch (e) {
if (e is WebException) {
Expand All @@ -120,17 +124,21 @@ class FpjsProPluginWeb {
/// Returns the visitor data generated by the native Fingerprint Pro client
/// Support [tags](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
/// Support [linkedId](https://dev.fingerprint.com/docs/quick-start-guide#tagging-your-requests)
/// Support [timeoutMs](https://dev.fingerprint.com/reference/get-function#timeout)
/// Throws a [FingerprintProError] if identification request fails for any reason
static Future<List<Object>> getVisitorData(
{Object? tags, String? linkedId}) async {
{Object? tags, String? linkedId, int? timeoutMs}) async {
if (!_isInitialized) {
throw Exception(
'You need to initialize the FPJS Client first by calling the "initFpjs" method');
}
try {
FingerprintJSAgent fp = await (_fpPromise as Future<FingerprintJSAgent>);
final getOptions = FingerprintJSGetOptions(
linkedId: linkedId, tag: tags, extendedResult: _isExtendedResult);
linkedId: linkedId,
tag: tags,
timeout: timeoutMs,
extendedResult: _isExtendedResult);
final IdentificationResult result =
await promiseToFuture(fp.get(getOptions));

Expand Down
10 changes: 9 additions & 1 deletion lib/js_agent_interop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,16 @@ class FingerprintJSGetOptions {
/// Adds details about the visitor to the result
external bool extendedResult;

/// Controls client-side timeout. Client timeout controls total time (both client-side and server-side) that any
/// identification event is allowed to run. It doesn't include time when the page is in background (not visible).
/// The value is in milliseconds.
external int? get timeout;

external factory FingerprintJSGetOptions(
{Object? tag, String? linkedId, bool extendedResult = false});
{Object? tag,
String? linkedId,
int? timeout,
bool extendedResult = false});
}

/// Interop for JS Agent exceptions
Expand Down
2 changes: 1 addition & 1 deletion lib/web_error.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ FingerprintProError unwrapWebError(WebException error) {
return FailedError(message);
}
if (message == FingerprintJS.ERROR_CLIENT_TIMEOUT) {
return RequestTimeoutError(message);
return ClientTimeoutError(message);
}
if (message == FingerprintJS.ERROR_SERVER_TIMEOUT) {
return RequestTimeoutError(message);
Expand Down
Loading

0 comments on commit 4bc4cc1

Please sign in to comment.