All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.html5push.Html5Push.scala Maven / Gradle / Ivy

package sss.openstar.html5push


import java.security.Security
import java.util.Base64

import nl.martijndwars.webpush.{Notification, PushService}
import org.apache.http.{HttpResponse, HttpStatus}
import org.bouncycastle.jce.spec.ECPublicKeySpec
import sss.ancillary.Logging
import sss.openstar.UniqueNodeIdentifier
import sss.openstar.html5push.Subscriptions.Subscription
import sss.openstar.util.IOExecutionContext

import scala.concurrent.Future
import scala.util.Success

class Html5Push(
                 html5PushServer: PushService,
                 subscriptions: Subscriptions)(implicit ioExecution: IOExecutionContext) extends Logging {

  import ioExecution.ec


  def getKeyAsBytes(auth: String): Array[Byte] = Base64.getDecoder.decode(auth)

  //TEMPORARY??
  private def getPublicKey(auth: String) = {

    import java.security.KeyFactory

    import org.bouncycastle.jce.ECNamedCurveTable
    import org.bouncycastle.jce.provider.BouncyCastleProvider
    if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) Security.addProvider(new BouncyCastleProvider)

    val kf = KeyFactory.getInstance("ECDH", BouncyCastleProvider.PROVIDER_NAME)
    val ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1")
    val point = ecSpec.getCurve.decodePoint(getKeyAsBytes(auth))
    val pubSpec = new ECPublicKeySpec(point, ecSpec);

    kf.generatePublic(pubSpec)
  }


  def pushAndLog(who: UniqueNodeIdentifier,
                 msg: String
                ): Unit = {

    val payload = s"$msg:/"
    subscriptions.retrieve(who) match {
      case Some(s) =>
        pushAndLog(who, payload, s)
      case None =>
        log.debug(s"No html5 subscription found for $who")
    }
  }

  def pushAndLog(who: String, payload: String, sub: Subscription): Unit = {
    push(payload, sub).onComplete {
          //https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
      case Success(r: HttpResponse) if r.getStatusLine.getStatusCode < 299 =>
      case Success(r: HttpResponse) if r.getStatusLine.getStatusCode == HttpStatus.SC_BAD_REQUEST =>
        //if the request is BAD remove the sub on the ASSUMPTION that it's expired/invalid
        subscriptions.persist(who, None)
        log.warn(s"Failed to send Notification to {}, removed sub: {}", who, r)
      case e => log.warn("Problem sending HTML5 Notification to {}, {}", who, e)
    }
  }


  def push(payload: String, sub: Subscription): Future[HttpResponse] = Future {

    val n = Notification.builder()
      .userAuth(sub.auth)
      .userPublicKey(getPublicKey(sub.key))
      .endpoint(sub.endpoint)
      .payload(payload)
      .build()

    html5PushServer.send(n)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy