|
|
@@ -1,201 +1,36 @@
|
|
|
package com.weEat
|
|
|
|
|
|
import com.tflucke.webroutes.Headers
|
|
|
-import org.scalajs.dom.document
|
|
|
-import org.scalajs.dom.raw.{Element,HTMLInputElement,MouseEvent,HTMLDivElement}
|
|
|
-import scala.scalajs.js
|
|
|
-import scala.scalajs.js.annotation.JSExportTopLevel
|
|
|
-import scala.scalajs.js.timers
|
|
|
-import scala.util.{Try,Success,Failure}
|
|
|
+import com.weEat.view.View
|
|
|
+import org.querki.jquery.{JQueryStatic => $}
|
|
|
+import org.scalajs.dom.{document,window}
|
|
|
+import org.scalajs.dom.raw.HTMLInputElement
|
|
|
import scala.concurrent.ExecutionContext.Implicits.global
|
|
|
-import scala.concurrent.Future
|
|
|
-import com.weEat.shared.models._
|
|
|
-import com.weEat.controllers.UserController
|
|
|
-import org.querki.jquery.{JQueryEventObject,JQuery,JQueryXHR,JQueryStatic => $}
|
|
|
-import com.tflucke.webroutes.{HTTPException,TimeoutException}
|
|
|
-import play.api.libs.json.Json
|
|
|
-import com.weEat.util.SessionStorage
|
|
|
|
|
|
object Main {
|
|
|
+ implicit def headers = Headers(Seq(
|
|
|
+ csrfHeader,
|
|
|
+ OAuthManager.authHeader
|
|
|
+ ).flatten.toMap)
|
|
|
+
|
|
|
/* Get the CSRF token embedded in the HTML page. This token will implicitly
|
|
|
* be passed to Rest RPC.
|
|
|
*/
|
|
|
- implicit def headers = Headers(Seq(csrfHeader, authHeader).flatten.toMap)
|
|
|
-
|
|
|
- @JSExportTopLevel(name="asyncCount")
|
|
|
- var asyncCount = 0
|
|
|
-
|
|
|
- def track[T](future: Future[T]): Future[T] = {
|
|
|
- asyncCount += 1
|
|
|
- future andThen {
|
|
|
- case _=> asyncCount -= 1
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
val csrfSelector = "input[name=\"csrfToken\"]"
|
|
|
def csrfHeader = Option(document.querySelector(csrfSelector)).map({ x =>
|
|
|
("Csrf-Token" -> x.asInstanceOf[HTMLInputElement].value)
|
|
|
})
|
|
|
|
|
|
- def authHeader = SessionStorage.get("access-token").map({ token =>
|
|
|
- ("Authorization" -> token)
|
|
|
- })
|
|
|
-
|
|
|
def main(args: Array[String]): Unit = {
|
|
|
- refreshToken
|
|
|
- $("#btn-login").click(promptLogin _)
|
|
|
- $("#btn-signup").click(promptSignup _)
|
|
|
- $("#btn-logout").click(logout _)
|
|
|
- }
|
|
|
-
|
|
|
- def promptLogin() = {
|
|
|
- $("body").append(overlayWindow("/assets/views/login.html", login))
|
|
|
- }
|
|
|
-
|
|
|
- def promptSignup() = {
|
|
|
- $("body").append(overlayWindow("/assets/views/register.html", signup))
|
|
|
- }
|
|
|
-
|
|
|
- def refreshToken: Unit = {
|
|
|
- SessionStorage.remove("access-token")
|
|
|
- SessionStorage.get("username").map({user =>
|
|
|
- SessionStorage.get("refresh-token").map({refresh =>
|
|
|
- track(UserController.accessToken()(RefreshRequest(user, refresh))
|
|
|
- .map(loginComplete(user)).recover({
|
|
|
- case _ => SessionStorage.remove("username").remove("refresh-token")
|
|
|
- }))
|
|
|
- })
|
|
|
- }).flatten
|
|
|
- }
|
|
|
+ import org.scalajs.dom.experimental.URLSearchParams
|
|
|
|
|
|
- def loginComplete(user: String)(auth: UserAuthorization) = {
|
|
|
- SessionStorage.set("access-token",
|
|
|
- "%s %s".format(auth.tokenType, auth.accessToken)
|
|
|
- )
|
|
|
- SessionStorage.set("username", user)
|
|
|
- SessionStorage.set("refresh-token", auth.refreshToken)
|
|
|
- timers.setTimeout(auth.expiresIn)(refreshToken)
|
|
|
- $("#login-btns").hide
|
|
|
- $("#logout-btns").show
|
|
|
- //track()
|
|
|
- }
|
|
|
-
|
|
|
- def signup(div: JQuery): Future[Any] = {
|
|
|
- import java.util.InputMismatchException
|
|
|
-
|
|
|
- val email = div.find("#email").value.toString
|
|
|
- val password = div.find("#password").value.toString
|
|
|
- track((if (!password.equals(div.find("#password2").value.toString))
|
|
|
- Future.failed(new InputMismatchException("Passwords do not match."))
|
|
|
- else
|
|
|
- UserController.registerUser()(UserRegistration(
|
|
|
- div.find("#fname").value.toString,
|
|
|
- div.find("#lname").value.toString,
|
|
|
- email,
|
|
|
- password
|
|
|
- )) map(loginComplete(email))) andThen({
|
|
|
- case Failure(e: InputMismatchException) => showError(div, e.getMessage)
|
|
|
- case Failure(e: HTTPException) => showError(div, e.responseText)
|
|
|
- case Failure(e: TimeoutException) => showError(div, e.getMessage)
|
|
|
- }))
|
|
|
- }
|
|
|
-
|
|
|
- @js.native
|
|
|
- @js.annotation.JSGlobal("btoa")
|
|
|
- def base64Encode(str: String): String = js.native
|
|
|
-
|
|
|
- def showError(div: JQuery, msg: String) = div.find(".alert-danger").html(
|
|
|
- s"<strong>Error:</strong> $msg"
|
|
|
- ).show
|
|
|
-
|
|
|
- def parseString[T](implicit reader: play.api.libs.json.Reads[T]) = {
|
|
|
- str: String => Json.parse(str).as[T]
|
|
|
- }
|
|
|
-
|
|
|
- def login(div: JQuery): Future[Any] = {
|
|
|
- val email = div.find("#email").value.toString
|
|
|
- implicit var headers = Main.headers +
|
|
|
- ("Authorization" -> ("Basic " + base64Encode(
|
|
|
- "%s:%s".format(email, div.find("#password").value)
|
|
|
- )))
|
|
|
- track(UserController.accessToken()(PasswordRequest()).andThen({
|
|
|
- case Success(auth) => loginComplete(email)(auth)
|
|
|
- case Failure(error: HTTPException) => Try(
|
|
|
- error.responseObject(parseString[GrantError])
|
|
|
- ) match {
|
|
|
- case Success(gError) => showError(div, gError.errorDescription)
|
|
|
- case Failure(_) => showError(div, error.getMessage)
|
|
|
- }
|
|
|
- case Failure(error: TimeoutException) => showError(div, error.getMessage)
|
|
|
- }))
|
|
|
- }
|
|
|
-
|
|
|
- def logout: Future[Unit] = {
|
|
|
- track(SessionStorage.get("username").map({user =>
|
|
|
- SessionStorage.get("refresh-token").map({ refresh: String =>
|
|
|
- UserController.revokeAccessToken()(RefreshRequest(user, refresh))
|
|
|
- })
|
|
|
- }).flatten.getOrElse(Future.failed(
|
|
|
- new IllegalStateException("No login information.")
|
|
|
- )).map({ _ =>
|
|
|
- SessionStorage.remove("username").remove("refresh-token")
|
|
|
- document.location.reload(false)
|
|
|
- }))
|
|
|
- }
|
|
|
-
|
|
|
- def shadeWindow(cancelFn: Option[(() => Unit)]) = {
|
|
|
- val shadeDiv = $("<div>").css(js.Dictionary[js.Any](
|
|
|
- "z-index" -> 99,
|
|
|
- "background-color" -> "rgba(0, 0, 0, 0.5)",
|
|
|
- "position" -> "fixed",
|
|
|
- "top" -> 0,
|
|
|
- "bottom" -> 0,
|
|
|
- "left" -> 0,
|
|
|
- "right" -> 0
|
|
|
- ))
|
|
|
- shadeDiv.click((event: JQueryEventObject) => {
|
|
|
- if (event.target == shadeDiv(0))
|
|
|
- {
|
|
|
- cancelFn match {
|
|
|
- case Some(fn) => fn()
|
|
|
- case None =>
|
|
|
- }
|
|
|
- $(event.target).detach
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
+ OAuthManager.refreshToken
|
|
|
+ $("#btn-login").click(OAuthManager.promptLogin _)
|
|
|
+ $("#btn-signup").click(OAuthManager.promptSignup _)
|
|
|
+ $("#btn-logout").click(OAuthManager.logout _)
|
|
|
|
|
|
- def overlayWindow[T](
|
|
|
- contentUrl: String,
|
|
|
- submitFn: (JQuery) => Future[T],
|
|
|
- cancelFn: Option[() => Unit] = None
|
|
|
- ) = {
|
|
|
- val promptDiv = $("<div>").css(js.Dictionary[js.Any](
|
|
|
- "margin" -> "auto",
|
|
|
- "padding" -> "1em",
|
|
|
- "background-color" -> "#ffffff",
|
|
|
- "position" -> "relative",
|
|
|
- "border-radius" -> "1em",
|
|
|
- "top" -> "50%",
|
|
|
- "transform" -> "translateY(-50%)"
|
|
|
- )).addClass("w-50")
|
|
|
- val shadeDiv = shadeWindow(cancelFn).append(promptDiv)
|
|
|
- promptDiv.load(contentUrl, "",
|
|
|
- (elm: Element, resp: String, status: String, xhr: JQueryXHR) => {
|
|
|
- $(elm).find("*[data-cb='cancel']").click((event: JQueryEventObject) => {
|
|
|
- cancelFn.map({fn => fn()})
|
|
|
- shadeDiv.detach
|
|
|
- })
|
|
|
- val div = $(elm).find("*[data-cb='success']").click(
|
|
|
- (event: JQueryEventObject) => {
|
|
|
- submitFn(promptDiv) onComplete {
|
|
|
- case Success(_) => shadeDiv.detach
|
|
|
- case Failure(err) => System.err.println(err)
|
|
|
- }
|
|
|
- }
|
|
|
- )
|
|
|
- }
|
|
|
- )
|
|
|
- shadeDiv.append(promptDiv)
|
|
|
+ View.fromTag(
|
|
|
+ new URLSearchParams(window.location.search).get("t")
|
|
|
+ ).present
|
|
|
}
|
|
|
}
|