Using Xamarin and AWS SNS trying to get push notifications working. They were working just fine the other day. Today they aren't.
I have an Android device and an iOS device. Whenever I change anything in my application the other device is supposed to get a push notification. The iOS device is working.
C# code for subscribing on Android:
[Service(Exported = false)]
public 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(
"shhh it's a secret.", 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)
{
// Add custom implementation here as needed.
//... handling my token on the back-end
}
void Subscribe(string token)
{
var pubSub = GcmPubSub.GetInstance(this);
pubSub.Subscribe(token, "/topics/global", null);
}
}
Dropping some breakpoints in there I can see that my device token does match the token for the AWS endpoint that I'm trying to send messages to. For some reason though, I keep getting error messages back from AWS. This is what AWS is sending me:
{"DeliveryAttempts":1,"EndpointArn":"arn:aws:sns:...:endpoint/GCM/...","EventType":"DeliveryFailure","FailureMessage":"Platform token associated with the endpoint is not valid","FailureType":"InvalidPlatformToken","MessageId":"...","Resource":"arn:aws:sns:...:app/GCM/...","Service":"SNS","Time":"2016-03-28T18:22:59.360Z"}
What could be causing this if I know the token I'm getting back from the application matches the token of my AWS endpoint?
It is most likely that the registration id that is being passed to the SNS is not correct. I would recommend that you turn on the delivery status and see the actual response from GCM, see https://mobile.awsblog.com/post/TxHTXGC8711JNF/Using-the-Delivery-Status-feature-of-Amazon-SNS for information
Related
I'm developing an Android app that consumes data from my own REST API server. I want to use Firebase authentication because it allows the user to login using Google, Facebook, Twitter... in a very simple way.
But I'm not sure how to use ID tokens:
Because ID tokens have expiration date, should I call getToken method on every request in the client app, so I'm sure I'm sending a valid token every time?
Should I call verifyIdToken in the server each time I receive a request from the client app?
I don't know what these methods (getToken and verifyIdToken) do under the hood, and because they are asynchronous, I fear they are doing a request to Firebase servers on every call. So I think that making 2 request to Firebase servers in each of my requests is not the way to go...
Both getToken() and VerifyIdToken() are designed to be called for every outgoing/incoming request.
1) Although getToken() is asynchronous, the Firebase Android SDK actually caches the current Firebase user token in local storage. So long as the cached token is still valid (i.e. within one hour since issued), getToken() returns the token immediately. Only when the cached token expires does the SDK fetch a new token from remote Firebase server.
2) VerifyIdToken() is also optimized for performance. It caches the Firebase token public cert (valid for 6 hours) which is used to validate the token signature on local machine. No RPC is involved except for downloading the public cert.
You refresh token each time when is no more valid. And yes, you should verify token on server-side each time. If is no more valid, you send 401 error code with error message (if you want). Verify token is used when you refresh token, and token is append to each request. If you use OkHttp you can create an interceptor that is adding token in header to each request and also can refresh token when error code is 401.
POST https://YOUR_AUTH0_DOMAIN/delegation
Content-Type: 'application/json'
{
"client_id": "YOUR_CLIENT_ID",
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"refresh_token": "your_refresh_token",
"api_type": "app"
}
From what you have explained in the question, I guess you are talking about cross client resource access using Google sign in. And specifically you seem to be interested in obtaining the Id token once and use it without having to obtain on each subsequent API call.
This more or less is synonymous with the offline access mechanism.
In offline access, the Client I.e. the Android app asks for user authorisation for requested scopes. Upon authorisation, instead of issuing an access token, auth server returns a short lived authorisation code which can be used to generate an access token and refresh token.
The client then can pass the authorisation code to the backend over a secure connection. Backend server can retrieve the author token and refresh token and store them in a secure location. The access token is short lived and can be used to access scoped resources for a short time and refreshed from time to time using the refresh token. The refresh token does not expire but can be revoked. If revoked, server app should ask the client app to re-fetch the author code.
Please go through this link which details the complete infrastructure along with the steps to be followed both by client and server app -
https://developers.google.com/identity/protocols/CrossClientAuth
Now coming to your question, you should use a slightly different API to obtain the auth code. Check out this API -
https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.Builder.html#requestServerAuthCode(java.lang.String)
Sample code at - https://developers.google.com/identity/sign-in/android/offline-access
Use below code in your application class and regId is the value holder for your device token.
private void checkPlayService() {
// Check device for Play Services APK. If check succeeds, proceed with
// GCM registration.
if (checkPlayServices()) {
GoogleCloudMessaging googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
regId = getRegistrationId();
if (TextUtils.isEmpty(regId)) {
registerInBackground();
}
} else {
Log.i(TAG, "No valid Google Play Services APK found.");
}
}
private String getRegistrationId() {
String registrationId = sp.getString(Consts.PROPERTY_REG_ID, "");
if (TextUtils.isEmpty(registrationId)) {
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = sp.getInt(PROPERTY_APP_VERSION,0);
int currentVersion = getAppVersion();
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (googleCloudMessaging == null) {
googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
}
regId = googleCloudMessaging.register(Consts.PROJECT_NUMBER);
msg = "Device registered, registration ID=" + regId;
Log.e("GCMID",msg);
storeRegistrationId(regId);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
Log.i(TAG, msg + "\n");
}
}.execute(null, null, null);
}
private void storeRegistrationId(String regId) {
int appVersion = getAppVersion();
Log.i(TAG, "Saving regId on app version " + appVersion);
sp.edit().putString(Consts.PROPERTY_REG_ID, regId).commit();
sp.edit().putInt(PROPERTY_APP_VERSION, appVersion).commit();
}
I followed Google Cloud Messaging (GCM) with local device groups on Android gives HTTP Error code 401 to manage local device groups on Android and successfully got a notification key, but when I send message to the notification key, I never get the message back.
Has anyone ever got this work?
My send code is like:
public void sendMessage(View view) {
AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
try {
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(getApplicationContext());
String to = notificationKey; // the notification key
AtomicInteger msgId = new AtomicInteger();
String id = Integer.toString(msgId.incrementAndGet());
Bundle data = new Bundle();
data.putString("hello", "world");
gcm.send(to, id, data);
Log.e(TAG, "sendMessage done.");
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
return null;
}
};
task.execute();
}
It seems there's a misunderstanding about the GCM concept. The app server is an integral part of GCM messaging.
The server side of Google Cloud Messaging (GCM) consists of two
components:
GCM connection servers provided by Google. These servers take messages
from an app server and send them to a client app running on a device.
Google provides connection servers for HTTP and XMPP.
An application
server that you must implement in your environment. This application
server sends data to a client app via the chosen GCM connection
server, using the appropriate XMPP or HTTP protocol.
Try the Android GCM Playground to get a better understanding of this.
Here's a snippet:
public void sendMessage() {
String senderId = getString(R.string.gcm_defaultSenderId);
if (!("".equals(senderId))) {
String text = upstreamMessageField.getText().toString();
if (text == "") {
showToast("Please enter a message to send");
return;
}
// Create the bundle for sending the message.
Bundle message = new Bundle();
message.putString(RegistrationConstants.ACTION, RegistrationConstants.UPSTREAM_MESSAGE);
message.putString(RegistrationConstants.EXTRA_KEY_MESSAGE, text);
try {
gcm.send(GcmPlaygroundUtil.getServerUrl(senderId),
String.valueOf(System.currentTimeMillis()), message);
showToast("Message sent successfully");
} catch (IOException e) {
Log.e(TAG, "Message failed", e);
showToast("Upstream FAILED");
}
}
}
The to field of the send method represents the sender ID of your project. You cannot use this method to send messages to Instance ID tokens (other devices), Device to Device messaging is not currently supported by GCM.
You are correct to avoid including the API key in your client app, so currently you will need an app server to send these types of messages.
I am using Google Cloud Messaging with XMPP in order to have both downstream and upstream messages.
Only client side I get a token by doing this on a worker thread:
InstanceID instanceID = InstanceID.getInstance(this);
try {
String token = instanceID.getToken(getString(R.string.gcm_senderID), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
send_token(token, getString(R.string.gcm_senderID));
} catch (IOException e) {
e.printStackTrace();
}
I then send this token over to the server where it is received. I am able to send messages to the client with this token.
Then I can send an upstream message on the client side with this:
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg;
Bundle data = new Bundle();
data.putString("message", message);
try {
messenger.send(getString(R.string.gcm_senderID) + "#gcm.googleapis.com", messageId.addAndGet(1) + "", data);
} catch (IOException e) {
e.printStackTrace();
}
msg = "Sent message";
return msg;
}
}.execute(null, null, null);
In the upstream message sent from the client, there is a from field, that seems to be a token as well. If I send a message to this from the server side, my phone receives it as well.
What confuses me is that the token in the from field is not equal to the one generated by the InstanceID service.
The first 18 characters or so are equal, but after that they are very different. As such, is there a good way to identify what device sent what message?
I could store the token generated by the Instance ID each time in the Bundle, but I was wondering if there might be any way to make the from field of the upstream message be consistent with the generated ID.
Edit: Using the deprecated register function, I was able to get a consistent registration ID.
String token = GoogleCloudMessaging.getInstance().register(getString(R.string.gcm_senderID));
But is there a way to do this with InstanceID?
Calling GoogleCloudMessaging.getInstance(context).register(senderId) instead of getToken(senderId, "GCM") seems to resolve the issue, the XMPP server will then receive the correct token, every time, in the "from" property of the upstream message.
My device is running CyanogenMod, so the Google Play services app doesn't update automatically. Since the old register() work, this issue is likely caused by a bug in the google-play-services_lib when talking to an older version of the GMS app.
I've answered instead of comment with the vain hopes of an Google dev seeing this.
I'm working on a mobile application and i'm on Push Notification.
I can retrieve a token from a phone (apple or android) for send a push but i have a question :
This token is always the same ? If a get one time the token, i need to check if the token change ?
From apple documentation,
The form of this phase of token trust ensures that only APNs generates
the token which it will later honor, and it can assure itself that a
token handed to it by a device is the same token that it previously
provisioned for that particular device—and only for that device.
If the user restores backup data to a new device or reinstalls the
operating system, the device token changes.
So, its always good to update the server with the token received from APN. As part of optimisation, if you are receiving the same token, there is no need to update the server.
For Android:
It depends on your implementation, but what is recommended from google is that the registration id, can be changed after the app is updated...
Everytime the registration id is changed, the client should update the server with the new value.
check: http://developer.android.com/google/gcm/client.html#sample-register
if (checkPlayServices()) {
gcm = GoogleCloudMessaging.getInstance(this);
regid = getRegistrationId(context);
if (regid.isEmpty()) {
registerInBackground();
}
} else {
Log.i(TAG, "No valid Google Play Services APK found.");
}
private void registerInBackground() {
new AsyncTask() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
// The request to your server should be authenticated if your app
// is using accounts.
sendRegistrationIdToBackend();
// For this demo: we don't need to send it because the device
// will send upstream messages to a server that echo back the
// message using the 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
...
}
I am doing work on GCM (Google Cloud Messaging) in Android. I am looking for the upstream message using GCM.
Code send the GCM messages to cloud here
try {
Bundle data = new Bundle();
// the account is used for keeping
// track of user notifications
data.putString("account", account);
// the action is used to distinguish
// different message types on the server
data.putString("action", Constants.ACTION_REGISTER);
String msgId = Integer.toString(getNextMsgId());
gcm.send(projectId + "#gcm.googleapis.com", msgId,
Constants.GCM_DEFAULT_TTL, data);
} catch (IOException e) {
Log.e("grokkingandroid",
"IOException while sending registration id", e);
}
Now question is that what cloud would do for that upstream message, Where it can be useful in Android and How ??
When you send an upstream message from your app, the GCM Cloud Connection Server (CCS) transfers that message to your server. In order for that to work, you must implement a server that supports XMPP protocol and establishes a TLS connection with GCM Cloud Connection Server. You also need your API project to be white-listed for using this feature. You can read more about it here.
As for usefulness, it allows you to send messages to your app via the GCM connection instead of via your own connection between your app and your server. That's more battery efficient.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "FCM Token creation logic");
// Get variables reference
deviceText = (TextView) findViewById(R.id.deviceText);
editTextEcho = (EditText) findViewById(R.id.editTextEcho);
buttonUpstreamEcho = (Button) findViewById(R.id.buttonUpstreamEcho);
//Get token from Firebase
FirebaseMessaging.getInstance().subscribeToTopic("test");
final String token = FirebaseInstanceId.getInstance().getToken();
Log.d(TAG, "Token: " + token);
deviceText.setText(token);
//Call the token service to save the token in the database
tokenService = new TokenService(this, this);
tokenService.registerTokenInDB(token);
buttonUpstreamEcho.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "Echo Upstream message logic");
String message = editTextEcho.getText().toString();
Log.d(TAG, "Message: " + message + ", recipient: " + token);
FirebaseMessaging.getInstance().send(new RemoteMessage.Builder(FCM_PROJECT_SENDER_ID + FCM_SERVER_CONNECTION)
.setMessageId(Integer.toString(RANDOM.nextInt()))
.addData("message", message)
.addData("action", BACKEND_ACTION_ECHO)
.build());
// To send a message to other device through the XMPP Server, you should add the
// receiverId and change the action name to BACKEND_ACTION_MESSAGE in the data
}
});
}
This is a sample Android project to showcase the Firebase Cloud Messaging (FCM) to manage upstream and downstream messages.
https://github.com/carlosCharz/FCMTest
This is the video in youtube that explains what it does.
https://www.youtube.com/watch?v=SEzOKSoAMG0
Hope you find it useful.