-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
AjaxClient.scala
134 lines (111 loc) · 4.77 KB
/
AjaxClient.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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)
}
}
}
}