CometD Issues with Publishing Data to a Channel - push-notification

I am new to cometd, I have planned to send a message to the server and get the message in my browser using cometd
If i send a message it is successfully send to the server but couldn't get it in the browser
private void testService() {
String channelName = "/service/out";
log.info("Channel Name = " + channelName);
log.info("bayeuxServer : " + (bayeuxServer == null ? "Is Null" : "Is Not Null"));
System.out.println("CHANNELS : " + bayeuxServer.getChannels().toString());
System.out.println("Subscribers on /service/in = "+bayeuxServer.getChannel("/service/in").getSubscribers().toString());
System.out.println("Subscribers on /service/out = "+bayeuxServer.getChannel("/service/out").getSubscribers().toString());
// convert to cometd format
Map<String, Object> data = new HashMap<String, Object>(4);
data.put("serverMsg", getDetails());
ServerChannel channel = bayeuxServer.getChannel(channelName);
subscribers = channel.getSubscribers().size();
log.info("Subscribers = " + subscribers);
log.info("channel = " + channel);
channel.publish(sender, data, null);
System.out.println("Session subscriptions :" + sender.getServerSession().getSubscriptions());
System.out.println("Listeners on /service/out = "+bayeuxServer.getChannel("/service/out").getListeners().toString());
System.out.println("Subscribers on /service/out = "+bayeuxServer.getChannel("/service/out").getSubscribers().toString());
}
But this one is not working
#Subscription("/service/out")
public void echo(Message message)
{
System.out.println("Echo service published " + message);
}
Logs:
1
7:48:18,775 INFO ClientHelloService [] Bayeux server =org.cometd.server.BayeuxServerImpl#1436088
17:48:18,775 INFO ClientHelloService [] Message = Hello world
17:48:18,775 INFO ClientHelloService [] remote Client Id = 3renjdwk25ercglzli36tudpl
17:48:18,776 INFO ClientHelloService [] Local session = L:_21w17u5f3mvvluvp71c27yaqvt
17:48:18,776 INFO ClientHelloService [] session = 3renjdwk25ercglzli36tudpl - last connect 1 ms ago
17:48:18,776 INFO ClientHelloService [] Channel Name = /service/out
17:48:18,776 INFO ClientHelloService [] bayeuxServer : Is Not Null
CHANNELS : [/service/out, /meta/subscribe, /service, /service/*, /meta, /meta/handshake, /meta/disconnect, /service/in, /meta/connect, /meta/unsubscribe]
Subscribers on /service/in = []
Subscribers on /service/out = []
17:48:18,777 INFO ClientHelloService [] msg = Hello world
17:48:18,777 INFO ClientHelloService [] Subscribers = 0
17:48:18,777 INFO ClientHelloService [] channel = /service/out
17:48:18,777 DEBUG 21192840 [] < {data={serverMsg=Hello world}, channel=/service/out}
17:48:18,777 INFO ClientHelloService [] publish the channel
Session subscriptions :[]
Listeners on /service/out = []
Subscribers on /service/out = []
Application.js
var sendChannel = '/service/in'; // Message from jsp
var receiveChannel = '/service/*'; // Message from server
/*var cometdServerURL = 'http://127.0.0.1:8080/cometd';*/
require(['dojox/cometd', 'dojo/dom', 'dojo/domReady!'], function(cometd, dom)
{ // // configuration object
// cometd.websocketEnabled = true;
// Open connection to CometD server
cometd.configure({
url: location.protocol + '//' + location.host + config.contextPath + '/cometd',
logLevel: 'debug'
});
cometd.addListener('/meta/*', function(message)
{
if (message.successful)
{
dom.byId('status').innerHTML += '<div>CometD handshake successful</div>';
cometd.subscribe(receiveChannel, function(message) {
dom.byId('results').innerHTML +=' Message from server ' + message.data;
dom.byId('results').innerHTML +=' Subscription to ' + receiveChannel;
});
}
else if(_connectionBroken()){
dom.byId('status').innerHTML += '<div>CometD Connection Broken</div>';
}
else
{
dom.byId('status').innerHTML += '<div>CometD handshake failed</div>';
}
});
dom.byId('greeter').onclick = function()
{
var text = dom.byId('msg').value;
cometd.publish(sendChannel, 'Hello world');
dom.byId('msg').value = "" ;
dom.byId('results').innerHTML +='Message send to server' ;
};
cometd.handshake();
});

There are several mistakes in your code, addressed below.
First, you don't want in your javascript to add a listener to /meta/* to handle subscriptions in the if (message.successful) branch. That branch will be executed for any meta message response, for example also for responses to subscriptions (that are sent over /meta/subscribe), executing the code multiple times when that is not the intention.
Change the listener to listen to /meta/handshake channel and perform the subscription in that listener.
Likewise you want to execute the if (_connectionBroken()) branch in a /meta/connect listener.
Please refer to the primer to build a proper skeleton of your application.
Also, follow the tutorials to better understand the roles of your listeners.
Second, it is not recommended that you subscribe, from the client, to service channels.
Have a read at the CometD concepts to understand the difference between a service channel and a broadcast channel, and about the difference between adding a listener and subscribing.
Third, when you have service channel, publish() is a local activity, so no message will be delivered to remote clients. The right API to use in this case is ServerSession.deliver(...), if you really want to use service channels.
Finally, your use case is covered by the tutorials, so I recommend you follow those and your application will work.
Hope that helped.

var sendChannel = '/service/in'; // Message from jsp
var receiveChannel = '/service/out'; // Message from server
/*var cometdServerURL = 'http://127.0.0.1:8080/cometd';*/
require(['dojox/cometd', 'dojo/dom', 'dojo/domReady!','dojo/_base/unload'], function(cometd, dom)
{ // // configuration object
// Open connection to CometD server
cometd.configure({
url: location.protocol + '//' + location.host + config.contextPath + '/cometd',
logLevel: 'debug'
});
cometd.addListener('/meta/connect', function(message) {
var wasConnected;
if(cometd.isDisconnected()) {
dom.byId('status').innerHTML +=' Disconnected from the server = ' + message;
} else{
/* dom.byId('status').innerHTML +=' Disconnected from the server = ' + message.data;*/
}
});
// listener for handshake
cometd.addListener('/meta/handshake', function(message)
{
if (message.successful)
{
dom.byId('status').innerHTML += '<div>CometD handshake successful</div>';
cometd.batch(function()
{
cometd.subscribe(receiveChannel, function(message) {
dom.byId('results').innerHTML +=' Message from server ' + message.data;
dom.byId('results').innerHTML +=' Subscription to ' + receiveChannel;
});
// cometd.publish('/service/in', { name: 'World' });
});
}
else if(_connectionBroken()){
dom.byId('status').innerHTML += '<div>CometD Connection Broken</div>';
}
else
{
dom.byId('status').innerHTML += '<div>CometD handshake failed</div>';
}
});
dom.byId('greeter').onclick = function()
{
var text = dom.byId('msg').value;
cometd.publish(sendChannel, 'Hello world');
dom.byId('msg').value = "" ;
dom.byId('results').innerHTML +='Message send to server' ;
};
cometd.handshake();
});
Now also couldnt publish to the channels
#Service
public class ClientHelloService {
static Logger log = Logger.getLogger(
ClientHelloService.class.getName());
String details;
#Inject
private BayeuxServer bayeuxServer;
#Session
private LocalSession sender;
#Session
private ClientSession bayeuxClient;
#Session
ServerSession session;
int subscribers;
// represent callback
#Listener("/service/in")
public void processClientHello(ServerSession session, ServerMessage message)
{
log.info("Bayeux server =" + bayeuxServer);
log.info("Message = " + message.getData());
log.info("remote Client Id = " + session.getId());
log.info("Local session = " + sender);
log.info("session = " + getSession());
// log.info("sender = " + sender);
details = (String) message.getData();
// session.deliver(sender,"/service/out",getDetails() , null);
testService();
}
private void testService() {
// Create the channel name using the symbol
String channelName = "/service/out";
log.info("Channel Name = " + channelName);
log.info("bayeuxServer : " + (bayeuxServer == null ? "Is Null" : "Is Not Null"));
// Initialize the channel, making it persistant
// new sendChannel
bayeuxServer.createIfAbsent(channelName, new ConfigurableServerChannel.Initializer() {
public void configureChannel(ConfigurableServerChannel channel) {
log.info("Configurable channel " + channel);
// channel exists even if it has no subscribers
channel.setPersistent(true);
channel.setLazy(true);
}
});
System.out.println("CHANNELS : " + bayeuxServer.getChannels().toString());
System.out.println("Subscribers on /service/out = "+bayeuxServer.getChannel("/service/out").getSubscribers().toString());
// convert to cometd format
Map<String, Object> data = new HashMap<String, Object>(4);
data.put("serverMsg", getDetails());
log.info("msg = " + data.get("serverMsg"));
// Publish the channel to all
ServerChannel channel = bayeuxServer.getChannel(channelName);
subscribers = channel.getSubscribers().size();
log.info("Subscribers = " + subscribers);
log.info("channel = " + channel);
// publish the message
try {
channel.publish(getSession(), data, null);
log.info("publish the channel");
}
catch (Exception e){
System.out.println(" Exception = " + e);
}
System.out.println("Session subscriptions :" + sender.getServerSession().getSubscriptions());
System.out.println("Listeners on /service/out = "+bayeuxServer.getChannel("/service/out").getListeners().toString());
System.out.println("Subscribers on /service/out = "+bayeuxServer.getChannel("/service/out").getSubscribers().toString());
publishData(data);
}
public ServerSession getSession() {
return session;
}
public void setSession(ServerSession session) {
this.session = session;
}
private void publishData(Map<String, Object> data) {
System.out.println("Published data = " + data);
}
/* #org.cometd.annotation.Subscription("/service/out")*/
#Subscription("/service/out")
public void echo(Message message)
{
System.out.println("Echo service published " + message);
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}

Related

set FCM high-priority when using firebase-admin

I have the following code which uses firebase-admin to send messages using Firebase cloud messaging
Message message = null;
message = Message.builder().putData("From", fromTel).putData("To", toTel).putData("Text", text)
.setToken(registrationToken).build();
String response = null;
try {
response = FirebaseMessaging.getInstance().sendAsync(message).get();
responseEntity = new ResponseEntity<String>(HttpStatus.ACCEPTED);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("Successfully sent message: " + response);
The above code works fine. But I need to send "high-priority" messages so that the device can receive them while in doze mode.
How can I make the messages "high-priority"?
For sending to Android devices, when building the message, set its AndroidConfig to a value that has Priority.HIGH:
AndroidConfig config = AndroidConfig.builder()
.setPriority(AndroidConfig.Priority.HIGH).build();
Message message = null;
message = Message.builder()
.putData("From", fromTel).putData("To", toTel).putData("Text", text)
.setAndroidConfig(config) // <= ADDED
.setToken(registrationToken).build();
For additional details, see the example in the documentation.
When sending to Apple devices, use setApnsConfig(), as explained in the documentation.
This may help somebody.
public String sendFcmNotification(PushNotificationRequestDto notifyRequest) throws FirebaseMessagingException {
String registrationToken = notifyRequest.getToken();
AndroidConfig config = AndroidConfig.builder()
.setPriority(AndroidConfig.Priority.HIGH).build();
Notification notification = Notification.builder()
.setTitle(notifyRequest.getTitle())
.setBody(notifyRequest.getBody())
.build();
Message message = Message.builder()
.setNotification(notification)
// .putData("foo", "bar")
.setAndroidConfig(config)
.setToken(registrationToken)
.build();
return FirebaseMessaging.getInstance().send(message);
}
public async Task send_PushNotification(FirebaseAdmin.Messaging.Message MESSAGE)
{
var defaultApp = FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "key_FB.json")),
});
var message = MESSAGE;
message.Token = FB_TOKEN;
message.Android = new AndroidConfig();
message.Android.Priority = Priority.High;
message.Android.TimeToLive = new TimeSpan(0,0,5);
var messaging = FirebaseMessaging.DefaultInstance;
var result = await messaging.SendAsync(message);
Console.WriteLine(result);
}
Without an AndroidConfig Builder
function sendFCM(token, from, to, text) {
var admin = require("firebase-admin");
var data = {
from: from,
to: to,
text: text
};
let message = {
data: data,
token: token,
android: {
priority: "high", // Here goes priority
ttl: 10 * 60 * 1000, // Time to live
}
};
admin.messaging()
.send(message)
.then((response) => {
// Do something with response
}).catch((error) => {
console.log(error);
});
}

Apple Notification Failed: ID=2, Code=ConnectionError

I'm using PushSharp 4.0.4, installed from NuGet
In the OnNotificationFailed(ApnsNotification, AggregateException) event of the Apns broker, I often get this exception:
Apple Notification Failed: ID=2, Code=ConnectionError .
According to me it appears due to p12 file. It may not have all the right to access by the external API.
private void SendPushNotification(string deviceToken, string message)
{
try
{
//Get Certificate
var appleCert = System.IO.File.ReadAllBytes(HttpContext.Current.Server.MapPath("Certificates.p12"));
//Configuration(NOTE: .pfx can also be used here)
var config = new ApnsConfiguration(ApnsConfiguration.ApnsServerEnvironment.Sandbox ,appleCert, "1234567890");
//Create a new broker
var apnsBroker = new ApnsServiceBroker(config);
//Wire up events
apnsBroker.OnNotificationFailed += (notification, aggregateEx) =>
{
aggregateEx.Handle(ex =>
{
// See what kind of exception it was to further diagnose
if (ex is ApnsNotificationException)
{
var notificationException = (ApnsNotificationException)ex;
// Deal with the failed notification
var apnsNotification = notificationException.Notification;
var statusCode = notificationException.ErrorStatusCode;
string desc = $"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}";
Console.WriteLine(desc);
lblStatus.Text = desc;
}
else
{
string desc = $"Apple Notification Failed for some unknown reason : {ex.InnerException}";
// Inner exception might hold more useful information like an ApnsConnectionException
Console.WriteLine(desc);
lblStatus.Text = desc;
}
// Mark it as handled
return true;
});
};
apnsBroker.OnNotificationSucceeded += (notification) =>
{
lblStatus.Text = "Apple Notification Sent successfully!";
};
var fbs = new FeedbackService(config);
fbs.FeedbackReceived += (string devicToken, DateTime timestamp) =>
{
// Remove the deviceToken from your database
// timestamp is the time the token was reported as expired
};
//Start Proccess
apnsBroker.Start();
if (deviceToken != "")
{
apnsBroker.QueueNotification(new ApnsNotification
{
DeviceToken = deviceToken,
Payload = JObject.Parse(("{\"aps\":{\"badge\":1,\"sound\":\"oven.caf\",\"alert\":\"" + (message + "\"}}")))
});
}
apnsBroker.Stop();
}
catch (Exception)
{
throw;
}
}
I'm using PushSharp 4.0.4, installed from NuGet .
To be run APNS push notification in C#.I got an error:
Apple Notification Failed: ID=1, Code=ConnectionError
Solution:
In this Error to Export key chain private key certificate .p12 format and place the certificate try again.To be get the out put.

How to intercept the headers from in call to one service and insert it to another request in gRPC-java?

I have two servers - HelloServer and WorldServer.
Both implement the same proto file:
// The greeting service definition.
service GreeterService {
// Sends a greeting
rpc GreetWithHelloOrWorld (GreeterRequest) returns (GreeterReply) {}
rpc GreetWithHelloWorld (GreeterRequest) returns (GreeterReply) {}
}
message GreeterRequest {
string id = 1;
}
// The response message containing the greetings
message GreeterReply {
string message = 1;
string id = 2;
}
I want to add traceIds to the requests. As far as I understand, this is achieved through adding the traceId in the Metadata object.
Here is the test I am using to check that the traceIds are passed along. The request is made to the HelloServer which in turns calls the WorldServer and then finally returns the response.
#Test
public void greetHelloWorld() {
String traceId = UUID.randomUUID().toString();
Metadata metadata = new Metadata();
metadata.put(MetadataKeys.TRACE_ID_METADATA_KEY, traceId);
Greeter.GreeterRequest greeterRequest = Greeter.GreeterRequest.newBuilder().setId(traceId).build();
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 8080)
.usePlaintext(true)
.build();
AtomicReference<Metadata> trailersCapture = new AtomicReference<>();
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
ClientInterceptor clientInterceptor = MetadataUtils.newAttachHeadersInterceptor(metadata);
ClientInterceptor metadataCapturingClientInterceptor = MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture);
GreeterServiceBlockingStub blockingStub = GreeterServiceGrpc.newBlockingStub(ClientInterceptors.intercept(channel, clientInterceptor, metadataCapturingClientInterceptor));
GreeterServiceStub asyncStub = GreeterServiceGrpc.newStub(channel);
try {
Greeter.GreeterReply greeterReply = blockingStub.greetWithHelloWorld(greeterRequest);
String idInResponse = greeterReply.getId();
String idInHeaders = headersCapture.get().get(MetadataKeys.TRACE_ID_METADATA_KEY);
logger.info("Response from HelloService and WorldService -- , id = {}, headers = {}", greeterReply.getMessage(), idInResponse, idInHeaders);
assertEquals("Ids in response and header did not match", idInResponse, idInHeaders);
} catch (StatusRuntimeException e) {
logger.warn("Exception when calling HelloService and WorldService\n" + e);
fail();
} finally {
channel.shutdown();
}
}
Implementation of ServerInterceptor:
#Override
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
String traceId = headers.get(MetadataKeys.TRACE_ID_METADATA_KEY);
logger.info("objId=" + this.toString().substring(this.toString().lastIndexOf('#')) + " Trace id -- 1=" + headers.get(MetadataKeys.TRACE_ID_METADATA_KEY));
return next.startCall(new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
#Override
public void sendHeaders(Metadata headers) {
headers.put(MetadataKeys.TRACE_ID_METADATA_KEY, traceId);
logger.info("objId=" + this.toString().substring(this.toString().lastIndexOf('#')) + " Trace id -- 2 " + headers.get(MetadataKeys.TRACE_ID_METADATA_KEY));
super.sendHeaders(headers);
}
#Override
public void sendMessage(RespT message) {
logger.info("objId=" + this.toString().substring(this.toString().lastIndexOf('#')) + " message=" + message.toString());
super.sendMessage(message);
}
}, headers);
Here is the implementation of the greetWithHelloWorld() method:
public void greetWithHelloWorld(com.comcast.manitoba.world.hello.Greeter.GreeterRequest request,
io.grpc.stub.StreamObserver<com.comcast.manitoba.world.hello.Greeter.GreeterReply> responseObserver) {
Greeter.GreeterRequest greeterRequest = Greeter.GreeterRequest.newBuilder().setId(request.getId()).build();
Metadata metadata = new Metadata();
metadata.put(MetadataKeys.TRACE_ID_METADATA_KEY, "");
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8081)
.usePlaintext(true).build();
AtomicReference<Metadata> trailersCapture = new AtomicReference<>();
AtomicReference<Metadata> headersCapture = new AtomicReference<>();
ClientInterceptor clientInterceptor = MetadataUtils.newAttachHeadersInterceptor(metadata);
ClientInterceptor metadataCapturingClientInterceptor = MetadataUtils.newCaptureMetadataInterceptor(headersCapture, trailersCapture);
GreeterServiceGrpc.GreeterServiceBlockingStub blockingStub = GreeterServiceGrpc.newBlockingStub(ClientInterceptors.intercept(channel, clientInterceptor, metadataCapturingClientInterceptor));
String messageFromWorldService = "";
String replyIdFromWorldService = "";
try {
Greeter.GreeterReply greeterReply = blockingStub.greetWithHelloOrWorld(greeterRequest);
messageFromWorldService = greeterReply.getMessage();
replyIdFromWorldService = greeterReply.getId();
logger.info("Response from WorldService -- {}, id = {}", messageFromWorldService, replyIdFromWorldService);
} catch (StatusRuntimeException e) {
logger.warn("Exception when calling HelloService\n" + e);
}
Greeter.GreeterReply greeterReply = Greeter.GreeterReply.newBuilder().setMessage("Hello" + messageFromWorldService).setId(replyIdFromWorldService).build();
responseObserver.onNext(greeterReply);
responseObserver.onCompleted();
}
The problem is in the greetWithHelloWorld() method, I don't have access to Metadata, so I cannot extract the traceId from the header and attach it to the request to the World server. However, if I put a breakpoint in that method, I can see that request object does have the traceId in it which is private to it and unaccessible.
Any ideas how can I achieve this? Also, is this the best way to do pass traceIds around? I found some references to using Context. What is the difference between Context and Metadata?
The expected approach is to use a ClientInterceptor and ServerInterceptor. The client interceptor would copy from Context into Metadata. The server interceptor would copy from Metadata to Context. Use Contexts.interceptCall in the server interceptor to apply the Context all callbacks.
Metadata is for wire-level propagation. Context is for in-process propagation. Generally the application should not need to interact directly with Metadata (in Java).

Alexa skill and Azure AD authentication

I am trying to build an alexa skill that connects to an enterprise app that uses Azure AD authentication. We set everything up like in this article https://blogs.msdn.microsoft.com/premier_developer/2016/12/12/amazon-alexa-skills-development-with-azure-active-directory-and-asp-net-core-1-0-web-api/, but I am having problems with moving the token from the body of the request to the headers.
The request comes through fine, I can parse it, I add the token to the header but on the test page in amazon I get this message: "There was an error calling the remote endpoint, which returned HTTP 302 : Found"
This is the code for adding the token to the headers:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.Use(async (context, next) =>
{
string path = string.Format(ConfigurationManager.AppSettings["ExchangeTraceDrivePath"], "AlexaRequest", DateTime.Now.ToFileTime(), "");
var stream = context.Request.Body;
try
{
using (var buffer = new MemoryStream())
{
await stream.CopyToAsync(buffer);
var bodyBuffer = new byte[buffer.Length];
buffer.Position = 0L;
buffer.Read(bodyBuffer, 0, bodyBuffer.Length);
var body = Encoding.UTF8.GetString(bodyBuffer);
using (var sw = new StreamWriter(path))
{
sw.WriteLine(DateTime.Now.ToString() + " body: " + body);
sw.WriteLine("---------------------------------------------------------------------------------------------");
foreach (var header in context.Request.Headers)
{
sw.WriteLine(DateTime.Now.ToString() + " header key: " + header.Key);
foreach (var val in header.Value)
{
sw.WriteLine(DateTime.Now.ToString() + " header value: " + val);
}
}
sw.WriteLine("---------------------------------------------------------------------------------------------");
dynamic json = JObject.Parse(body);
sw.WriteLine(DateTime.Now.ToString() + " parsed body: " + json);
sw.WriteLine("---------------------------------------------------------------------------------------------");
if (json?.session?.user?.accessToken != null)
{
sw.WriteLine(DateTime.Now.ToString() + " access accessToken found " +
json?.session?.user?.accessToken);
sw.WriteLine("---------------------------------------------------------------------------------------------");
context.Request.Headers.Add("Authorization",
new string[] { string.Format("Bearer {0}", json?.session?.user?.accessToken) });
foreach (var header in context.Request.Headers)
{
sw.WriteLine(DateTime.Now.ToString() + " header key: " + header.Key);
foreach (var val in header.Value)
{
sw.WriteLine(DateTime.Now.ToString() + " header value: " + val);
}
}
sw.WriteLine("---------------------------------------------------------------------------------------------");
}
buffer.Position = 0L;
context.Request.Body = buffer;
}
}
}
catch
{
}
finally
{
await next.Invoke();
// Restore the original stream.
context.Request.Body = stream;
}
});
//ExpireTimeSpan and SlidinExpiration only work when UseTokenLifetime = false
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// This is NOT ASP.NET Session Timeout (that should be set to same value in web.config)
// This is the expiration on the cookie that holds the Azure AD token
ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToDouble(expirationTimeSpan)),
// Set SlidingExpiration=true to instruct the middleware to re-issue a new cookie
// with a new expiration time any time it processes a request which is more than
// halfway through the expiration window.
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
// This method is called every time the cookie is authenticated, which
// is every time a request is made to the web app
OnValidateIdentity = CookieAuthNotification.OnValidateIdentity
}
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
UseTokenLifetime = false,
/*
* Skipping the Home Realm Discovery Page in Azure AD
* http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/
*/
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OpenIdConnectNotification.RedirectToIdentityProvider,
MessageReceived = OpenIdConnectNotification.MessageReceived,
SecurityTokenReceived = OpenIdConnectNotification.SecurityTokenReceived,
SecurityTokenValidated = OpenIdConnectNotification.SecurityTokenValidated,
AuthorizationCodeReceived = OpenIdConnectNotification.AuthorizationCodeReceived,
AuthenticationFailed = OpenIdConnectNotification.AuthenticationFailed
},
});
}
I ended up creating a separate middleware to move the token from the body to the header
public class AlexaJWTMiddleware : OwinMiddleware
{
private readonly OwinMiddleware _next;
public AlexaJWTMiddleware(OwinMiddleware next) : base(next)
{
_next = next;
}
public override Task Invoke(IOwinContext context)
{
var stream = context.Request.Body;
if (context.Request.Headers.ContainsKey("SignatureCertChainUrl")
&& context.Request.Headers["SignatureCertChainUrl"]
.Contains("https://s3.amazonaws.com/echo.api/echo-api-cert-4.pem")
&& !context.Request.Headers.ContainsKey("Authorization"))
{
try
{
using (var buffer = new MemoryStream())
{
stream.CopyToAsync(buffer);
var bodyBuffer = new byte[buffer.Length];
buffer.Position = 0L;
buffer.Read(bodyBuffer, 0, bodyBuffer.Length);
var body = Encoding.UTF8.GetString(bodyBuffer);
dynamic json = JObject.Parse(body);
if (json?.session?.user?.accessToken != null)
{
context.Request.Headers.Add("Authorization",
new string[] { string.Format("Bearer {0}", json?.session?.user?.accessToken) });
}
buffer.Position = 0L;
context.Request.Body = buffer;
}
}
catch
{
}
finally
{
// Restore the original stream.
context.Request.Body = stream;
}
}
else
{
return _next.Invoke(context);
}
return _next.Invoke(context);
}
}
and then adding jwt authentication besides the openId one
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.Use(typeof(AlexaJWTMiddleware));
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = domain,
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:AppIdUri"]
},
AuthenticationType = "OAuth2Bearer",
});
//ExpireTimeSpan and SlidinExpiration only work when UseTokenLifetime = false
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// This is NOT ASP.NET Session Timeout (that should be set to same value in web.config)
// This is the expiration on the cookie that holds the Azure AD token
ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToDouble(expirationTimeSpan)),
// Set SlidingExpiration=true to instruct the middleware to re-issue a new cookie
// with a new expiration time any time it processes a request which is more than
// halfway through the expiration window.
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
// This method is called every time the cookie is authenticated, which
// is every time a request is made to the web app
OnValidateIdentity = CookieAuthNotification.OnValidateIdentity
}
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
UseTokenLifetime = false,
/*
* Skipping the Home Realm Discovery Page in Azure AD
* http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/
*/
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OpenIdConnectNotification.RedirectToIdentityProvider,
MessageReceived = OpenIdConnectNotification.MessageReceived,
SecurityTokenReceived = OpenIdConnectNotification.SecurityTokenReceived,
SecurityTokenValidated = OpenIdConnectNotification.SecurityTokenValidated,
AuthorizationCodeReceived = OpenIdConnectNotification.AuthorizationCodeReceived,
AuthenticationFailed = OpenIdConnectNotification.AuthenticationFailed
},
});
}

How to send a push notification to more than one device (iOS)?

I'm trying to optimize the push notifications on my server. For now I send those one by one (with an old library) and it takes a while (4 hours).
I refactored my service to send a notification with a lot of device tokens (For now I tried with batches of 500 tokens). For that I'm using the Redth/PushSharp library. I followed the sample code then I adapted it to send the notifications to severals device tokens.
PushService service = new PushService();
//Wire up the events
service.Events.OnDeviceSubscriptionExpired += new PushSharp.Common.ChannelEvents.DeviceSubscriptionExpired(Events_OnDeviceSubscriptionExpired);
service.Events.OnDeviceSubscriptionIdChanged += new PushSharp.Common.ChannelEvents.DeviceSubscriptionIdChanged(Events_OnDeviceSubscriptionIdChanged);
service.Events.OnChannelException += new PushSharp.Common.ChannelEvents.ChannelExceptionDelegate(Events_OnChannelException);
service.Events.OnNotificationSendFailure += new PushSharp.Common.ChannelEvents.NotificationSendFailureDelegate(Events_OnNotificationSendFailure);
service.Events.OnNotificationSent += new PushSharp.Common.ChannelEvents.NotificationSentDelegate(Events_OnNotificationSent);
service.Events.OnChannelCreated += new PushSharp.Common.ChannelEvents.ChannelCreatedDelegate(Events_OnChannelCreated);
service.Events.OnChannelDestroyed += new PushSharp.Common.ChannelEvents.ChannelDestroyedDelegate(Events_OnChannelDestroyed);
//Configure and start ApplePushNotificationService
string p12Filename = ...
string p12FilePassword = ...
var appleCert = File.ReadAllBytes(p12Filename);
service.StartApplePushService(new ApplePushChannelSettings(true, appleCert, p12FilePassword));
var appleNotification = NotificationFactory.Apple();
foreach (var itemToProcess in itemsToProcess)
{
itemToProcess.NotificationDateTime = DateTime.Now;
mobile.SubmitChanges();
string deviceToken = GetCleanDeviceToken(itemToProcess.MobileDevice.PushNotificationIdentifier);
appleNotification.ForDeviceToken(deviceToken);
}
service.QueueNotification(appleNotification
.WithAlert(itemsToProcess[0].MobileDeviceNotificationText.Text)
.WithSound("default")
.WithBadge(0)
.WithCustomItem("View", itemsToProcess[0].Value.ToString()));
//Stop and wait for the queues to drains
service.StopAllServices(true);
Then I tried to send 3 notifications to 2 devices. Only the first device got them (and the problem is not device-related because I tried with both of them separately).
Right after that an OperationCanceledException is thrown in the PushChannelBase class. So I don't know what's wrong. Any idea?
You should queue a separate notification for each item to process.
It is not possible set multiple device tokens on a single notification. The OperationCanceledException will occur, because you do.
Example: Console C# Application
This assumes
you have valid production and development certificates
you have stored multiple device tokens within your database
you have a notification that comes from your database
You are using PushSharp Library
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PushSharp;
using PushSharp.Core;
using PushSharp.Apple;
using System.IO;
namespace MyNotification
{
class Program
{
//args may take "true" or "false" to indicate the app is running for
//development or production (Default = false which means Development)
static void Main(string[] args)
{
bool isProduction = false;
if (args != null && args.Length == 1)
{
Console.Write(args[0] + Environment.NewLine);
bool.TryParse(args[0], out isProduction);
}
try
{
//Gets a notification that needs sending from database
AppNotification notification = AppNotification.GetNotification();
if (notification != null && notification.ID > 0)
{
//Gets all devices to send the above notification to
List<IosDevice> devices = IosDevice.GetDevices(!isProduction);
if (devices != null && devices.Count > 0)
{
PushBroker push = new PushBroker();//a single instance per app
//Wire up the events for all the services that the broker registers
push.OnNotificationSent += NotificationSent;
push.OnChannelException += ChannelException;
push.OnServiceException += ServiceException;
push.OnNotificationFailed += NotificationFailed;
push.OnDeviceSubscriptionExpired += DeviceSubscriptionExpired;
push.OnChannelCreated += ChannelCreated;
push.OnChannelDestroyed += ChannelDestroyed;
//make sure your certifcates path are all good
string apnsCertFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../Certificate/Certificates_Apple_Push_Production.p12");
if (!isProduction)
apnsCertFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../Certificate/Certificates_Apple_Push_Development.p12");
var appleCert = File.ReadAllBytes(apnsCertFile);
push.RegisterAppleService(new ApplePushChannelSettings(isProduction, appleCert, "135TrID35")); //Extension method
foreach (IosDevice device in devices)
{
//if it is required to send additional information as well as the alert message, uncomment objects[] and WithCustomItem
//object[] obj = { "North", "5" };
push.QueueNotification(new AppleNotification()
.ForDeviceToken(device.DeviceToken)
.WithAlert(DateTime.Now.ToString())//(notification.AlertMessage)
//.WithCustomItem("Link", obj)
.WithBadge(device.BadgeCount + 1)
.WithSound(notification.SoundFile));//sound.caf
}
push.StopAllServices(waitForQueuesToFinish: true);
}
}
Console.WriteLine("Queue Finished, press return to exit...");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
static void NotificationSent(object sender, INotification notification)
{
Console.WriteLine("Sent: " + sender + " -> " + notification);
}
static void NotificationFailed(object sender, INotification notification, Exception notificationFailureException)
{
Console.WriteLine("Failure: " + sender + " -> " + notificationFailureException.Message + " -> " + notification);
}
static void ChannelException(object sender, IPushChannel channel, Exception exception)
{
Console.WriteLine("Channel Exception: " + sender + " -> " + exception);
}
static void ServiceException(object sender, Exception exception)
{
Console.WriteLine("Service Exception: " + sender + " -> " + exception);
}
static void DeviceSubscriptionExpired(object sender, string expiredDeviceSubscriptionId, DateTime timestamp, INotification notification)
{
Console.WriteLine("Device Subscription Expired: " + sender + " -> " + expiredDeviceSubscriptionId);
}
static void ChannelDestroyed(object sender)
{
Console.WriteLine("Channel Destroyed for: " + sender);
}
static void ChannelCreated(object sender, IPushChannel pushChannel)
{
Console.WriteLine("Channel Created for: " + sender);
}
}
}

Resources