问题
I need help. Cloud functions written in node.js are throwing errors and not causing push notifications (alerter) to alert in my Android java app that a comment has been made or that a user has liked a post. On Firebase Console, I was able to get the error message in cloud functions log:
Successfully sent message: { results: [ { error: [Object] } ],
canonicalRegistrationTokenCount: 0,
failureCount: 1,
successCount: 0,
multicastId: 7952971403531609000 }
When I view the error message on Firebase Cloud Functions log, I also get the following:
Error: { Error: The provided registration token is not registered. A previously valid registration token can be unregistered for a variety of reasons. See the error documentation for more details. Remove this registration token and stop using it to send messages.
at FirebaseMessagingError.Error (native)
at FirebaseMessagingError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseMessagingError (/user_code/node_modules/firebase-admin/lib/utils/error.js:253:16)
at Function.FirebaseMessagingError.fromServerError (/user_code/node_modules/firebase-admin/lib/utils/error.js:283:16)
at /user_code/node_modules/firebase-admin/lib/messaging/messaging.js:381:63
at Array.forEach (native)
at mapRawResponseToDevicesResponse (/user_code/node_modules/firebase-admin/lib/messaging/messaging.js:377:26)
at /user_code/node_modules/firebase-admin/lib/messaging/messaging.js:558:24
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
errorInfo:
{ code: 'messaging/registration-token-not-registered',
message: 'The provided registration token is not registered. A previously valid registration token can be unregistered for a variety of reasons. See the error documentation for more details. Remove this registration token and stop using it to send messages.' },
codePrefix: 'messaging' }
The message is very clear: A previously valid registration token can be unregistered for a variety of reasons. I have seen other posts on these error topics but none outlines exactly how to treat and use tokens during registration and login.
Here is the Firebase Cloud Function Code ...
'use-strict'
const functions = require('firebase-functions');
const admin=require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendCommentNotification=functions.firestore.document("Notifications/{user_id}/Comment/{comment_id}").onWrite((change,context)=> {
const user_id=context.params.user_id;
const comment_id=context.params.comment_id;
console.log(user_id+":"+comment_id);
return admin.firestore().collection("Notifications").doc(user_id).collection("Comment").doc(comment_id).get().then((queryResult)=>{
const post_id=queryResult.data().post_id;
const admin_user_id=queryResult.data().admin_id;
const noti_id=queryResult.data().notification_id;
const timestamp=queryResult.data().timestamp;
const post_desc=queryResult.data().post_desc;
const admin_data=admin.firestore().collection("Users").doc(admin_user_id).get();
const commenter_data=admin.firestore().collection("Users").doc(user_id).get();
return Promise.all([commenter_data,admin_data]).then(result=>{
const commenter_name=result[0].data().name;
const commenter_image=result[0].data().image;
const admin_token=result[1].data().token_id;
const admin_name=result[1].data().name;
if(commenter_name!=admin_name)
{
const payload={
data:{
notification_id:noti_id,
timestamp:timestamp,
post_id:post_id,
admin_id:admin_user_id,
title:commenter_name,
from_image:commenter_image,
post_desc:post_desc,
body:"Commented on your post",
click_action:"com.app.ej.ms.TARGET_COMMENT"
}
};
admin.messaging().sendToDevice(admin_token,payload).then(function (response) {
console.log("Successfully sent message:", response);
return;
})
.catch(function (error) {
console.log("Error sending message:", error);
});
}
});
});
});
...
Here is in the RegisterActivity.java
...
private void registerUser() {
mAuth.createUserWithEmailAndPassword(email_, pass_).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull final Task<AuthResult> task) {
if (task.isSuccessful()) {
Map<String,Object> usernameMap=new HashMap<String, Object>();
usernameMap.put("username",username_);
firebaseFirestore.collection("Usernames")
.document(username_)
.set(usernameMap)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
task.getResult()
.getUser()
.sendEmailVerification()
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
final String userUid = task.getResult().getUser().getUid();
final StorageReference user_profile = storageReference.child(userUid + ".png");
user_profile.putFile(imageUri).addOnCompleteListener(new OnCompleteListener<UploadTask.TaskSnapshot>() {
@Override
public void onComplete(@NonNull final Task<UploadTask.TaskSnapshot> task) {
if (task.isSuccessful()) {
user_profile.getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
@Override
public void onSuccess(Uri uri) {
//String token_id = FirebaseInstanceId.getInstance().getToken(); // Deprecated
// TODO https://firebase.google.com/docs/cloud-messaging/android/client#retrieve-the-current-registration-token.
// FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener( RegisterActivity.this, new OnSuccessListener<InstanceIdResult>() {
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task) {
if (!task.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task.getException());
return;
}
// String token_id = instanceIdResult.getToken();
String token_id = task.getResult().getToken();
Log.i(TAG, "RegisterActivity Token ID (token_id): " + token_id);
Map<String, Object> userMap = new HashMap<>();
userMap.put("id", userUid);
userMap.put("name", name_);
userMap.put("image", uri.toString());
userMap.put("email", email_);
userMap.put("bio",getString(R.string.default_bio));
userMap.put("username", username_);
userMap.put("location", location_);
userMap.put("token_id", ""); //token_id
firebaseFirestore.collection("Users").document(userUid).set(userMap).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
mDialog.dismiss();
Toast.makeText(RegisterActivity.this, "Verification email sent", Toast.LENGTH_SHORT).show();
finish();
}
...
Here is in the LoginActivity.java
...
public void performLogin(final boolean override) {
final String email_, pass_;
email_ = email.getText().toString();
pass_ = password.getText().toString();
if (!TextUtils.isEmpty(email_) && !TextUtils.isEmpty(pass_)) {
mDialog.show();
mAuth.signInWithEmailAndPassword(email_, pass_).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull final Task<AuthResult> task) {
if (task.isSuccessful()) {
//Toast.makeText(LoginActivity.this, "Login Successful, continue to email verified", Toast.LENGTH_LONG).show();
Log.i(TAG, "Login Successful, continue to email verified");
if (task.getResult().getUser().isEmailVerified()) {
//Toast.makeText(LoginActivity.this, "Email is verified Successful, continue to get token", Toast.LENGTH_LONG).show();
Log.i(TAG, "Email is verified Successful, continue to get token");
//final String token_id = FirebaseInstanceId.getInstance().getToken(); Deprecated
// FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener( LoginActivity.this, new OnSuccessListener<InstanceIdResult>() {
FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task2) {
if (!task2.isSuccessful()) {
Log.w(TAG, "getInstanceId failed", task2.getException());
return;
}
// Get new Instance ID token
String token_id = task2.getResult().getToken();
Log.i(TAG, "Get Token Listener, Token ID (token_id): " + token_id);
final String current_id = task.getResult().getUser().getUid();
mFirestore.collection("Users").document(current_id).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
@Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
if (!override) {
if (documentSnapshot.getString("token_id").equals(token_id) || documentSnapshot.getString("token_id").equals("")) {
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("token_id", token_id);
mFirestore.collection("Users").document(current_id).update(tokenMap).addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
FirebaseFirestore.getInstance().collection("Users").document(current_id).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
@Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
String username = documentSnapshot.getString("username");
String name = documentSnapshot.getString("name");
String email = documentSnapshot.getString("email");
String image = documentSnapshot.getString("image");
String password = pass_;
String location = documentSnapshot.getString("location");
String bio = documentSnapshot.getString("bio");
// TODO Added Qiscus login here
userHelper.insertContact(username, name, email, image, password, location, bio);
loginPresenter.login(name, email, password);
// TODO Add to Override option
// Original placement of functions:
mDialog.dismiss();
MainActivity.startActivity(LoginActivity.this);
finish();
}
...
Here's my code for FCM ...
public class FCMService extends FirebaseMessagingService {
private static final String TAG = FCMService.class.getSimpleName();
private NotificationUtil notificationUtils;
private String cDesc;
@Override
public void onNewToken(String token) {
super.onNewToken(token);
Log.d("NEW_TOKEN",token);
//String refreshedToken = FirebaseInstanceId.getInstance().getToken();
// Deprecated
storeRegIdInPref(token);
sendRegistrationToServer(token);
Intent registrationComplete = new Intent(Config.REGISTRATION_COMPLETE);
registrationComplete.putExtra("token", token);
LocalBroadcastManager.getInstance(this).sendBroadcast(registrationComplete);
}
private void sendRegistrationToServer(final String token) {
Log.i(TAG, "sendRegistrationToServer:" + token);
}
private void storeRegIdInPref(String token) {
SharedPreferences pref = getApplicationContext().getSharedPreferences(Config.SHARED_PREF, MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putString("regId", token);
editor.apply();
}
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
handleDataMessage(remoteMessage);
}
}
...
(Edit)Firestore Database Structure
I appreciate Frank's answer but here is the structure of my database. I understand that I may need to restructure my database so that it can hold multiple token_ids at once so that the friends/commenter cloud functions can do the work of deleting the tokens that produce error. But I quite frankly don't know how to do that. Any help or website welcomed.
回答1:
There is way too much code in your question, so I didn't read all of it. But the gist of the error message is that you're sending an instance ID token to FCM that is not (or no longer known). You will need to remove the token from your so-called token registry, usually simply the database where you keep the tokens.
My favorite example of how to remove expired tokens is in the functions-samples repo:
tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
const response = await admin.messaging().sendToDevice(tokens, payload);
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
The above code calls sendToDevice(...)
, and then parses the results for expired token errors. It then deletes those tokens from the database, so that they won't keep causing errors.
来源:https://stackoverflow.com/questions/55545542/firebase-cloud-functions-tokens