io.udash.rpc.internals.UsesServerRPC.scala Maven / Gradle / Ivy
package io.udash.rpc.internals
import com.avsystem.commons.SharedExtensions._
import io.udash.rpc._
import io.udash.utils.{CallbacksHandler, Registration}
import org.scalajs.dom
import scala.concurrent.duration.{Duration, DurationInt}
import scala.concurrent.{Future, Promise}
import scala.scalajs.js
import scala.scalajs.js.Dictionary
/**
* Base trait for client-side components which use some RPC exposed by server-side.
*/
private[rpc] trait UsesServerRPC[ServerRPCType] extends UsesRemoteRPC[ServerRPCType] {
/**
* Proxy for remote RPC implementation. Use this to perform RPC calls.
*/
lazy val remoteRpc: ServerRPCType = remoteRpcAsReal.asReal(new RawRemoteRPC(Nil))
/**
* This allows for generation of proxy which translates RPC calls into raw calls that
* can be sent through the network.
*/
protected def remoteRpcAsReal: ServerRawRpc.AsRealRpc[ServerRPCType]
protected val connector: ServerConnector
protected def callTimeout: Duration = 30 seconds
private val pendingCalls: Dictionary[Promise[JsonStr]] = js.Dictionary.empty
private val exceptionCallbacks = new CallbacksHandler[Throwable]
private var cid: Int = 0
private def newCallId(): String = {
cid += 1
cid.toString
}
/**
* Registers callback which will be called whenever RPC request returns failure.
*
* The callbacks are executed in order of registration. Registration operations don't preserve callbacks order.
* Each callback is executed once, exceptions thrown in callbacks are swallowed.
*/
def onCallFailure(callback: exceptionCallbacks.CallbackType): Registration =
exceptionCallbacks.register(callback)
private def handleException(ex: Throwable): Unit =
exceptionCallbacks.fire(ex)
def handleResponse(response: RpcResponse): Unit = {
pendingCalls.remove(response.callId)
.foreach { promise =>
response match {
case RpcResponseSuccess(r, _) =>
promise.success(r)
case RpcResponseException(_, exception, _) =>
handleException(exception)
promise.failure(exception)
case RpcResponseFailure(cause, error, _) =>
val exception = RpcFailure(cause, error)
handleException(exception)
promise.failure(exception)
}
}
}
override protected[rpc] def fireRemote(getterChain: List[RpcInvocation], invocation: RpcInvocation): Unit =
sendRpcRequest(RpcFire(invocation, getterChain))
protected[rpc] def callRemote(callId: String, getterChain: List[RpcInvocation], invocation: RpcInvocation): Unit =
sendRpcRequest(RpcCall(invocation, getterChain, callId))
private def sendRpcRequest(request: RpcRequest): Unit =
connector.sendRpcRequest(request)
protected class RawRemoteRPC(getterChain: List[RpcInvocation]) extends ServerRawRpc {
def fire(invocation: RpcInvocation): Unit =
fireRemote(getterChain, invocation)
def call(invocation: RpcInvocation): Future[JsonStr] =
Promise[JsonStr]().setup { promise =>
val callId = newCallId()
callRemote(callId, getterChain, invocation)
pendingCalls.put(callId, promise)
dom.window.setTimeout(
() => handleResponse(RpcResponseException("Request timeout", UsesServerRPC.CallTimeout(callTimeout), callId)),
callTimeout.toMillis.toDouble
)
}.future
def get(invocation: RpcInvocation): ServerRawRpc =
new RawRemoteRPC(invocation :: getterChain)
}
}
object UsesServerRPC {
case class CallTimeout(callTimeout: Duration) extends RuntimeException(s"Response missing after $callTimeout.")
}