aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2016-11-13 17:14:43 -0800
committerJakob Odersky <jakob@odersky.com>2016-11-13 17:14:43 -0800
commit7efed1380f6a6cfc8d0b95015f1fa167b4a7bc23 (patch)
tree8a9f81a0f89a029811f1645b9f2e74f5b3b54d94
parente317debf1a5d639c9a5fde0f0353a7b3b7ae86a0 (diff)
downloadplay-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.scala16
-rw-r--r--server/app/controllers/HomeController.scala23
-rw-r--r--server/app/controllers/actors.scala56
-rw-r--r--server/app/views/index.scala.html6
-rw-r--r--server/conf/routes2
-rw-r--r--shared/src/main/scala/chat/Messages.scala3
-rw-r--r--shared/src/main/scala/chat/messages.scala10
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