diff options
author | Jakob Odersky <jakob@odersky.com> | 2016-11-13 17:14:43 -0800 |
---|---|---|
committer | Jakob Odersky <jakob@odersky.com> | 2016-11-13 17:14:43 -0800 |
commit | 7efed1380f6a6cfc8d0b95015f1fa167b4a7bc23 (patch) | |
tree | 8a9f81a0f89a029811f1645b9f2e74f5b3b54d94 | |
parent | e317debf1a5d639c9a5fde0f0353a7b3b7ae86a0 (diff) | |
download | play-scalajs-chat-7efed1380f6a6cfc8d0b95015f1fa167b4a7bc23.tar.gz play-scalajs-chat-7efed1380f6a6cfc8d0b95015f1fa167b4a7bc23.tar.bz2 play-scalajs-chat-7efed1380f6a6cfc8d0b95015f1fa167b4a7bc23.zip |
Implement chat room backend
-rw-r--r-- | client/src/main/scala/chat/Main.scala | 16 | ||||
-rw-r--r-- | server/app/controllers/HomeController.scala | 23 | ||||
-rw-r--r-- | server/app/controllers/actors.scala | 56 | ||||
-rw-r--r-- | server/app/views/index.scala.html | 6 | ||||
-rw-r--r-- | server/conf/routes | 2 | ||||
-rw-r--r-- | shared/src/main/scala/chat/Messages.scala | 3 | ||||
-rw-r--r-- | shared/src/main/scala/chat/messages.scala | 10 |
7 files changed, 96 insertions, 20 deletions
diff --git a/client/src/main/scala/chat/Main.scala b/client/src/main/scala/chat/Main.scala index c68e0d9..dbc2876 100644 --- a/client/src/main/scala/chat/Main.scala +++ b/client/src/main/scala/chat/Main.scala @@ -1,26 +1,22 @@ package chat +import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue import scala.scalajs.js import org.scalajs.dom -import scala.util.{ Failure, Success } -import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue +import org.scalajs.dom.raw.MessageEvent import upickle.default._ object Main extends js.JSApp { def main(): Unit = { val root = dom.document.getElementById("root") + val sock = new dom.WebSocket("ws://localhost:9000/socket/john") - dom.ext.Ajax.get("/message").onComplete { - case Success(msg) if 200 <= msg.status && msg.status < 300 => - root.textContent = "OK, " + read[Message](msg.responseText).data - case Success(msg) => - root.textContent = msg.responseText - case Failure(err) => - root.textContent = "ERROR: " + err + sock.onmessage = (msg: MessageEvent) => { + val event = read[Event](msg.data.asInstanceOf[String]) + dom.console.log(event.toString()) } - //dom.document.getElementById("scalajsShoutOut").textContent = SharedMessages.itWorks } } diff --git a/server/app/controllers/HomeController.scala b/server/app/controllers/HomeController.scala index 13bc8dc..4d48c98 100644 --- a/server/app/controllers/HomeController.scala +++ b/server/app/controllers/HomeController.scala @@ -1,14 +1,24 @@ package controllers -import play.api._ +import akka.actor.{ActorRef, ActorSystem, Props} +import akka.stream.Materializer +import chat._ +import javax.inject._ +import play.api.libs.streams.ActorFlow import play.api.mvc._ +import play.api.mvc.WebSocket.MessageFlowTransformer import upickle.default._ /** * This controller creates an `Action` to handle HTTP requests to the * application's home page. */ -class HomeController extends Controller { +class HomeController @Inject() ( + implicit system: ActorSystem, + mat: Materializer +) extends Controller { + + lazy val room: ActorRef = system.actorOf(Props(classOf[RoomActor])) /** * Create an Action to render an HTML page. @@ -21,8 +31,13 @@ class HomeController extends Controller { Ok(views.html.index()) } - def message = Action { implicit request => - Ok(write(chat.Message("hello"))) + def socket(uid: String) = WebSocket.accept[Command, Event] { request => + ActorFlow.actorRef(out => Props(classOf[ClientActor], uid, room, out)) } + implicit val transformer = MessageFlowTransformer.stringMessageFlowTransformer.map( + in => read[Command](in), + (out: Event) => write(out) + ) + } diff --git a/server/app/controllers/actors.scala b/server/app/controllers/actors.scala new file mode 100644 index 0000000..f10f925 --- /dev/null +++ b/server/app/controllers/actors.scala @@ -0,0 +1,56 @@ +package controllers + +import akka.actor.{ Actor, ActorRef, Terminated } +import chat._ +import scala.collection.mutable.HashMap + + +/** An actor instantiated for every websocket connection. It represents a + * chat client and registers with a chat room. + * @param uid user id of the connecting client + * @param room chat room actor that this client will join + * @param socket websocket actor, any message sent to it will get transferred to the remote + * browser + */ +class ClientActor(uid: String, room: ActorRef, socket: ActorRef) extends Actor { + + override def preStart() = { + room ! (self, uid) + } + + def receive = { + case cmd: Command => room ! cmd + case ev: Event => socket ! ev + } + +} + +/** An actor that represents a chat room. + * Handles commands and events subclassing `chat.Command` and `chat.Event` + */ +class RoomActor extends Actor { + val clients = new HashMap[ActorRef, String] + + override def receive = { + + case (client: ActorRef, uid: String) => + context.watch(sender) + clients += client -> uid + for ( (client, _) <- clients ) { + client ! Joined(uid) + } + + case Terminated(client) => + clients -= client + for ( (cl, _) <- clients ) { + cl ! Left(clients(client)) + } + + case Broadcast(content) => + val origin = clients(sender) + for ( (client, _) <- clients ) { + client ! Message(origin, content) + } + } +} + diff --git a/server/app/views/index.scala.html b/server/app/views/index.scala.html index 911bdaf..4b71c59 100644 --- a/server/app/views/index.scala.html +++ b/server/app/views/index.scala.html @@ -1,7 +1,9 @@ @() -@main("Welcome to Play") { - <main id="root">placeholder</main> +@main("Welcome to Chat") { + <main id="root"> + hello + </main> <script src="@routes.Assets.versioned("client-fastopt.js")" type="text/javascript"></script> <script type="text/javascript"> chat.Main().main() diff --git a/server/conf/routes b/server/conf/routes index ca64d25..ab400c7 100644 --- a/server/conf/routes +++ b/server/conf/routes @@ -5,7 +5,7 @@ # An example controller showing a sample home page GET / controllers.HomeController.index -GET /message controllers.HomeController.message +GET /socket/:uid controllers.HomeController.socket(uid) # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) diff --git a/shared/src/main/scala/chat/Messages.scala b/shared/src/main/scala/chat/Messages.scala deleted file mode 100644 index deff8eb..0000000 --- a/shared/src/main/scala/chat/Messages.scala +++ /dev/null @@ -1,3 +0,0 @@ -package chat - -case class Message(data: String) diff --git a/shared/src/main/scala/chat/messages.scala b/shared/src/main/scala/chat/messages.scala new file mode 100644 index 0000000..a27631e --- /dev/null +++ b/shared/src/main/scala/chat/messages.scala @@ -0,0 +1,10 @@ +package chat + +sealed trait Command // message to chat server +sealed trait Event // message from chat server + +case class Broadcast(content: String) extends Command + +case class Joined(uid: String) extends Event +case class Left(uid: String) extends Event +case class Message(uid: String, content: String) extends Event |