Skip to content

Instantly share code, notes, and snippets.

@inanna-malick
Forked from gre/pusher.scala
Last active August 11, 2016 00:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save inanna-malick/f3be49262e28aba31cf5 to your computer and use it in GitHub Desktop.
Save inanna-malick/f3be49262e28aba31cf5 to your computer and use it in GitHub Desktop.
Using Pusher API with Play framework in scala for publishing events
//send messages via Pusher API in play 2.4
import play.api.libs.json.{ Writes, Json }
import play.api.libs.ws.{ WSResponse, WSClient }
import play.api.libs.ws.ning.NingWSClient
import java.security.MessageDigest
import java.math.BigInteger
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import scala.concurrent.{ ExecutionContext, Future }
object Pusher {
val appId = "app_id"
val key = "key"
val secret = "secret"
val domain = "api.pusherapp.com"
val client = NingWSClient()
val p = new Pusher(client, PusherConfig(appId, key, secret, domain))
case class Msg(foo: String, bar: String) extends PusherEvent {
val eventName = "test event with type msg"
}
implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
implicit val writes = Json.writes[Msg]
def trigger() = p.trigger("test_channel", Msg("hello", "world")).onSuccess {
case resp =>
println(resp)
println(resp.body)
}
}
case class PusherConfig(appId: String, apiKey: String, secret: String, domain: String)
trait PusherEvent {
val eventName: String
}
class Pusher(client: WSClient, pusherConfig: PusherConfig) {
import pusherConfig.{ appId, apiKey, secret, domain }
//current timestamp in seconds, used for pusher auth
def authTimestamp() = System.currentTimeMillis() / 1000
//create and send a signed request: https://pusher.com/docs/rest_api#auth-signature
def trigger[T <: PusherEvent](channel: String, message: T)(implicit writes: Writes[T], ec: ExecutionContext): Future[WSResponse] = {
val url = "/apps/"+appId+"/channels/"+channel+"/events"
val body = Json.toJson(message).toString()
// request signature component: query parameters sorted by key, with keys converted to lowercase,
// then joined as in the query string. Note that the string must not be url escaped
// (e.g. given the keys auth_key: foo, Name: Something else, you get auth_key=foo&name=Something else)
val params = List(
"auth_key" -> apiKey,
"auth_timestamp" -> authTimestamp().toString,
"auth_version" -> "1.0",
"name" -> message.eventName,
"body_md5" -> md5(body)
).sortBy { case (key, _) => key }
val encodedParams =
params.map { case (key, value) => key+"="+value }.mkString("&")
val toSign = List("POST", url, encodedParams).mkString("\n")
val signature = sha256(toSign, secret)
val signedParams = params ++ List("auth_signature" -> signature)
client.url("http://"+domain + url).withQueryString(signedParams: _*).post(body)
}
private[this] def byteArrayToString(data: Array[Byte]): String = {
val hash = new BigInteger(1, data).toString(16)
"0" * (32 - hash.length) + hash
}
private[this] def md5(s: String): String =
byteArrayToString(MessageDigest.getInstance("MD5").digest(s.getBytes("US-ASCII")))
private[this] def sha256(s: String, secret: String): String = {
val mac = Mac.getInstance("HmacSHA256")
mac.init(new SecretKeySpec(secret.getBytes, "HmacSHA256"))
val digest = mac.doFinal(s.getBytes)
String.format("%0"+(digest.length << 1)+"x", new BigInteger(1, digest))
}
}
<!DOCTYPE html>
<head>
<title>Pusher Test</title>
<script src="https://js.pusher.com/3.0/pusher.min.js"></script>
<script>
// Enable pusher logging - don't include this in production
Pusher.log = function(message) {
if (window.console && window.console.log) {
window.console.log(message);
}
};
var pusher = new Pusher('key', {
encrypted: true
});
var channel = pusher.subscribe('test_channel');
channel.bind('my_event', function(data) {
alert(data.message);
});
</script>
</head>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment