You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
package outwatchapp.util.reactive
import scala.concurrent.Future
import io.circe.Decoder import io.circe.Encoder import io.circe.Printer import io.circe.parser._ import io.circe.syntax._ import monix.bio.Task import monix.execution.Ack import monix.execution.Cancelable import monix.execution.CancelablePromise import monix.execution.cancelables.SingleAssignCancelable import monix.reactive.Observable import monix.reactive.Observer import monix.reactive.OverflowStrategy import typings.reconnectingWebsocket.eventsMod.CloseEvent import typings.reconnectingWebsocket.eventsMod.ErrorEvent import typings.reconnectingWebsocket.eventsMod.Event import typings.reconnectingWebsocket.mod.{default => RW}
import scalajs.js import typings.std.MessageEvent import typings.reconnectingWebsocket.mod.Options import outwatchapp.util.reactive.Exceptions.DecodeException import outwatchapp.util.reactive.Exceptions.WrongTypeException import monix.reactive.observers.Subscriber import monix.reactive.observers.BufferedSubscriber import outwatchapp.util.reactive.Exceptions.UseAfterClose import monix.bio.IO
class ReconnectingWebSocketImpl[T: Encoder: Decoder]( ws: RW, overflowStrategy: OverflowStrategy.Synchronous[T] ) { val printer = Printer.noSpaces
/** @throws ReactiveException
*/ val source: Task[Observable[T]] = Task.deferAction(implicit s => for { obs <- Task( Observable .create[T](overflowStrategy) { sub => val c = SingleAssignCancelable() val onmessage: js.Function1[MessageEvent[_], Unit] = _.data match { case s: String => decode[T](s) .map { res => if (sub.onNext(res) == Ack.Stop) c.cancel() res } .left .foreach(err => sub.onError( DecodeException( s"Failed to decode $s. Error was $err" ) ) ) case other => sub.onError( WrongTypeException(s"Received wrong type: $other") ) } val emptyMsgFn: js.Function1[MessageEvent[_], Unit] = _ => println("message fn not initialized") ws.onmessage = onmessage
val onclose: js.Function1[CloseEvent, Unit] = a => { println( s"Websocket closing - ${a.code} ${a.wasClean} ${a.reason}" ) sub.onComplete() } ws.onclose = onclose
val onerror: js.Function1[ErrorEvent, Unit] = (e: Event) => sub.onError(new Exception(s"Error in WebSocket: $e"))
ws.onerror = onerror
c := Cancelable { () => ws.onmessage = emptyMsgFn } } .publish .refCount ) _ <- Task(obs.subscribe(Observer.empty)) } yield obs )
val sink: Task[Observer[T]] = Task.deferAction(implicit s => Task( BufferedSubscriber( new Subscriber[T] { import monix.execution.FutureUtils import scala.concurrent.duration._ override def scheduler = s override def onNext(elem: T): Future[Ack] = { val msg = printer.print(elem.asJson) if (ws.readyState == 2 || ws.readyState == 3) Ack.Stop else { FutureUtils .delayedResult(2.second)(ws.send(msg)) .flatMap(_ => Ack.Continue) } } override def onError(ex: Throwable): Unit = s.reportFailure(ex) override def onComplete(): Unit = println("CLOSING WEBSOCKET 2 ") }, OverflowStrategy.Default ) ) )
} object ReconnectingWebSocket { sealed trait Error final case object Error extends Error
/** @throws ReactiveException
*/ final case class Source[T](value: Observable[T])
/** A buffered Observer
* * @param value */ final case class Sink[T](value: Observer[T]) { def send(t: T) = IO.deferFuture(value.onNext(t)).flatMap { case Ack.Continue => Task.unit case Ack.Stop => IO.raiseError(UseAfterClose("Websocket was closed")) } }
private val defaultOptions = Options() .setMinReconnectionDelay(5000) .setMaxRetries(2) .setMaxEnqueuedMessages(50)
def onopen[A](op: => A): js.Function1[Event, Unit] = _ => op
def apply[T <: Product: Encoder: Decoder]( path: String, options: Options = defaultOptions, overflowStrategy: OverflowStrategy.Synchronous[T] = OverflowStrategy.DropOld(50) ) = for { websocket <- Task(new RW(path, js.undefined, options)) p = CancelablePromise[Unit]() _ <- Task(websocket.onopen = onopen(p.success(()))) _ <- Task.deferFuture(p.future) _ <- Task(websocket.onopen = onopen(())) impl = new ReconnectingWebSocketImpl[T](websocket, overflowStrategy) source <- impl.source sink <- impl.sink // _ <- Task.deferAction(implicit s => Task(source.subscribe(sink)))
} yield Source(source) -> Sink(sink) }
|