I'm creating a Grails application which makes use of the Atmosphere plugin to push data to the browser. However I'm having trouble in creating a broadcast channel to a single user (the session's user). My code is has follows:
Service:
static atmosphere = [mapping: '/atmosphere/recommend']
def onRequest = { event ->
def request = event.request
def response = event.response
event.suspend()
def broadcaster = event.broadcaster
request.session.broadcaster = broadcaster
broadcaster.broadcasterConfig.addFilter(new XSSHtmlFilter())
}
def onStateChange = { event ->
if (!event.message) return
event.resource.response.writer.with {
write "<script>parent.callback('${event.message}');</script>"
flush()
}
}
Controller:
def builder = new JSONBuilder()
def jsonResult = builder.build{
artist = artistInstance
location = {
lat = eventInstance.location.lat
lng = eventInstance.location.lng
}
}
session.broadcaster.broadcast(jsonResult)
This solution broadcasts the jsonResult to every user. What I want to achieve is to broadcast only for the current user.
Any ideas?
If you need more details just let me know.
Thanks
I thinks you can use session to share the onRequest's event.
request.session[userId]=event
then in controller :
broadcast(jsonResult,session[userId])
When you define your events in grails, you can filter out cients by using a browserFilter closure.
Hey you can make use of the uuid which is assigned to each Atmosphere Resource.
To retrieve the suspended uuid based on an Atmosphere Request you can do:
String suspendedUUID = (String)req.getAttribute(ApplicationConfig.SUSPENDED_ATMOSPHERE_RESOURCE_UUID);
You can pass the session
session.broadcaster.broadcast(jsonResult, AtmosphereResource)
See this API.
(I am Atmosphere's creator).
Related
I'm trying to track the sms delivery status of the messages I send using the bot framework. I'm using Twilio, and sending proactive messages. Right now I'm trying to do so with twilio status callbacks
This is similar to this question, I tried that approach but I couldn't get it to work. I've added my url on the TwiML app and it is not firing. I have double and triple checked, and I suspect this url is somehow ignored or not going through with my current set up. I don't get any callbacks on the proactive message nor on the replies the bot sends to the user. However the flow works fine and I can reply and get proper responses from the bot. Edit: calling this "approach 1"
approach 2: I've also tried this doing some light modifications on Twilio adapter, to be able to add my callback just before create message. (I changed it so it uses a customized client wrapper that adds my callback url when creating the twilio resource) This does work, partially: when I reply a message from my bot, I get the status callbacks. But as the proactive message is sent using the default adapter, I don't get a callback on the initial message.
approach 3: Finally, I also tried using the TwilioAdapter when sending the proactive message but for some reason as soon as I send an activity, the TurnContext is disposed, so I can't save the state or do any subsequent actions. This leads me to believe twilio adapter is not intended to be used this way (can't be used on proactive messages), but I'm willing to explore this path if necessary.
Here is the modified Twilio Adapter:
public class TwilioAdapterWithErrorHandler : TwilioAdapter
{
private const string TwilioNumberKey = "TwilioNumber";
private const string TwilioAccountSidKey = "TwilioAccountSid";
private const string TwilioAuthTokenKey = "TwilioAuthToken";
private const string TwilioValidationUrlKey = "TwilioValidationUrl";
public TwilioAdapterWithErrorHandler(IConfiguration configuration, ILogger<TwilioAdapter> logger, TwilioAdapterOptions adapterOptions = null)
: base(
new TwilioClientWrapperWithCallback(new TwilioClientWrapperOptions(configuration[TwilioNumberKey], configuration[TwilioAccountSidKey], configuration[TwilioAuthTokenKey], new Uri(configuration[TwilioValidationUrlKey]))), adapterOptions, logger)
{
OnTurnError = async (turnContext, exception) =>
{
// Log any leaked exception from the application.
logger.LogError(exception, $"[OnTurnError] unhandled error : {exception.Message}");
Task[] tasks = {
// Send a message to the user
turnContext.SendActivityAsync("We're sorry but this bot encountered an error when processing your answer."),
// Send a trace activity, which will be displayed in the Bot Framework Emulator
turnContext.TraceActivityAsync("OnTurnError Trace", exception.Message, "https://www.botframework.com/schemas/error", "TurnError")
};
Task all = Task.WhenAll(tasks); //task with the long running tasks
await Task.WhenAny(all, Task.Delay(5000)); //wait with a timeout
};
}
}
Modified client Wrapper:
public class TwilioClientWrapperWithCallback : TwilioClientWrapper
{
public TwilioClientWrapperWithCallback(TwilioClientWrapperOptions options) : base(options) { }
public async override Task<string> SendMessageAsync(TwilioMessageOptions messageOptions, CancellationToken cancellationToken)
{
var createMessageOptions = new CreateMessageOptions(messageOptions.To)
{
ApplicationSid = messageOptions.ApplicationSid,
MediaUrl = messageOptions.MediaUrl,
Body = messageOptions.Body,
From = messageOptions.From,
};
createMessageOptions.StatusCallback = new System.Uri("https://myApp.ngrok.io/api/TwilioSms/SmsStatusUpdated");
var messageResource = await MessageResource.CreateAsync(createMessageOptions).ConfigureAwait(false);
return messageResource.Sid;
}
}
Finally, here's my summarized code that sends the proactive message:
[HttpPost("StartConversationWithSuperBill/{superBillId:long}")]
[HttpPost("StartConversationWithSuperBill/{superBillId:long}/Campaign/{campaignId:int}")]
public async Task<IActionResult> StartConversation(long superBillId, int? campaignId)
{
ConversationReference conversationReference = this.GetConversationReference("+17545517768");
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
{
await turnContext.SendActivityAsync("proactive message 1");
//this code was edited for brevity. Here I would start a new dialog that would cascade into another, but the end result is the same, as soon as a message is sent, the turn context is disposed.
await turnContext.SendActivityAsync("proactive message 2"); //throws ObjectDisposedException
}, default(CancellationToken));
var result = new { status = "Initialized fine!" };
return new JsonResult(result);
}
private ConversationReference GetConversationReference(string targetNumber)
{
string fromNumber = "+18632704234";
return new ConversationReference
{
User = new ChannelAccount { Id = targetNumber, Role = "user" },
Bot = new ChannelAccount { Id = fromNumber, Role = "bot" },
Conversation = new ConversationAccount { Id = targetNumber },
//ChannelId = "sms",
ChannelId = "twilio-sms", //appparently when using twilio adapter we need to set this. if using TwiML app and not using Twilio Adapter, use the above. Otherwise the frameworks interprets answers from SMS as new conversations instead.
ServiceUrl = "https://sms.botframework.com/",
};
}
(I can see that I could just call create conversation reference and do two callbacks, one for each message, but in my actual code I'm creating a dialog that sends one message and then invokes another dialog that starts another message)
Edit 2:
Some clarifications:
On approach 2, I'm using two adapters, as suggested by code sample and documentation on using twilio adapter. The controller that starts the proactive message uses an instance of a default adapter (similar to this one), and TwilioController (the one that gets the twilio incoming messages) uses TwilioAdapterWithErrorHandler.
On approach 3, I excluded the default adapter, and both controllers use TwilioAdapterWithErrorHandler.
Edit 3:
Here's a small repo with the issue.
I found a fix for this problem, around approach 3, by changing the overload I use for ContinueConversation. Replace this :
//Start a new conversation.
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, async (turnContext, token) =>
With this:
//Start a new conversation.
var twilioAdapter = (TwilioAdapterWithErrorHandler)_adapter;
await twilioAdapter.ContinueConversationAsync(_appId, conversationReference, async (context, token) =>
This way, the context is not disposed, an I can use the twilio adapter for the proactive message and have status callbacks on all messages.
i've written an Azure function and connected the output to a notification-hub to send push notifications using APNS. It works fine as long as i send the notification to all registered devices, but i don't know how to use tags in order to address a specific user. If i try to use a tag, i get an error message saying "Exception while executing function: Functions.SendSinglePushNotification. Microsoft.Azure.WebJobs.Host: Error while handling parameter notification after function returned:. Microsoft.Azure.NotificationHubs: notification.Tag property should be null."
Here's my code so far:
#r "Microsoft.Azure.NotificationHubs"
#r "Newtonsoft.Json"
using System;
using Microsoft.Azure.NotificationHubs;
using Newtonsoft.Json;using
Microsoft.Azure.WebJobs.Host.Bindings.Runtime;
public static void Run(HttpRequestMessage req, TraceWriter log,Binder
binder, out AppleNotification notification)
{
string user = "Test";
string tagExpression = "Test";
string userTag = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "userid", true) == 0)
.Value;
string apnsNotificationPayload = "{\"aps\": {\"alert\": \"Test: (" + user + ")\" }}";
notification = new AppleNotification(apnsNotificationPayload);
}
I was trying to use notification = new
AppleNotification(apnsNotificationPayload,tagExpression);
but that does not work. How can i achieve that?
Thanks a lot and best regards
I had similar issue. Eventually, what worked for me was constructing Notification client manually. I am developing functions in Visual Studio, so my code is slightly different from yours.
[FunctionName("MyFunction")]
public static async Task Run([ServiceBusTrigger("queuename", AccessRights.Listen, Connection =
"<connection-settings-name>")] string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
var notificationHubSas = "<DefaultFullSharedAccessSignature from Azure portal>";
var notificationHubName = "myhub";
var nhClient = NotificationHubClient.CreateClientFromConnectionString(notificationHubSas, notificationHubName);
var tags = "";
await nhClient.SendTemplateNotificationAsync(<notification payload>, tags);
}
Is there some function in TweetSharp that could be used in a similar way to my 'IsFollowingMe' below?
I'd like to check whether a user is following me before I attempt to send some private message.
TweetSharp.TwitterService service;
string screenName = "#some_one";
string someMessage = "Some Message";
if (service.IsFollowingMe(screenName))
{
service.SendDirectMessage(screenName, someMessage);
else
NotifyThatSendingNotPossible();
}
First option to such approach is to use:
TweetSharp.TwitterService service;
TwitterCursorList<TwitterUser> followers = service.ListFollowers();
and then iterate through the result to find out if user is following my account. But this will eventually be ineffective when there are a lot of followers.
Another option is to execute service.SendDirectMessage and then check if the result is null or not. I tested such approach with success - however my application logic prefers to check in advance if sending is possible and based on this information should do different actions.
The answer is as follows:
TweetSharp.TwitterService service;
string fromUser = "#mr_sender";
string toUser = "#some_one";
string someMessage = "Some Message";
TweetSharp.TwitterFriendship friendship =
service.GetFriendshipInfo(fromUser, toUser);
if (friendship.Relationship.Source.CanDirectMessage.HasValue &&
friendship.Relationship.Source.CanDirectMessage.Value)
{
service.SendDirectMessage(screenName, someMessage);
}
else
{
NotifyThatSendingNotPossible();
}
I could able to solve this using below way.
var twitterFriendship = service.GetFriendshipInfo(new GetFriendshipInfoOptions() { SourceScreenName = "source_name", TargetScreenName = "target_name"});
After that, you can check like below
if(twitterFriendship.Relationship.Source.Following && twitterFriendship.Relationship.Target.FollowedBy)
{
service.SendDirectMessage(new SendDirectMessageOptions() { ScreenName="target_name",Text="message"})
}
I'm trying to extend the Groovy Mag Atmosphere Example (https://github.com/rbramley/GroovyMagJMS) to broadcast to different clients. (Like in Broadcasting to a subset of subscribers in Atmosphere)
A client connects with url http://localhost:8080/GrailsTest/atmosphere/messages/?id=1. An id will be passed to the server. The new added lookupBroadcaster Method creates a new Broadcaster Object with the id. When I wanna broadcast a message, the client does not receive the result.
Can somebody help me and maybe try it out?
I'm added the atmosphere 0.8.2 library to BuildConfig.groovy to use mappings like '/atmosphere/messages/*'.
dependencies {
runtime 'org.atmosphere:atmosphere-runtime:0.8.2'
}
class AtmosphereService {
static transactional = false
static atmosphere = [mapping: '/atmosphere/messages/*']
static exposes = ['jms']
#Subscriber(topic='msgevent')
def onEvent(msg) {
println 'onevent'
def payload = msg
if(msg instanceof Map) {
// convert map messages to JSON
payload = msg.encodeAsJSON()
}
Broadcaster b = lookupBroadcaster(msg["id"], false);
b.broadcast(payload)
return null
}
Broadcaster lookupBroadcaster(String id, Boolean createBroadcast) {
return BroadcasterFactory.getDefault().lookup(id, createBroadcast)
}
def onRequest = { event ->
def req = event.request
def id = req.getParameter("id")
Broadcaster b = lookupBroadcaster(id, true);
event.setBroadcaster(b);
b.addAtmosphereResource(event)
event.suspend()
}
def onStateChange = { event ->
if (event.message) {
log.info "onStateChange, message: ${event.message}"
if (event.isSuspended()) {
event.resource.response.writer.with {
write "<script>parent.callback('${event.message}');</script>"
flush()
}
event.resume()
}
}
}
}
That should work based on that code snippet. Is the onStateChange() method invoked when you broadcast? Since you are resuming, the first broadcast will works but after that the AtmosphereResource will be removed from its associated Broadcaster, hence no more update.
I'm using the Atmosphere plugin in a Grails application to make Ajax push calls to the client. The basic architecture is, I have a loop in the server which creates the data that I want to push to the browser, so in every iteration it uses the atmosphere broadcast() method to send the data to the client.
It works fine when I use it outside the loop, like this:
def builder = new JSONBuilder()
def jsonResult = builder.build{
artist = "incubus"
location = {
lat = 45.678909
lng = -14.45667
}
}
broadcaster['/atmosphere/recommend'].broadcast(jsonResult)
However, when I use it programmatically inside the loop, the browser throws the error: An invalid or illegal string was specified" code: "12, and doesn't work properly.
A simplified example of the loop is as follows:
[[lat:45.678909,lng:-14.45667],[lat:32.56433,lng:22.4566]].each{
def builder = new JSONBuilder()
def jsonResult = builder.build{
artist = "incubus"
location = {
lat = '"${it.lat}"'
lng = '"${it.lng}"'
}
}
broadcaster['/atmosphere/recommend'].broadcast(jsonResult)
}
Any ideas why is this happening?
Thanks!
I think it should work, if you remove the quotes.
location = {
lat = it.lat
lng = it.lng
}
Christian