Skip to content

Commit

Permalink
Merge pull request #426 from radarlabs/token-handling
Browse files Browse the repository at this point in the history
make desiredAccuracy configurable, avoid double request, add clearVerifiedLocationToken()
  • Loading branch information
nickpatrick authored Jan 14, 2025
2 parents cff00be + 161e4ca commit 07f2210
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 54 deletions.
2 changes: 1 addition & 1 deletion sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ apply plugin: "org.jetbrains.dokka"
apply plugin: 'io.radar.mvnpublish'

ext {
radarVersion = '3.18.11'
radarVersion = '3.19.0'
}

String buildNumber = ".${System.currentTimeMillis()}"
Expand Down
98 changes: 91 additions & 7 deletions sdk/src/main/java/io/radar/sdk/Radar.kt
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,36 @@ object Radar {
})
}

/**
* Tracks the user's location with device integrity information for location verification use cases.
*
* Note that you must configure SSL pinning before calling this method.
*
* @see [](https://radar.com/documentation/fraud)
*
* @param[callback] An optional callback.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun trackVerified(callback: RadarTrackVerifiedCallback? = null) {
trackVerified(false, RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, callback)
}

/**
* Tracks the user's location with device integrity information for location verification use cases.
*
* Note that you must configure SSL pinning before calling this method.
*
* @see [](https://radar.com/documentation/fraud)
*
* @param[block] A block callback.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun trackVerified(block: (status: RadarStatus, token: RadarVerifiedLocationToken?) -> Unit) {
trackVerified(false, RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, block)
}

/**
* Tracks the user's location with device integrity information for location verification use cases.
*
Expand All @@ -1004,7 +1034,7 @@ object Radar {
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun trackVerified(beacons: Boolean = false, callback: RadarTrackVerifiedCallback? = null) {
fun trackVerified(beacons: Boolean = false, desiredAccuracy: RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy = RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, callback: RadarTrackVerifiedCallback? = null) {
if (!initialized) {
callback?.onComplete(RadarStatus.ERROR_PUBLISHABLE_KEY)

Expand All @@ -1016,7 +1046,7 @@ object Radar {
this.verificationManager = RadarVerificationManager(this.context, this.logger)
}

this.verificationManager.trackVerified(beacons, callback)
this.verificationManager.trackVerified(beacons, desiredAccuracy, callback)
}

/**
Expand All @@ -1031,8 +1061,8 @@ object Radar {
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun trackVerified(beacons: Boolean = false, block: (status: RadarStatus, token: RadarVerifiedLocationToken?) -> Unit) {
trackVerified(beacons, object : RadarTrackVerifiedCallback {
fun trackVerified(beacons: Boolean = false, desiredAccuracy: RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy = RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, block: (status: RadarStatus, token: RadarVerifiedLocationToken?) -> Unit) {
trackVerified(beacons, desiredAccuracy, object : RadarTrackVerifiedCallback {
override fun onComplete(status: RadarStatus, token: RadarVerifiedLocationToken?) {
block(status, token)
}
Expand Down Expand Up @@ -1096,6 +1126,38 @@ object Radar {
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun getVerifiedLocationToken(callback: RadarTrackVerifiedCallback? = null) {
getVerifiedLocationToken(false, RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, callback)
}

/**
* Returns the user's last verified location token if still valid, or requests a fresh token if not.
*
* Note that you must configure SSL pinning before calling this method.
*
* @see [](https://radar.com/documentation/fraud)
*
* @param[block] A block callback.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun getVerifiedLocationToken(block: (status: RadarStatus, token: RadarVerifiedLocationToken?) -> Unit) {
getVerifiedLocationToken(false, RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, block)
}

/**
* Returns the user's last verified location token if still valid, or requests a fresh token if not.
*
* Note that you must configure SSL pinning before calling this method.
*
* @see [](https://radar.com/documentation/fraud)
*
* @param[beacons] A boolean indicating whether to range beacons.
* @param[desiredAccuracy] The desired accuracy.
* @param[callback] An optional callback.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun getVerifiedLocationToken(beacons: Boolean = false, desiredAccuracy: RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy = RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, callback: RadarTrackVerifiedCallback? = null) {
if (!initialized) {
callback?.onComplete(RadarStatus.ERROR_PUBLISHABLE_KEY)

Expand All @@ -1107,7 +1169,7 @@ object Radar {
this.verificationManager = RadarVerificationManager(this.context, this.logger)
}

this.verificationManager.getVerifiedLocationToken(callback)
this.verificationManager.getVerifiedLocationToken(beacons, desiredAccuracy, callback)
}

/**
Expand All @@ -1117,18 +1179,40 @@ object Radar {
*
* @see [](https://radar.com/documentation/fraud)
*
* @param[beacons] A boolean indicating whether to range beacons.
* @param[desiredAccuracy] The desired accuracy.
* @param[block] A block callback.
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun getVerifiedLocationToken(block: (status: RadarStatus, token: RadarVerifiedLocationToken?) -> Unit) {
getVerifiedLocationToken(object : RadarTrackVerifiedCallback {
fun getVerifiedLocationToken(beacons: Boolean = false, desiredAccuracy: RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy = RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, block: (status: RadarStatus, token: RadarVerifiedLocationToken?) -> Unit) {
getVerifiedLocationToken(beacons, desiredAccuracy, object : RadarTrackVerifiedCallback {
override fun onComplete(status: RadarStatus, token: RadarVerifiedLocationToken?) {
block(status, token)
}
})
}

/**
* Clears the user's last verified location token.
*
* @see [](https://radar.com/documentation/fraud)
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@JvmStatic
fun clearVerifiedLocationToken() {
if (!initialized) {
return
}
this.logger.i("clearVerifiedLocationToken()", RadarLogType.SDK_CALL)

if (!this::verificationManager.isInitialized) {
this.verificationManager = RadarVerificationManager(this.context, this.logger)
}

this.verificationManager.clearVerifiedLocationToken()
}

/**
* Optionally sets the user's expected country and state for jurisdiction checks.
*
Expand Down
112 changes: 66 additions & 46 deletions sdk/src/main/java/io/radar/sdk/RadarVerificationManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ internal class RadarVerificationManager(
}
}

fun trackVerified(beacons: Boolean = false, callback: Radar.RadarTrackVerifiedCallback? = null) {
fun trackVerified(beacons: Boolean = false, desiredAccuracy: RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy = RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.MEDIUM, callback: Radar.RadarTrackVerifiedCallback? = null) {
val verificationManager = this
val lastTokenBeacons = beacons

Expand All @@ -84,7 +84,7 @@ internal class RadarVerificationManager(
val googlePlayProjectNumber = config.googlePlayProjectNumber

Radar.locationManager.getLocation(
RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.HIGH,
desiredAccuracy,
Radar.RadarLocationSource.FOREGROUND_LOCATION,
object :
Radar.RadarLocationCallback {
Expand Down Expand Up @@ -239,48 +239,57 @@ internal class RadarVerificationManager(
return
}

verificationManager.trackVerified(verificationManager.startedBeacons, object : Radar.RadarTrackVerifiedCallback {
verificationManager.trackVerified(this.startedBeacons, RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.HIGH, object : Radar.RadarTrackVerifiedCallback {
override fun onComplete(
status: Radar.RadarStatus,
token: RadarVerifiedLocationToken?
) {
var expiresIn = 0
var minInterval: Int = verificationManager.startedInterval
verificationManager.scheduleNextIntervalWithLastToken()
}
})
}

token?.let {
expiresIn = it.expiresIn
fun scheduleNextIntervalWithLastToken() {
val verificationManager = this

// if expiresIn is shorter than interval, override interval
// re-request early to maximize the likelihood that a cached token is available
minInterval = minOf(it.expiresIn - 10, verificationManager.startedInterval)
}
var minInterval: Int = verificationManager.startedInterval

// min interval is 10 seconds
if (minInterval < 10) {
minInterval = 10;
}
this.lastToken?.let {
val lastTokenElapsed = (SystemClock.elapsedRealtime() - this.lastTokenElapsedRealtime).toInt() / 1000

if (runnable == null) {
runnable = Runnable {
verificationManager.logger.d("Token request interval fired")
// if expiresIn is shorter than interval, override interval
// re-request early to maximize the likelihood that a cached token is available
minInterval = minOf(it.expiresIn - lastTokenElapsed, verificationManager.startedInterval)

callTrackVerified()
}
}
verificationManager.logger.d("Calculated next interval | minInterval = $minInterval; expiresIn = ${it.expiresIn}; lastTokenElapsed = $lastTokenElapsed; startedInterval = ${verificationManager.startedInterval}")
}

runnable?.let {
handler.removeCallbacks(it)
var interval = minInterval - 10

if (!verificationManager.started) {
return
}
// min interval is 10 seconds
if (interval < 10) {
interval = 10
}

verificationManager.logger.d("Requesting token again in $minInterval seconds | minInterval = $minInterval; expiresIn = $expiresIn; interval = ${verificationManager.startedInterval}")
if (runnable == null) {
runnable = Runnable {
verificationManager.logger.d("Token request interval fired")

handler.postDelayed(it, minInterval * 1000L)
}
callTrackVerified()
}
})
}

runnable?.let {
handler.removeCallbacks(it)

if (!verificationManager.started) {
return
}

verificationManager.logger.d("Requesting token again in $interval seconds")

handler.postDelayed(it, interval * 1000L)
}
}

fun startTrackingVerified(interval: Int, beacons: Boolean) {
Expand Down Expand Up @@ -345,7 +354,11 @@ internal class RadarVerificationManager(
Radar.locationManager.locationClient.requestLocationUpdates(RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy.HIGH, 0, 0, RadarLocationReceiver.getVerifiedLocationPendingIntent(context))
}

callTrackVerified()
if (this.isLastTokenValid()) {
this.scheduleNextIntervalWithLastToken()
} else {
callTrackVerified()
}
}

fun stopTrackingVerified() {
Expand All @@ -368,30 +381,37 @@ internal class RadarVerificationManager(
}
}

fun getVerifiedLocationToken(callback: Radar.RadarTrackVerifiedCallback? = null) {
val lastTokenElapsed = (SystemClock.elapsedRealtime() - this.lastTokenElapsedRealtime) / 1000
fun getVerifiedLocationToken(beacons: Boolean, desiredAccuracy: RadarTrackingOptions.RadarTrackingOptionsDesiredAccuracy, callback: Radar.RadarTrackVerifiedCallback? = null) {
if (this.isLastTokenValid()) {
Radar.flushLogs()

if (this.lastToken != null) {
this.lastToken?.let {
val lastDistanceToStateBorder = it.user.state?.distanceToBorder ?: -1.0
callback?.onComplete(Radar.RadarStatus.SUCCESS, this.lastToken)

if (lastTokenElapsed < it.expiresIn && it.passed && lastDistanceToStateBorder > 1609) {
Radar.logger.d("Last token valid | lastToken.expiresIn = ${it.expiresIn}; lastTokenElapsed = $lastTokenElapsed; lastToken.passed = ${it.passed}; lastDistanceToStateBorder = $lastDistanceToStateBorder")
return
}

Radar.flushLogs()
this.trackVerified(beacons, desiredAccuracy, callback)
}

callback?.onComplete(Radar.RadarStatus.SUCCESS, it)
fun clearVerifiedLocationToken() {
this.lastToken = null
}

return
}
fun isLastTokenValid(): Boolean {
val lastToken = this.lastToken ?: return false

Radar.logger.d("Last token invalid | lastToken.expiresIn = ${it.expiresIn}; lastTokenElapsed = $lastTokenElapsed; lastToken.passed = ${it.passed}; lastDistanceToStateBorder = $lastDistanceToStateBorder")
}
val lastTokenElapsed = (SystemClock.elapsedRealtime() - this.lastTokenElapsedRealtime) / 1000
val lastDistanceToStateBorder = lastToken.user.state?.distanceToBorder ?: -1.0

val lastTokenValid = lastTokenElapsed < lastToken.expiresIn && lastToken.passed && lastDistanceToStateBorder > 1609

if (lastTokenValid) {
Radar.logger.d("Last token valid | lastToken.expiresIn = ${lastToken.expiresIn}; lastTokenElapsed = $lastTokenElapsed; lastToken.passed = ${lastToken.passed}; lastDistanceToStateBorder = $lastDistanceToStateBorder")
} else {
Radar.logger.d("No last token")
Radar.logger.d("Last token invalid | lastToken.expiresIn = ${lastToken.expiresIn}; lastTokenElapsed = $lastTokenElapsed; lastToken.passed = ${lastToken.passed}; lastDistanceToStateBorder = $lastDistanceToStateBorder")
}

this.trackVerified(this.lastTokenBeacons, callback)
return lastTokenValid
}

fun setExpectedJurisdiction(countryCode: String?, stateCode: String?) {
Expand Down

0 comments on commit 07f2210

Please # to comment.