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

japgolly.webapputil.ajax.AjaxClient.scala Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC12
Show newest version
package japgolly.webapputil.ajax

import japgolly.scalajs.react.AsyncCallback
import japgolly.scalajs.react.extra.Ajax
import japgolly.webapputil.binary.BinaryData
import japgolly.webapputil.general._
import org.scalajs.dom.XMLHttpRequest
import scala.scalajs.js.typedarray.ArrayBuffer

trait AjaxClient[P[_]] {
  def asyncFunction(p: AjaxProtocol[P]): AsyncFunction[
                                           p.protocol.RequestType,
                                           ErrorMsg,
                                           p.protocol.ResponseType]
}

object AjaxClient {

  /** Calls are never made. AsyncCallbacks never complete. */
  def never[P[_]]: AjaxClient[P] =
    new AjaxClient[P] {
      override def asyncFunction(p: AjaxProtocol[P]) =
        AsyncFunction.const(AsyncCallback.never[Either[ErrorMsg, p.protocol.ResponseType]])
    }

  trait Response[+A] {
    def shouldRetry: Boolean
    def result: Either[ErrorMsg, A]
  }

  object Response {

    def apply[A](result: Either[ErrorMsg, A]): Response[A] =
      apply(result, shouldRetry = result.isLeft)

    def apply[A](result: Either[ErrorMsg, A], shouldRetry: Boolean): Response[A] = {
      val _shouldRetry = shouldRetry
      val _result      = result
      new Response[A] {
        override def shouldRetry = _shouldRetry
        override def result      = _result
      }
    }

    def success[A](result: A): Response[A] =
      apply(Right(result))
  }

  trait WithRetries[P[_]] extends AjaxClient[P] {

    protected def singleCall(p: AjaxProtocol[P])(req: p.protocol.RequestType): AsyncCallback[Response[p.protocol.ResponseType]]

    protected val maxRetries: Int =
      2

    protected def callWithRetry(p: AjaxProtocol[P])(req: p.protocol.RequestType): AsyncCallback[Response[p.protocol.ResponseType]] = {
      val once = singleCall(p)(req)
      AsyncCallback.tailrec(maxRetries) { retriesRemaining =>
        if (retriesRemaining > 0)
          once.attempt.flatMap {
            case Right(r)               if r.shouldRetry   => AsyncCallback.pure(Left(retriesRemaining - 1))
            case Right(r)                                  => AsyncCallback.pure(Right(r))
            case Left(AjaxException(x)) if x.status == 501 => AsyncCallback.pure(Left(retriesRemaining - 1)) // server rejected due to protocol ver diff
            case Left(e)                                   => AsyncCallback.throwException(e)
          }
        else
          once.map(Right(_))
      }
    }

    override def asyncFunction(p: AjaxProtocol[P]): AsyncFunction[p.protocol.RequestType, ErrorMsg, p.protocol.ResponseType] =
      AsyncFunction
        .simple((req: p.protocol.RequestType) => callWithRetry(p)(req).map(_.result))
        .extractErrorFromOutput
  }

  trait Binary[P[_]] extends WithRetries[P] {
    protected def encode[A](p: P[A], a: A): BinaryData
    protected def decode[A](p: P[A], b: BinaryData): Response[A]

    protected def isSuccess(xhr: XMLHttpRequest): Boolean =
      xhr.status >= 200 && xhr.status < 300

    override protected def singleCall(p: AjaxProtocol[P])(req: p.protocol.RequestType): AsyncCallback[Response[p.protocol.ResponseType]] = {
      val prep   = p.protocol.prepareSend(req)
      val reqBin = encode(p.requestProtocol.codec, prep.request)

      Ajax("POST", p.url.relativeUrl)
        .setRequestHeader("Content-Type", "application/octet-stream")
        .and(_.responseType = "arraybuffer")
        .send(reqBin.unsafeArrayBuffer)
        .asAsyncCallback
        .map { xhr =>
          if (isSuccess(xhr)) {
            val ab       = xhr.response.asInstanceOf[ArrayBuffer]
            val resBin   = BinaryData.unsafeFromArrayBuffer(ab)
            val resCodec = prep.response.codec
            decode(resCodec, resBin)
          } else
            throw AjaxException(xhr)
        }
    }
  }

  trait Json[P[_]] extends WithRetries[P] {
    protected def encode[A](p: P[A], a: A): String
    protected def decode[A](p: P[A], j: String): Response[A]

    protected def isSuccess(xhr: XMLHttpRequest): Boolean =
      (xhr.status >= 200 && xhr.status < 300) && (xhr.getResponseHeader("Content-Type") match {
        case null => true
        case t    => t.takeWhile(_ != ';') == "application/json"
      })

    override def singleCall(p: AjaxProtocol[P])(req: p.protocol.RequestType): AsyncCallback[Response[p.protocol.ResponseType]] = {
      val prep    = p.protocol.prepareSend(req)
      val reqJson = encode(p.requestProtocol.codec, prep.request)

      Ajax("POST", p.url.relativeUrl)
        .setRequestContentTypeJsonUtf8
        .send(reqJson)
        .asAsyncCallback
        .map { xhr =>
          if (isSuccess(xhr)) {
            val resJson  = xhr.responseText
            val resCodec = prep.response.codec
            decode(resCodec, resJson)
          } else
            throw AjaxException(xhr)
        }
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy