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)
}
}