All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.pusher.platform.BaseClient.kt Maven / Gradle / Ivy

package com.pusher.platform

import com.pusher.platform.RequestDestination.Absolute
import com.pusher.platform.RequestDestination.Relative
import com.pusher.platform.network.*
import com.pusher.platform.retrying.RetryStrategyOptions
import com.pusher.platform.subscription.*
import com.pusher.platform.tokenProvider.TokenProvider
import com.pusher.util.*
import elements.*
import okhttp3.*
import java.io.File
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import elements.Headers as ElementsHeaders

data class BaseClient(
    val host: String,
    val dependencies: PlatformDependencies,
    val client: OkHttpClient = OkHttpClient(),
    private val encrypted: Boolean = true
) {

    private val schema = if (encrypted) "https" else "http"
    private val baseUrl = "$schema://$host"

    internal val logger = dependencies.logger
    private val mediaTypeResolver = dependencies.mediaTypeResolver
    private val sdkInfo = dependencies.sdkInfo

    private val httpClient = client.newBuilder().apply {
        readTimeout(0, TimeUnit.MINUTES)
    }.build()

    @JvmOverloads
    fun  subscribeResuming(
            destination: RequestDestination,
            listeners: SubscriptionListeners,
            headers: ElementsHeaders,
            tokenProvider: TokenProvider?,
            tokenParams: Any?,
            retryOptions: RetryStrategyOptions,
            bodyParser: DataParser,
            initialEventId: String? = null
    ): Subscription {
        val id = SubscriptionIDGenerator.next()
        return createResumingStrategy(
                subscriptionID = id,
                initialEventId = initialEventId,
                logger = logger,
                nextSubscribeStrategy = createTokenProvidingStrategy(
                        subscriptionID = id,
                        tokenProvider = tokenProvider,
                        tokenParams = tokenParams,
                        logger = logger,
                        nextSubscribeStrategy = createBaseSubscription(
                                path = destination.toRequestPath(),
                                bodyParser = bodyParser
                        )
                ),
                errorResolver = ErrorResolver(retryOptions)
        )(listeners, headers)
    }

    fun  subscribeNonResuming(
            destination: RequestDestination,
            listeners: SubscriptionListeners,
            headers: ElementsHeaders,
            tokenProvider: TokenProvider?,
            tokenParams: Any?,
            retryOptions: RetryStrategyOptions,
            bodyParser: DataParser
    ): Subscription {
        val id = SubscriptionIDGenerator.next()
        return createRetryingStrategy(
                subscriptionID = id,
                logger = logger,
                nextSubscribeStrategy = createTokenProvidingStrategy(
                        subscriptionID = id,
                        tokenProvider = tokenProvider,
                        tokenParams = tokenParams,
                        logger = logger,
                        nextSubscribeStrategy = createBaseSubscription(path = destination.toRequestPath(), bodyParser = bodyParser)),
                errorResolver = ErrorResolver(retryOptions)
        )(listeners, headers)
    }

    @JvmOverloads
    fun  request(
            requestDestination: RequestDestination,
            headers: ElementsHeaders,
            method: String,
            responseParser: DataParser,
            body: String? = null,
            tokenProvider: TokenProvider? = null,
            tokenParams: Any? = null
    ): Future> = tokenProvider
            .authHeaders(headers, tokenParams)
            .flatMapFutureResult { authHeaders ->
                performRequest(
                        destination = requestDestination,
                        headers = authHeaders,
                        method = method,
                        requestBody = body?.let { RequestBody.create(MediaType.parse("application/json"), it) },
                        responseParser = responseParser
                )
            }

    @JvmOverloads
    fun  upload(
            requestDestination: RequestDestination,
            headers: ElementsHeaders = emptyHeaders(),
            file: File,
            responseParser: DataParser,
            tokenProvider: TokenProvider? = null,
            tokenParams: Any? = null
    ): Future> = when {
        file.exists() -> {
            tokenProvider.authHeaders(headers, tokenParams).flatMapFutureResult { authHeaders ->
                performRequest(
                        destination = requestDestination,
                        headers = authHeaders,
                        method = "POST",
                        requestBody = file.toRequestMultipartBody(),
                        responseParser = responseParser
                )
            }
        }
        else -> Futures.now(Errors.upload("File does not exist at ${file.path}").asFailure())
    }

    @JvmOverloads
    fun  externalUpload(
            requestDestination: RequestDestination,
            method: String,
            headers: ElementsHeaders = emptyHeaders(),
            data: ByteArray,
            mimeType: String,
            responseParser: DataParser
    ): Future> =
            performRequest(
                    destination = requestDestination,
                    headers = headers,
                    method = method,
                    requestBody = RequestBody.create(MediaType.parse(mimeType), data),
                    responseParser = responseParser
            )

    /**
     * Provides a future that will provide the same headers with auth token if possible.
     */
    private fun TokenProvider?.authHeaders(headers: ElementsHeaders, tokenParams: Any? = null): Future> =
            this?.fetchToken(tokenParams)
                    ?.mapResult { token -> headers + ("Authorization" to listOf("Bearer $token")) }
                    ?: headers.asSuccess().toFuture()

    private fun File.toRequestMultipartBody(): MultipartBody =
            MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("file", name, toRequestBody())
                    .build()

    private fun File.toRequestBody(): RequestBody =
            RequestBody.create(MediaType.parse(mediaTypeResolver.fileMediaType(this) ?: ""), this)

    /**
     * Ensures that:
     *  - GET doesn't have a body
     *  - PUT and POST have an empty body if missing
     */
    private fun RequestBody?.forMethod(method: String): RequestBody? = when (method.toUpperCase()) {
        "GET" -> null
        "POST", "PUT" -> this ?: RequestBody.create(MediaType.parse("text/plain"), "")
        else -> this
    }

    private fun  performRequest(
            destination: RequestDestination,
            headers: ElementsHeaders,
            method: String,
            requestBody: RequestBody?,
            responseParser: DataParser
    ): Future> = Futures.schedule {
        val requestURL = destination.toRequestPath()
        logger.verbose("Request started: $method $requestURL with body: $requestBody")

        val request = createRequest {
            method(method, requestBody.forMethod(method))
            url(requestURL)
            headers.forEach { (name, values) ->
                values.forEach { value -> addHeader(name, value) }
            }
        }

        val response = httpClient.newCall(request).execute()

        when (response?.code()) {
            null -> OtherError("Response was null").asFailure()
            in 200..299 -> response
                    .also { logger.verbose("Request OK: $method $requestURL with status code: ${response.code()} ") }
                    .body()?.string()
                    .orElse { Errors.other("Missing body in $response") }
                    .flatMap(responseParser)
            else -> response
                    .also { logger.verbose("Request Failed: $method $requestURL with status code: ${response.code()}") }
                    .body()?.string()
                    .parseAs { Errors.network("could not parse error response: $response") }
                    .map { b ->
                        Errors.response(
                                statusCode = response.code(),
                                headers = response.headers().toMultimap(),
                                error = b.error,
                                errorDescription = b.errorDescription,
                                URI = b.URI
                        )
                    }
                    .recover { it }
                    .asFailure()
        }
    }

    private fun  createBaseSubscription(
            path: String,
            bodyParser: DataParser
    ): SubscribeStrategy = { listeners, headers ->
        BaseSubscription(
                path = path,
                headers = headers,
                onOpen = listeners.onOpen,
                onError = listeners.onError,
                onEvent = listeners.onEvent,
                onEnd = listeners.onEnd,
                httpClient = httpClient,
                logger = logger,
                baseClient = this,
                messageParser = bodyParser
        )
    }

    internal fun createRequest(block: Request.Builder.() -> Unit): Request =
            Request.Builder().apply {
                addHeader("X-SDK-Product", sdkInfo.product)
                addHeader("X-SDK-Version", sdkInfo.sdkVersion)
                addHeader("X-SDK-Language", sdkInfo.language)
                addHeader("X-SDK-Platform", sdkInfo.platform)
            }.also(block).build()

    private fun RequestDestination.toRequestPath(): String = when (this) {
        is Absolute -> url
        is Relative -> absolutePath(path)
    }

    private fun absolutePath(path: String): String = "$baseUrl/$path".replaceMultipleSlashesInUrl()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy