Skip to content
This repository was archived by the owner on Aug 1, 2023. It is now read-only.

Commit f143b2d

Browse files
committed
Merge branch 'blocoio-dispatcher' into develop
2 parents 96130cf + 09b667b commit f143b2d

File tree

8 files changed

+123
-157
lines changed

8 files changed

+123
-157
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Unrelease
88
### Added
9+
- dynamic context in the main suspend methods
910
- error code for missing read url
1011

1112
## [0.6.0] - 2020-10-13

blockstack-sdk/src/main/java/org/blockstack/android/sdk/Blockstack.kt

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.util.Base64
44
import android.util.Log
55
import com.colendi.ecies.EncryptedResultForm
66
import com.colendi.ecies.Encryption
7+
import kotlinx.coroutines.CoroutineDispatcher
78
import kotlinx.coroutines.Dispatchers
89
import kotlinx.coroutines.withContext
910
import kotlinx.serialization.json.Json
@@ -44,7 +45,8 @@ import java.security.InvalidParameterException
4445
import java.text.SimpleDateFormat
4546
import java.util.*
4647

47-
class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
48+
class Blockstack(private val callFactory: Call.Factory = OkHttpClient(),
49+
val dispatcher: CoroutineDispatcher = Dispatchers.IO) {
4850

4951
private var btcAddrResolver: BitAddrResolver
5052

@@ -78,7 +80,7 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
7880
return makeAuthResponseToken(account, privateKeyPayload, scopes)
7981
}
8082

81-
private suspend fun makeAuthResponseToken(account: BlockstackAccount, privateKeyPayload: String?, scopes: Array<Scope>): String {
83+
private suspend fun makeAuthResponseToken(account: BlockstackAccount, privateKeyPayload: String?, scopes: Array<Scope>): String = withContext(dispatcher) {
8284
val username = account.username
8385
val profile = if (username != null) {
8486
lookupProfile(username, null)
@@ -114,7 +116,7 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
114116
"did:btc-addr:${account.keys.keyPair.toBtcAddress()}"
115117

116118
val jwt = JWTTools()
117-
return jwt.createJWT(payload, issuerDID, signer, algorithm = JwtHeader.ES256K)
119+
return@withContext jwt.createJWT(payload, issuerDID, signer, algorithm = JwtHeader.ES256K)
118120
}
119121

120122
/**
@@ -126,16 +128,15 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
126128
* blockstack.js [[getNameInfo]] function.
127129
* @returns {Promise} that resolves to a profile object
128130
*/
129-
suspend fun lookupProfile(username: String, zoneFileLookupURL: URL?): Profile {
131+
suspend fun lookupProfile(username: String, zoneFileLookupURL: URL?): Profile = withContext(dispatcher){
130132

131133
val request = buildLookupNameInfoRequest(username, zoneFileLookupURL?.toString())
132-
val response = withContext(Dispatchers.IO) {
133-
callFactory.newCall(request).execute()
134-
}
134+
val response = callFactory.newCall(request).execute()
135+
135136
if (response.isSuccessful) {
136137
val nameInfo = JSONObject(response.body!!.string())
137138
if (nameInfo.has("address") && nameInfo.has("zonefile")) {
138-
return resolveZoneFileToProfile(nameInfo) ?: return Profile(JSONObject())
139+
return@withContext resolveZoneFileToProfile(nameInfo) ?: return@withContext Profile(JSONObject())
139140

140141
} else {
141142
throw InvalidParameterException("name info does not contain address or zonefile property")
@@ -435,16 +436,16 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
435436
* @param appPrivateKey (String) the app private key used to generate the app address
436437
* @result the URL of the app index file or null if it fails
437438
*/
438-
suspend fun getAppBucketUrl(gaiaHubUrl: String, privateKey: String): String? {
439+
suspend fun getAppBucketUrl(gaiaHubUrl: String, privateKey: String): String? = withContext(dispatcher){
439440
val challengeSigner = PrivateKey(HexString(privateKey)).toECKeyPair()
440441
val response = fetchPrivate("${gaiaHubUrl}/hub_info")
441442
val responseJSON = response.json()
442443
if (responseJSON != null) {
443444
val readURL = responseJSON.getString("read_url_prefix")
444445
val address = challengeSigner.toBtcAddress()
445-
return "${readURL}${address}/"
446+
return@withContext "${readURL}${address}/"
446447
} else {
447-
return null
448+
return@withContext null
448449
}
449450
}
450451

@@ -457,7 +458,7 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
457458
*@param zoneFileLookupURL The URL to use for zonefile lookup. If false, this will use the blockstack.js's getNameInfo function instead.
458459
*@result the public read URL of the file or null on error
459460
*/
460-
suspend fun getUserAppFileUrl(path: String, username: String, appOrigin: String, zoneFileLookupURL: String?): String {
461+
suspend fun getUserAppFileUrl(path: String, username: String, appOrigin: String, zoneFileLookupURL: String?): String = withContext(dispatcher){
461462
val profile = lookupProfile(username, zoneFileLookupURL?.let { URL(it) })
462463
var bucketUrl = NO_URL
463464
if (profile.json.has("apps")) {
@@ -468,16 +469,14 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
468469
bucketUrl = "${bucket}${path}"
469470
}
470471
}
471-
return bucketUrl
472+
return@withContext bucketUrl
472473
}
473474

474-
private suspend fun fetchPrivate(url: String): Response {
475+
private suspend fun fetchPrivate(url: String): Response = withContext(dispatcher) {
475476
val request = Request.Builder().url(url)
476477
.addHeader("Referrer-Policy", "no-referrer")
477478
.build()
478-
return withContext(Dispatchers.IO) {
479-
callFactory.newCall(request).execute()
480-
}
479+
return@withContext callFactory.newCall(request).execute()
481480
}
482481

483482
fun wrapProfileToken(token: String): ProfileTokenPair {
@@ -560,7 +559,7 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
560559
* @param expiresAt the time of expiration of the token, defaults to next year
561560
* @return the signed profile token
562561
*/
563-
suspend fun signProfileToken(profile: Profile, privateKey: String, subject: Entity, issuer: Entity, issuedAt: Date = Date(), expiresAt: Date = nextYear()): ProfileTokenPair {
562+
suspend fun signProfileToken(profile: Profile, privateKey: String, subject: Entity, issuer: Entity, issuedAt: Date = Date(), expiresAt: Date = nextYear()): ProfileTokenPair = withContext(dispatcher) {
564563

565564

566565
val payload = mapOf(
@@ -584,7 +583,7 @@ class Blockstack(private val callFactory: Call.Factory = OkHttpClient()) {
584583
val signature: String = jwtSigner.sign(signingInput, KPSigner(privateKey))
585584
val token = listOf(signingInput, signature).joinToString(".")
586585
Log.d(TAG, token)
587-
return wrapProfileToken(token)
586+
return@withContext wrapProfileToken(token)
588587
}
589588

590589
companion object {

blockstack-sdk/src/main/java/org/blockstack/android/sdk/BlockstackConnect.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import android.content.Intent
55
import android.util.Log
66
import androidx.annotation.StyleRes
77
import androidx.appcompat.app.AppCompatActivity
8-
import kotlinx.coroutines.Dispatchers
9-
import kotlinx.coroutines.ExperimentalCoroutinesApi
10-
import kotlinx.coroutines.FlowPreview
11-
import kotlinx.coroutines.withContext
8+
import kotlinx.coroutines.*
129
import org.blockstack.android.sdk.model.BlockstackConfig
1310
import org.blockstack.android.sdk.model.UserData
1411
import org.blockstack.android.sdk.ui.BlockstackConnectActivity
@@ -20,11 +17,13 @@ object BlockstackConnect {
2017

2118
private var blockstackSession: BlockstackSession? = null
2219
private var blockstackSignIn: BlockstackSignIn? = null
20+
private var dispatcher: CoroutineDispatcher = Dispatchers.IO
2321

2422
@JvmOverloads
25-
fun config(blockstackConfig: BlockstackConfig, sessionStore: ISessionStore, appDetails: AppDetails? = null): BlockstackConnect {
26-
blockstackSession = BlockstackSession(sessionStore, blockstackConfig)
27-
blockstackSignIn = BlockstackSignIn(sessionStore, blockstackConfig, appDetails)
23+
fun config(blockstackConfig: BlockstackConfig, sessionStore: ISessionStore, appDetails: AppDetails? = null, dispatcher: CoroutineDispatcher = Dispatchers.IO): BlockstackConnect {
24+
blockstackSession = BlockstackSession(sessionStore, blockstackConfig, dispatcher = dispatcher)
25+
blockstackSignIn = BlockstackSignIn(sessionStore, blockstackConfig, appDetails, dispatcher = dispatcher)
26+
this.dispatcher = dispatcher
2827
return this
2928
}
3029

@@ -63,7 +62,7 @@ object BlockstackConnect {
6362
if (authResponseTokens.size > 1) {
6463
val authResponse = authResponseTokens[1]
6564
Log.d(TAG, "AuthResponse token: $authResponse")
66-
withContext(Dispatchers.IO) {
65+
withContext(dispatcher) {
6766
val userDataResult = blockstackSession?.handlePendingSignIn(authResponse)
6867
?: errorResult
6968
result = if (userDataResult.hasValue) {

blockstack-sdk/src/main/java/org/blockstack/android/sdk/BlockstackSession.kt

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.blockstack.android.sdk
33
import android.util.Log
44
import com.colendi.ecies.EncryptedResultForm
55
import com.colendi.ecies.Encryption
6+
import kotlinx.coroutines.CoroutineDispatcher
67
import kotlinx.coroutines.Dispatchers
78
import kotlinx.coroutines.withContext
89
import me.uport.sdk.core.hexToByteArray
@@ -34,7 +35,8 @@ const val SIGNATURE_FILE_EXTENSION = ".sig"
3435
class BlockstackSession(private val sessionStore: ISessionStore, private val appConfig: BlockstackConfig? = null,
3536
private val callFactory: Call.Factory = OkHttpClient(),
3637
val blockstack: Blockstack = Blockstack(),
37-
val hub: Hub = Hub(callFactory)) {
38+
val hub: Hub = Hub(callFactory),
39+
val dispatcher: CoroutineDispatcher = Dispatchers.IO) {
3840

3941
private var appPrivateKey: String?
4042
var gaiaHubConfig: GaiaHubConfig? = null
@@ -53,37 +55,37 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
5355
* @return result object with the user data after sign-in or with an error
5456
*
5557
*/
56-
suspend fun handlePendingSignIn(authResponse: String): Result<UserData> {
58+
suspend fun handlePendingSignIn(authResponse: String): Result<out UserData> = withContext(dispatcher) {
5759
val transitKey = sessionStore.getTransitPrivateKey()
5860
val nameLookupUrl = sessionStore.sessionData.json.optString("core-node", "https://core.blockstack.org")
5961

6062
val tokenTriple = try {
6163
blockstack.decodeToken(authResponse)
6264
} catch (e: IllegalArgumentException) {
63-
return Result(null, ResultError(ErrorCode.LoginFailedError, "The authResponse parameter is an invalid base64 encoded token\n" +
65+
return@withContext Result(null, ResultError(ErrorCode.LoginFailedError, "The authResponse parameter is an invalid base64 encoded token\n" +
6466
"2 dots requires\n" +
6567
"Auth response: $authResponse"))
6668
}
6769
val tokenPayload = tokenTriple.second
6870
val isValidToken = blockstack.verifyToken(authResponse)
6971

7072
if (!isValidToken) {
71-
return Result(null, ResultError(ErrorCode.LoginFailedError, "invalid auth response"))
73+
return@withContext Result(null, ResultError(ErrorCode.LoginFailedError, "invalid auth response"))
7274
}
7375
val appPrivateKey = decrypt(tokenPayload.getString("private_key"), transitKey)
7476

7577
if (appPrivateKey == null) {
76-
return Result(null, ResultError(ErrorCode.LoginFailedError, "auth response used different transient key"))
78+
return@withContext Result(null, ResultError(ErrorCode.LoginFailedError, "auth response used different transient key"))
7779
}
7880

7981
val coreSessionToken = decrypt(tokenPayload.optString("core_token"), transitKey)
8082

8183
val userData = authResponseToUserData(tokenPayload, nameLookupUrl, appPrivateKey, coreSessionToken, authResponse)
8284

83-
this.appPrivateKey = appPrivateKey
85+
this@BlockstackSession.appPrivateKey = appPrivateKey
8486
sessionStore.updateUserData(userData)
8587

86-
return Result(userData)
88+
return@withContext Result(userData)
8789
}
8890

8991
suspend fun handleUnencryptedSignIn(authResponse: String): Result<UserData> {
@@ -130,7 +132,7 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
130132
private suspend fun extractProfile(tokenPayload: JSONObject, nameLookupUrl: String): JSONObject {
131133
val profileUrl = tokenPayload.optStringOrNull("profile_url")
132134
if (profileUrl != null && profileUrl.isNotBlank()) {
133-
return withContext(Dispatchers.IO) {
135+
return withContext(dispatcher) {
134136
val request = Request.Builder().url(profileUrl)
135137
.build()
136138
val response = callFactory.newCall(request).execute()
@@ -222,7 +224,7 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
222224
*/
223225
suspend fun getFile(path: String, options: GetFileOptions): Result<out Any> {
224226
Log.d(TAG, "getFile: path: $path options: $options")
225-
return withContext(Dispatchers.IO) {
227+
return withContext(dispatcher) {
226228
val urlResult = getFileUrl(path, options)
227229
if (urlResult.hasErrors) {
228230
return@withContext urlResult
@@ -313,12 +315,10 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
313315
}
314316

315317
suspend fun getGaiaAddress(appDomain: String, username: String): String {
316-
val fileUrl = blockstack.getUserAppFileUrl("/", username, appDomain, null)
317-
val address = Regex("([13][a-km-zA-HJ-NP-Z0-9]{26,35})").find(fileUrl)?.value
318-
if (address != null) {
319-
return address
320-
} else {
321-
return ""
318+
return withContext(dispatcher) {
319+
val fileUrl = blockstack.getUserAppFileUrl("/", username, appDomain, null)
320+
val address = Regex("([13][a-km-zA-HJ-NP-Z0-9]{26,35})").find(fileUrl)?.value
321+
return@withContext address ?: ""
322322
}
323323
}
324324

@@ -333,7 +333,7 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
333333
* @property a result object wiht a `String` representation of a url from
334334
* which you can read the file that was just put.
335335
*/
336-
suspend fun putFile(path: String, content: Any, options: PutFileOptions): Result<out String> {
336+
suspend fun putFile(path: String, content: Any, options: PutFileOptions): Result<out String> = withContext(dispatcher){
337337
Log.d(TAG, "putFile: path: ${path} options: ${options}")
338338
val gaiaHubConfiguration = getOrSetLocalGaiaHubConnection()
339339
val valid = content is String || content is ByteArray
@@ -378,7 +378,7 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
378378
}
379379
}
380380

381-
return withContext(Dispatchers.IO) {
381+
382382
try {
383383
val response = hub.uploadToGaiaHub(path, requestContent, gaiaHubConfiguration, contentType)
384384
if (!response.isSuccessful) {
@@ -409,7 +409,7 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
409409
?: e.toString()))
410410
}
411411

412-
}
412+
413413
}
414414

415415
private fun getSignKey(options: PutFileOptions): String {
@@ -440,21 +440,21 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
440440
}
441441

442442

443-
suspend fun deleteFile(path: String, options: DeleteFileOptions = DeleteFileOptions()): Result<out Unit> {
443+
suspend fun deleteFile(path: String, options: DeleteFileOptions = DeleteFileOptions()): Result<out Unit> = withContext(dispatcher){
444444
try {
445445
val response = hub.deleteFromGaiaHub(path, options.gaiaHubConfig ?: gaiaHubConfig!!)
446446
if (response != null) {
447447
if (response.isSuccessful) {
448-
return Result(Unit)
448+
return@withContext Result(Unit)
449449
} else {
450-
return Result(null, ResultError(ErrorCode.NetworkError,
450+
return@withContext Result(null, ResultError(ErrorCode.NetworkError,
451451
"failed to delete $path", response.code.toString()))
452452
}
453453
} else {
454-
return Result(null, ResultError(ErrorCode.UnknownError, "failed to delete $path"))
454+
return@withContext Result(null, ResultError(ErrorCode.UnknownError, "failed to delete $path"))
455455
}
456456
} catch (e: Exception) {
457-
return Result(null, ResultError(ErrorCode.UnknownError,
457+
return@withContext Result(null, ResultError(ErrorCode.UnknownError,
458458
"failed to delete $path: ${e.message ?: e.toString()}"))
459459
}
460460
}
@@ -487,7 +487,6 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
487487
blockstack.getUserAppFileUrl(path, options.username,
488488
options.app ?: appConfig!!.appDomain.getOrigin(),
489489
options.zoneFileLookupURL?.toString())
490-
491490
} else {
492491
val gaiaHubConfig = getOrSetLocalGaiaHubConnection()
493492
hub.getFullReadUrl(path, gaiaHubConfig)
@@ -506,7 +505,7 @@ class BlockstackSession(private val sessionStore: ISessionStore, private val app
506505
}
507506

508507
val request = buildListFilesRequest(page, gaiaHubConfig ?: getHubConfig())
509-
val response = withContext(Dispatchers.IO) {
508+
val response = withContext(dispatcher) {
510509
callFactory.newCall(request).execute()
511510
}
512511
if (!response.isSuccessful) {

0 commit comments

Comments
 (0)