Is there any way to send a push notification from server when a user complete a task? For example: A todo app will notify on that date with push notification. I want to use firebase and firestore for storing user tokens.
Alarm manager can be a solution that I have found but I don't wanna use it.
Yes, you can use scheduler to send notification from server to your app:
You may follow my working code:
Emplement IJob:
public class SendNotificationViaFcm: IJob
{
public void Execute(IJobExecutionContext context)
{
bool isNotificationSent=false;
try
{
var taskToSendNotification = FirebaseCloudMessaging.SendMessage();
Task.WaitAll(taskToSendNotification);
isNotificationSent = taskToSendNotification.Result;
}
catch (Exception exception)
when (
exception is ObjectDisposedException || exception is ArgumentNullException ||
exception is AggregateException)
{
}
catch (Exception exception) when (exception is InvalidOperationException)
{
}
catch (Exception exception)
{
// ignored
}
}
}
Call FCM Api from your server:
public class FirebaseCloudMessaging
{
private static readonly Uri FcmUri = new Uri(
uriString: #"https://fcm.googleapis.com",
uriKind: UriKind.Absolute);
private const string FcmApiKey = "Your Legacy Server Key";
public static async Task<bool> SendMessage()
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = FcmUri;
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
"key=" + FcmApiKey);
var response = await httpClient.PostAsJsonAsync(#"/fcm/send", new
{
to = "/topics/global",
priority = "high",
data = new
{
title = "Warning",
message = "Please start app to track movemoent!"
}
//to = "/topics/global",
//priority = "high",
//notification = new
//{
// title = "Warning!",
// body = "Please start app to track movemoent!"
//}
});
Debug.Write(response.Content.ReadAsStringAsync());
var ck = response.IsSuccessStatusCode;
return response.IsSuccessStatusCode;
}
}
}
Implement schedular for your time interval:
public class Scheduler
{
public static void Start()
{
try
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
scheduler.Start();
// scheduler.Shutdown();
var sentCloudNotification = JobBuilder.Create<SendNotificationViaFcm>().Build();
var cloudNotificationTrigger = TriggerBuilder.Create().WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever()).Build();
scheduler.ScheduleJob(sentCloudNotification, cloudNotificationTrigger);
}
catch (SchedulerException exception)
{
Debug.Write(exception.Message);
}
catch (Exception exception)
{
Debug.Write(exception.Message);
}
}
}
Finally Run in from your Global.asax.cs
protected void Application_Start()
{
Scheduler.Start();
}
It sounds like you're looking for a tool that allows you to schedule transactional notifications. What sort of server technology are you using?
From a high level you could do something like this:
1) user adds a task in the Android application
2) android application sends request to server to save the task
3) you have some code that runs in some sort of on task save callback that schedules a block of code to run in the future using crontab, celery or something similar.
4) the block of code that runs in the future is an api call to twilio to send a push notification
relevant links: https://www.twilio.com, https://firebase.google.com/docs/cloud-messaging/, http://www.celeryproject.org/, https://en.wikipedia.org/wiki/Cron
Related
When I first log into my app, I go through the following code:
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
Which triggers this activity:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.enigmadream.storyvoque" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}
And finally this code to link the account to Cognito:
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:79ebf8e1-97de-4d1c-959a-xxxxxxxxxxxx";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
{
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
}
else
ShowMessage(task.Exception.InnerException != null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
This all works great, and after the login, I am able to use my identity/credentials to retrieve data from DynamoDB. With this object:
Amazon.DynamoDBv2.AmazonDynamoDBClient ddbc = new Amazon.DynamoDBv2.AmazonDynamoDBClient(credentials, RegionEndpoint.USEast2);
The second time I run my app, this code runs:
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
ShowMessage(string.Format("I still remember you're {0} ", idToken));
And if I try to use the credentials with DynamoDB (or anything, I assume) at this point, I get errors that I don't have access to the identity. I have to logout (credentials.Clear()) and login again to obtain proper credentials.
I could require that a user go through the whole login process every time my app runs, but that's a real pain because the Google login process requires the user to know how to manually close the web browser to get back to the application after authenticating. Is there something I'm missing about the purpose and usage of cached credentials? When I use most apps, they aren't requiring me to log into my Google account every time and close a web browser just to access their server resources.
It looks like the refresh token needs to be submitted back to the OAuth2 provider to get an updated id token to add to the credentials object. First I added some code to save and load the refresh_token in a config.json file:
private Dictionary<string, string> config;
const string CONFIG_FILE = "config.json";
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var idToken = e.Account.Properties["id_token"];
if (e.Account.Properties.ContainsKey("refresh_token"))
{
if (config == null)
config = new Dictionary<string, string>();
config["refresh_token"] = e.Account.Properties["refresh_token"];
WriteConfig();
}
credentials.AddLogin("accounts.google.com", idToken);
CognitoLogin(idToken).ContinueWith((t) =>
{
try
{
t.Wait();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
ShowMessage("Login cancelled");
}
void WriteConfig()
{
using (var configWriter = new System.IO.StreamWriter(
Application.OpenFileOutput(CONFIG_FILE, Android.Content.FileCreationMode.Private)))
{
configWriter.Write(ThirdParty.Json.LitJson.JsonMapper.ToJson(config));
configWriter.Close();
}
}
public void Login()
{
try
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
{
var idToken = credentials.GetIdentityId();
if (ReadConfig())
{
LoginRefreshAsync().ContinueWith((t) =>
{
try
{
t.Wait();
if (!t.Result)
FullLogin();
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
});
}
else
{
credentials.Clear();
FullLogin();
}
}
}
else
FullLogin();
bDidLogin = true;
}
catch(Exception ex)
{
ShowMessage(string.Format("Error logging in: {0}", ex.Message));
}
}
private bool ReadConfig()
{
bool bFound = false;
foreach (string filename in Application.FileList())
if (string.Compare(filename, CONFIG_FILE, true) == 0)
{
bFound = true;
break;
}
if (!bFound)
return false;
using (var configReader = new System.IO.StreamReader(Application.OpenFileInput(CONFIG_FILE)))
{
config = ThirdParty.Json.LitJson.JsonMapper.ToObject<Dictionary<string, string>>(configReader.ReadToEnd());
return true;
}
}
Then refactored the code that initiates the interactive login into a separate function:
public void FullLogin()
{
auth = new Xamarin.Auth.OAuth2Authenticator(CLIENTID_GOOGLE, string.Empty, "openid",
new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new Uri("com.enigmadream.storyvoque:/oauth2redirect"),
new Uri("https://accounts.google.com/o/oauth2/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
Refactored the code that retrieves a Cognito identity into its own function:
private async Task CognitoLogin(string idToken)
{
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = ID_POOL;
try
{
var result = await cli.GetIdAsync(req);
ShowMessage(string.Format("Identity {0} retrieved", result.IdentityId));
}
catch (Exception ex)
{
ShowMessage(ex.Message);
}
}
And finally implemented a function that can retrieve a new token based on the refresh token, insert it into the current Cognito credentials, and get an updated Cognito identity.
private async Task<bool> LoginRefreshAsync()
{
string tokenUrl = "https://accounts.google.com/o/oauth2/token";
try
{
using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())
{
string contentString = string.Format(
"client_id={0}&grant_type=refresh_token&refresh_token={1}&",
Uri.EscapeDataString(CLIENTID_GOOGLE),
Uri.EscapeDataString(config["refresh_token"]));
System.Net.Http.HttpContent content = new System.Net.Http.ByteArrayContent(
System.Text.Encoding.UTF8.GetBytes(contentString));
content.Headers.Add("content-type", "application/x-www-form-urlencoded");
System.Net.Http.HttpResponseMessage msg = await client.PostAsync(tokenUrl, content);
string result = await msg.Content.ReadAsStringAsync();
string idToken = System.Json.JsonValue.Parse(result)["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
/* EDIT -- discovered this is not necessary! */
// await CognitoLogin(idToken);
return true;
}
}
catch (Exception ex)
{
ShowMessage(ex.Message);
return false;
}
}
I'm not sure if this is optimal or even correct, but it seems to work. I can use the resulting credentials to access DynamoDB without having to prompt the user for permission/credentials again.
There's a very different solution I'm trying to fit with the other answer. But it's so different, I'm adding it as a separate answer.
It appears the problem was not so much related to needing to explicitly use a refresh token to get an updated access token (I think this is done implicitly), but rather needing to remember the identity token. So rather than include all the complexity of manually applying a refresh token, all that's needed is to store the identity token (which can be done in a way similar to how the refresh token was being stored). Then we just need to add that same identity token back to the credentials object when it's missing.
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (config.Read())
{
if (config["id_token"] != null)
credentials.AddLogin(currentProvider.Name, config["id_token"]);
Edit: The problem of needing to use a refresh token does still exist. This code works if the token hasn't expired, but attempting to use these credentials after the token has expired will fail, so there is still some need to use a refresh token somehow in some cases.
want to start development with AWS IOT using Android app
I am seeking for example for IOT in android. need to start basic configuration on AWS console and android app. i already tested temperature demo but didn't get any clue from that! need a basic steps on shadow, policy , role. how to configure them step by step and use of cognito.
below getshadow() method is called onCreate , need to update value on real time basis not ony onCreate.
public void getShadows() {
GetShadowTask getControlShadowTask = new GetShadowTask("TemperatureControl");
getControlShadowTask.execute();
}
private class GetShadowTask extends AsyncTask<Void, Void, AsyncTaskResult<String>> {
private final String thingName;
public GetShadowTask(String name) {
thingName = name;
}
#Override
protected AsyncTaskResult<String> doInBackground(Void... voids) {
try {
GetThingShadowRequest getThingShadowRequest = new GetThingShadowRequest()
.withThingName(thingName);
GetThingShadowResult result = iotDataClient.getThingShadow(getThingShadowRequest);
// Toast.makeText(getApplication(),result.getPayload().remaining(),Toast.LENGTH_LONG).show();
byte[] bytes = new byte[result.getPayload().remaining()];
result.getPayload().get(bytes);
String resultString = new String(bytes);
return new AsyncTaskResult<String>(resultString);
} catch (Exception e) {
Log.e("E", "getShadowTask", e);
return new AsyncTaskResult<String>(e);
}
}
#Override
protected void onPostExecute(AsyncTaskResult<String> result) {
if (result.getError() == null) {
JsonParser parser=new JsonParser();
JsonObject jsonObject= (JsonObject) parser.parse(result.getResult());
response=result.getResult();
setPoint=jsonObject.getAsJsonObject("state").getAsJsonObject("reported")
.get("current_date").getAsString();
textView.setText(setPoint);
// Toast.makeText(getApplication(),setPoint,Toast.LENGTH_LONG).show();
Log.i(GetShadowTask.class.getCanonicalName(), result.getResult());
} else {
Log.e(GetShadowTask.class.getCanonicalName(), "getShadowTask", result.getError());
Toast.makeText(getApplication(),result.getError().toString(),Toast.LENGTH_LONG).show();
}
}
}
UPDATE
Thing Shadow
{
"desired": {
"welcome": "aws-iot"
},
"reported": {
"welcome": "aws-iot",
"current_date": "06-Sep-2017 1:26:40 PM"
}
}
AWS has provided a complete Github repo of Android samples. In the samples do the PubSubWebSocket to connect, subscribe and publish the data to the shadow.
If you have a closer look into the PubSubWebSocket example you will find a detailed information on how to to make a thing policy and role. It cannot be more concise and clear than that.
For understanding and using Cognito follow AmazonCognitoAuthDemo example to make the identity pool and use it in the PubSubWebSocket example.
To get a better understanding of roles and Cognito. Please read the AWS documentation.
Update:
In the IoT thing policy did you give appropriate permissions to connect, subscribe and publish. The option can be found in AWS IoT->Security->Policy->Create Policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iot:*",
"Resource": "arn:aws:iot:us-east-2:293751794947:topic/replaceWithATopic"
}
]
}
The above policy gives all access to the user. Also, make sure your pool which you created is for unauthenticated users.
To get the changes to the shadow type the following in the sample android(WebSocketAwsPubSub) edit box $aws/things/thing_name/shadow/update/accepted
And to publish the data to the shadow type $aws/things/thing_name/shadow/update
Update 2:
Android Code where you will receive the reported messaged. Its suscribing to the device. Its the copy of the snippet from PubSubWebSocketSample.
public void AwsSubscribe(){
final String topic = "$aws/things/D1/shadow/update/accepted";
Log.d(LOG_TAG, "topic = " + topic);
try {
mqttManager.subscribeToTopic(topic, AWSIotMqttQos.QOS0,
new AWSIotMqttNewMessageCallback() {
#Override
public void onMessageArrived(final String topic, final byte[] data) {
runOnUiThread(new Runnable() {
#Override
public void run() {
try {
String message = new String(data, "UTF-8");
Log.d(LOG_TAG, "Message arrived:");
Log.d(LOG_TAG, " Topic: " + topic);
Log.d(LOG_TAG, " Message: " + message);
tvLastMessage.setText(message);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Message encoding error.", e);
}
}
});
}
});
} catch (Exception e) {
Log.e(LOG_TAG, "Subscription error.", e);
}
}
If you want to create a topic, just change the value of this variable final String topic = "YOUR TOPIC" then subscribe to it by using the sample code.
i'm developing an android app and i followed this link to use google push notification
my problem is that the first time that app starts, push notification work's fine but as I restart my app, push notification stops working and I cant receive notifications.
here is my code:
[Service(Exported = false)]
class RegistrationIntentService : IntentService
{
static object locker = new object();
public RegistrationIntentService() : base("RegistrationIntentService") { }
protected override void OnHandleIntent(Intent intent)
{
try
{
Log.Info("RegistrationIntentService", "Calling InstanceID.GetToken");
lock (locker)
{
var instanceID = InstanceID.GetInstance(this);
var token = instanceID.GetToken(
"my_id", GoogleCloudMessaging.InstanceIdScope, null);
Log.Info("RegistrationIntentService", "GCM Registration Token: " + token);
SendRegistrationToAppServer(token);
Subscribe(token);
}
}
catch (Exception e)
{
Log.Debug("RegistrationIntentService", "Failed to get a registration token");
return;
}
}
void SendRegistrationToAppServer(string token)
{
// some code
}
void Subscribe(string token)
{
var pubSub = GcmPubSub.GetInstance(this);
pubSub.Subscribe(token, "/topics/global", null);
}
}
[Service(Exported = false), IntentFilter(new[] { "com.google.android.c2dm.intent.RECEIVE" })]
public class MyGcmListenerService : GcmListenerService
{
public override void OnMessageReceived(string from, Bundle data)
{
Log.Debug("MyGcmListenerService", "From: " + from);
Log.Debug("MyGcmListenerService", "Message: " + message);
}
}
[Service(Exported = false), IntentFilter(new[] { "com.google.android.gms.iid.InstanceID" })]
class MyInstanceIDListenerService : InstanceIDListenerService
{
public override void OnTokenRefresh()
{
var intent = new Intent(this, typeof(RegistrationIntentService));
StartService(intent);
}
}
and i send notification with this method
public static void SendNotification(string MESSAGE, string token)
{
var jGcmData = new JObject();
var jData = new JObject();
jData.Add("message", MESSAGE);
jGcmData.Add("to", token);
jGcmData.Add("data", jData);
var url = new Uri("https://gcm-http.googleapis.com/gcm/send");
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation(
"Authorization", "key=" + API_KEY);
System.Threading.Tasks.Task.WaitAll(client.PostAsync(url,
new StringContent(jGcmData.ToString(), Encoding.Default, "application/json"))
.ContinueWith(response =>
{
Console.WriteLine(response);
Console.WriteLine("Message sent: check the client device notification tray.");
}));
}
}
catch (Exception e)
{
Console.WriteLine("Unable to send GCM message:");
Console.Error.WriteLine(e.StackTrace);
}
}
do you have any idea?
Is this in the emulator? I had this issue with the emulator and fixed it by turning off the option to preserve application data/cache between deploys. In Visualstudio it's Tools->Options-Xamarin->Android Options. Now I get a new token on each deploy and it always works.
Why this happens I haven't figured out. Maybe it's a security feature that a different build of the app cannot use the same taken?
From the official documentation of Xamarin:
"If you force-close the app, FCM will stop delivering notifications. Android prevents background service broadcasts from inadvertently or unnecessarily launching components of stopped applications. (For more information about this behavior, see Launch controls on stopped applications.) For this reason, it is necessary to manually uninstall the app each time you run it and stop it from a debug session – this forces FCM to generate a new token so that messages will continue to be received."
https://learn.microsoft.com/en-us/xamarin/android/data-cloud/google-messaging/remote-notifications-with-fcm?tabs=windows
I am trying to learn to use azure mobile app, but I am having serious problems in using the NotificationHub. I have an Imagine subscription to Azure. I creating an android mobile app with azure backend. I have created a notification hub associated to the azure mobile app on the azure portal.
To register the app on the notification hub I used the code in this tutorial:
https://learn.microsoft.com/en-gb/azure/notification-hubs/notification-hubs-android-push-notification-google-fcm-get-started
The users are authenticated on the azure backend previuosly by using their google account, microsoft account or facebook account. New users are inserted into the table Users by the following node js code written for the table script Users.js. I want a push notification to Welcome the new User.
var azureMobileApps = require('azure-mobile-apps');
var logger = require('azure-mobile-apps/src/logger');
var table = azureMobileApps.table();
table.access = 'authenticated';
/**
* Adds the email address from the claims to the context item - used for
* insert operations
* #param {Context} context the operation context
* #returns {Promise} context execution Promise
*/
function addEmailToContext(context) {
/*
* Getting claim fields
*/
return context.user.getIdentity().then((data) => {
if( data.microsoftaccount != undefined){
context.item.email = data.microsoftaccount.claims.emailaddress;
context.item.name = data.microsoftaccount.claims.givenname;
context.item.surname = data.microsoftaccount.claims.surname;
}
if( data.google != undefined){
context.item.email = data.google.claims.emailaddress;
context.item.name = data.google.claims.givenname;
context.item.surname = data.google.claims.surname;
context.item.picture_url = data.google.claims.picture;
}
if( data.facebook != undefined){
context.item.email = data.facebook.claims.emailaddress;
context.item.name = data.facebook.claims.givenname;
context.item.surname = data.facebook.claims.surname;
}
logger.info('[tables/Users.js] --> NEW USER REGISTERED:'
+'\n\t Name:'+context.item.name
+'\n\t Surname:'+context.item.surname
+'\n\t Email:'+context.item.email);
// Execute the insert. The insert returns the results as a Promise,
// Do the push as a post-execute action within the promise flow.
return context.execute()
.then(function (results) {
// Only do the push if configured
if (context.push) {
// Mobile Apps adds a user tag when registering for push notifications
// Define the GCM payload.
var payload = {
"data": {
"message": 'Welcome '+context.item.username
}
};
context.push.gcm.send(context.user.id, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
}
// Don't forget to return the results from the context.execute()
return results;
})
.catch(function (error) {
logger.error('Error while running context.execute: ', error);
});
});
}
// CREATE - add or overwrite the authenticated user
table.insert(addEmailToContext);
module.exports = table;
According to "How to: Send push notifications to an authenticated user using tags" in the tutorial on How to use the Azure Mobile Apps Node.js SDK
"When an authenticated user registers for push notifications, a user ID tag is automatically added to the registration. "
So in the Users.js, as suggested in this tutorial I wrote the following code to send the push notification to the user.
context.push.gcm.send(context.user.id, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
With this code the push notification results to be sent successfully, but the device doesn't receive any notifications. If I use null instead of context.user.id then all devices receive the push notification correctly:
context.push.gcm.send(null, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
I also tried to invoke the following custom API to create tag when the user is registered to the hub. The invoked API is the following:
var logger = require('azure-mobile-apps/src/logger');
exports.post = function(req, res) {
logger.info('[api/registerTag.js] --> Invoked');
// Get the notification hub used by the mobile app.
var push = req.azureMobile.push,
installationId = req.get('X-ZUMO-INSTALLATION-ID'),
tags = req.body.tag.toString();
// Define an update tags operation.
var updateOperation = [{
"op": "add",
"path": "/tags",
"value": tags
}];
// Update the installation to add the new tags.
push.patchInstallation(installationId, updateOperation, function(error) {
if(error){
logger.error('[api/registerTag.js] --> An error occurred while adding'
+'the following tags: \n\t'+tags, error);
res.status(error.statusCode).send(error.detail);
} else {
logger.info('[api/registerTag.js] --> The following tags have been added'
+'to the Notification Hub: \n\t'+tags, error);
res.status(200).send(tags);
}
});
};
On the console it is printed that the tag has been added successfully. But if I then modify the Users.js code like this:
...
// Only do the push if configured
if (context.push) {
// Mobile Apps adds a user tag when registering for push notifications
var userTag = '_UserId:' + context.user.id;
logger.info("TAG "+userTag);
// Define the GCM payload.
var payload = {
"data": {
"message": 'Welcome '+context.item.username
}
};
context.push.gcm.send(userTag, payload, function (error) {
if (error) {
logger.error('Error while sending push notification: ', error);
} else {
logger.info('Push notification sent successfully!');
}
});
}
...
again nothing is received. I have also tried whitelisting tags or adding them automatically using the Push section of the mobile app like shown in the image:
IMAGE LINK: i.stack.imgur.com/KBvQI.png
But the problem is still there. Hope someone can help me. Thanks.
After several times of testing, I succeeded in reproducing your issue and got the same problem. To achieve your requirement I did some modification in Android client-end:
1, Cache authentication user in the MainActivity class. Following is my code snippet. For more details you can refer here.
public static final String SHAREDPREFFILE = "temp";
public static final String USERIDPREF = "uid";
public static final String TOKENPREF = "tkn";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
// Create the Mobile Service Client instance, using the provided Mobile Service URL and key
mClient = new MobileServiceClient(
"https://yourwebsitename.azurewebsites.net",
this).withFilter(new ProgressFilter());
// Extend timeout from default of 10s to 20s
mClient.setAndroidHttpClientFactory(new OkHttpClientFactory() {
#Override
public OkHttpClient createOkHttpClient() {
OkHttpClient client = new OkHttpClient();
client.setReadTimeout(20, TimeUnit.SECONDS);
client.setWriteTimeout(20, TimeUnit.SECONDS);
return client;
}
});
authenticate();
} catch (MalformedURLException e) {
createAndShowDialog(new Exception("There was an error creating the Mobile Service. Verify the URL"), "Error");
} catch (Exception e){
createAndShowDialog(e, "Error");
}
}
private void authenticate() {
// We first try to load a token cache if one exists.
if (loadUserTokenCache(mClient)) {
createTable();
register();
}
// If we failed to load a token cache, login and create a token cache
else {
// Login using the Google provider.
ListenableFuture<MobileServiceUser> mLogin = mClient.login(MobileServiceAuthenticationProvider.Google);
Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
#Override
public void onFailure(Throwable exc) {
createAndShowDialog("You must log in. Login Required", "Error");
}
#Override
public void onSuccess(MobileServiceUser user) {
createAndShowDialog(String.format("You are now logged in - %1$2s", user.getUserId()), "Success");
cacheUserToken(mClient.getCurrentUser());
createTable();
register();
}
});
}
}
private void cacheUserToken(MobileServiceUser user) {
SharedPreferences prefs = getSharedPreferences(SHAREDPREFFILE, Context.MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putString(USERIDPREF, user.getUserId());
editor.putString(TOKENPREF, user.getAuthenticationToken());
editor.commit();
}
private void register() {
NotificationsManager.handleNotifications(this, NotificationSettings.SenderId, MyHandler.class);
registerWithNotificationHubs();
}
2, In RegistrationIntentService class replace regID = hub.register(FCM_token).getRegistrationId(); with the following code:
regID = hub.register(FCM_token, prefs.getString("uid", "")).getRegistrationId();
3, Make sure add the line below to the first line within onHandleIntent method.
SharedPreferences prefs = getSharedPreferences("temp", Context.MODE_PRIVATE);
I'm running an application with SignalR 2.2.0 on server side and signalr-java-client (self compiled, last GitHub version) on Android as client.
Currently, there are 4 clients connected to my hub. From time to time, it happens, that all 4 clients simultaneously receive the HTTP status 400 with the message "The connection id is in the incorrect format" (the clients were connected before). I analyzed this multiple times and am not able to find any information/pattern when or why this happens.
The connecten is secured via JWT, the token is definitely valid. When retrieving a new token, the connection is stopped and started again. Apart from this, it is very unlikely that the error is device-related, because the error is thrown at all 4 clients the same time.
I know, this error can occur when the client's Identity changes, but an Identity change for 4 clients the same time seems very unlikely to me.
This is the server-code used for authentication (Deepak asked).
The following method gets called in my Startup.cs:
public static void ConfigureOAuth(IAppBuilder app, string audienceID, string sharedSecret)
{
byte[] secret = TextEncodings.Base64Url.Decode(sharedSecret);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
Provider = new MyOAuthBearerAuthenticationProvider(),
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceID },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(Issuer, secret)
}
});
}
Here's the code of MyOAuthBearerAuthenticationProvider class:
class MyOAuthBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
/// <summary>
/// Get's a JWT from querysting and puts it to context
/// </summary>
public override Task RequestToken(OAuthRequestTokenContext context)
{
if (context.Token == null)
{
string value = context.Request.Query.Get("auth_token");
if (!string.IsNullOrEmpty(value)) //token from queryString
{
context.Token = value;
}
}
return Task.FromResult<object>(null);
}
}
I have to retrieve the token from query string, because additionally to the java-client, a javascript client is used, which is not able to set headers.
Lastly, I secure my hub and some of it's methods with the Authorization attribute:
[Authorize(Roles = "MyExampleRole")]
This is the client-code for connection:
public boolean connect(String url, String token) {
if (connected) {
return true;
}
try {
this.hubConnection = new HubConnection(url, "auth_token=" + token, true, logger);
this.hubProxy = hubConnection.createHubProxy("MyHub");
this.hubProxy.subscribe(this.signalRMethodProvider);
this.hubConnection.stateChanged(stateChangedCallback);
SignalRFuture<Void> awaitConnection = this.hubConnection.start();
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
return true;
}
catch (InterruptedException | TimeoutException | ExecutionException e) {
log.error("connect", e);
return false;
}
}
Does anybody have an Idea, how to fix this problem or where I may receive further information?
Thank you very much
-Lukas
seems fine...
possible alteration you can do is change
awaitConnection.get(10000, TimeUnit.MILLISECONDS);
to
awaitConnection.done(new Action<Void>() {
#Override
public void run(Void obj) throws Exception {
Log.d(TAG, "Hub Connected");
}
}).onError(new ErrorCallback() {
#Override
public void onError(Throwable error) {
error.printStackTrace();
Log.d(TAG, "SignalRServiceHub Cancelled");
}
}).onCancelled(new Runnable() {
#Override
public void run() {
Log.d(TAG, "SignalRServiceHub Cancelled");
}
});