FCM with Parse Server Push Notifications mismatchs

2019-06-10 19:25发布

问题:

I used to try this with GCM but I couldn't get it to work with Parse Server .. So I took a stackoverflow users advice and gave it a try with FCM .

My device gets the registration id from FCM like this :

04-15 17:01:29.773 I/parse.GcmRegistrar(30144): GCM registration successful. Registration Id: APA91bFoNUPYdsjN6O_CkPje-O0hXjNz9kvURZMex72xClyBr_5o6D0vYtI-F0iyAGgSYjpIEaJt2QQ2CXk2qpI11gPFUSUdzH-NxQRXSK3hPkuaiC_lciVV3E0fp6A_VZUoYJ8VxOIh

I tried to send a notification from the firebase console with this ID and its working my event gets fired and its all good .

The problem starts when i want to use ParseCloud function to send notifications to my users. While I was searching the device output log for errors i found this one :

04-15 17:01:25.490 E/parse.GcmRegistrar(30144): Found com.parse.push.gcm_sender_id <meta-data> element with value "id:767075137222", but the value is missing the expected "id:" prefix

This one is weird cause my manifest includes the gcm_sender_id plus it includes the prefix id: Here is my manifest

<?xml version="1.0" encoding="utf-8"?>

<uses-permission android:name="android.permission.INTERNET" />


<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<uses-permission android:name="android.permission.VIBRATE" />

<uses-permission android:name="android.permission.WAKE_LOCK" />

<uses-permission android:name="android.permission.GET_ACCOUNTS" />

<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

<permission android:name="com.companyname.appname.permission.C2D_MESSAGE" android:protectionLevel="signature" />

<uses-permission android:name="com.companyname.appname.permission.C2D_MESSAGE" />

<application android:label="Fuse.Android" android:icon="@mipmap/ic_launcher">

    <service android:name="parse.ParsePushService" />

    <receiver android:name="parse.ParsePushBroadcastReceiver"

    android:permission="com.google.android.c2dm.permission.SEND">

        <intent-filter>


            <action android:name="com.google.android.c2dm.intent.RECEIVE" />

            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

            <category android:name="com.companyname.appname" />

        </intent-filter>

    </receiver>
    <meta-data android:name="com.parse.push.gcm_sender_id"
                    android:value="id:767075137222"/>
</application>

I searched online and people were saying that this problem comes when you're not using the right API KEY & Sender Id .. I'm using these :

Next my index for Parse Server looks like this :

    // Example express application adding the parse-server module to expose Parse
// compatible API routes.

var express = require('express');
var ParseServer = require('parse-server').ParseServer;
var path = require('path');

var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;

if (!databaseUri) {
  console.log('DATABASE_URI not specified, falling back to localhost.');
}
var pushConfig = {};
if (process.env.GCM_SENDER_ID && process.env.GCM_API_KEY) {
   pushConfig['android'] = { 
   senderId: process.env.GCM_SENDER_ID || '',
   apiKey: process.env.GCM_API_KEY || ''};
}



var api = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
  appId: process.env.APP_ID || 'myAppId',
  masterKey: process.env.MASTER_KEY || '', //Add your master key here. Keep it secret!
  serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse',  // Don't forget to change to https if needed
    push: pushConfig,
  liveQuery: {
    classNames: ["Posts", "Comments"] // List of classes to support for query subscriptions
  }

});
// Client-keys like the javascript key or the .NET key are not necessary with parse-server
// If you wish you require them, you can set them as options in the initialization above:
// javascriptKey, restAPIKey, dotNetKey, clientKey

var app = express();

// Serve static assets from the /public folder
app.use('/public', express.static(path.join(__dirname, '/public')));

// Serve the Parse API on the /parse URL prefix
var mountPath = process.env.PARSE_MOUNT || '/parse';
app.use(mountPath, api);

// Parse Server plays nicely with the rest of your web routes
app.get('/', function(req, res) {
  res.status(200).send('I dream of being a website.  Please star the parse-server repo on GitHub!');
});

// There will be a test page available on the /test path of your server url
// Remove this before launching your app
app.get('/test', function(req, res) {
  res.sendFile(path.join(__dirname, '/public/test.html'));
});

var port = process.env.PORT || 1337;
var httpServer = require('http').createServer(app);
httpServer.listen(port, function() {
    console.log('parse-server-example running on port ' + port + '.');
});

// This will enable the Live Query real-time server
ParseServer.createLiveQueryServer(httpServer);

I've defined the GCM_SENDER_ID and GCM_API_KEY to the config vars of the Heroku hosting Parse .

After i call the ParseCloud function from the client app i get this in the heroku logs :

    Apr 15 08:03:02 fuseparse app/web.1: } method=POST, url=/parse/push, host=fuseparse.herokuapp.com, connection=close, user-agent=node-XMLHttpRequest, Parse/js1.11.1 (NodeJS 9.11.1), accept=*/*, content-type=text/plain, x-request-id=ea046fd0-5fb7-46b7-9ceb-e6a0fd2ebad1, x-forwarded-for=54.81.77.161, x-forwarded-proto=https, x-forwarded-port=443, via=1.1 vegur, connect-time=0, x-request-start=1523804582292, total-route-time=0, content-length=270, installationId=e2dc9f85-3c2f-464e-beca-c8b9d2cba528, alert=The Giants scored! 
Apr 15 08:03:02 fuseparse app/web.1: verbose: RESPONSE from [POST] /parse/push: { 
Apr 15 08:03:02 fuseparse app/web.1:   "headers": { 
Apr 15 08:03:02 fuseparse app/web.1:     "X-Parse-Push-Status-Id": "upnMh1652U" 
Apr 15 08:03:02 fuseparse app/web.1:   }, 
Apr 15 08:03:02 fuseparse app/web.1:   "response": { 
Apr 15 08:03:02 fuseparse app/web.1:     "result": true 
Apr 15 08:03:02 fuseparse app/web.1:   } 
Apr 15 08:03:02 fuseparse app/web.1: } X-Parse-Push-Status-Id=upnMh1652U, result=true 
Apr 15 08:03:02 fuseparse app/web.1: #### PUSH OK 
Apr 15 08:03:02 fuseparse app/web.1: verbose: _PushStatus upnMh1652U: sending push to installations with 1 batches 
Apr 15 08:03:02 fuseparse app/web.1: verbose: Sending push to 1 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM sending to 1 device 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM GCM Response: { 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM     "multicast_id": 5516369214301735000, 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM     "success": 0, 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM     "failure": 1, 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM     "canonical_ids": 0, 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM     "results": [ 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM         { 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM             "error": "MismatchSenderId" 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM         } 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM     ] 
Apr 15 08:03:02 fuseparse app/web.1: node-pre-gyp verb parse-server-push-adapter GCM } 
Apr 15 08:03:02 fuseparse app/web.1: verbose: _PushStatus upnMh1652U: sent push! 0 success, 1 failures 
Apr 15 08:03:02 fuseparse app/web.1: verbose: _PushStatus upnMh1652U: needs cleanup devicesToRemove=[] 

I've been at it for days .. Can somebody tell me if what im trying to do is possible and if it is possible where i might be doing this wrong ???

回答1:

if you are using fcm notification you need to add this in your manifest !

<!-- Firebase Notifications -->
        <service android:name=".HERE_YOUR_CLASS_WHICH_EXTENDS_FirebaseMessagingService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>

        <service android:name=".HERE_YOUR_CLASS_WHICH_EXTENDS_FirebaseInstanceIDService">
            <intent-filter>
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
            </intent-filter>
        </service>
        <!-- ./Firebase Notifications -->


回答2:

Whoever has a problem with this error :

04-15 17:01:25.490 E/parse.GcmRegistrar(30144): Found com.parse.push.gcm_sender_id <meta-data> element with value "id:767075137222", but the value is missing the expected "id:" prefix

but has defined the gcm_sender_id in the androidmanifest most likely has a faulty sdk or parse.dll installed . What i did is I downloaded the open source SDK which is available in the parseplatform github and modified the GcmRegistrar.cs class to not return null for the sender_id :

This took me a long time but for anyone who has the same problem pls download the latest .dll or if the problem still persists it means that the dll was not updated and you have to manually do this . In my case i was using the .NET Sdk and it wasn't updated .

Download the open source sdk and replace Internal/Push/GcmRegistrar.cs with this

using System;
using Android.App;
using Android.Content;
using Android.OS;
using System.Threading.Tasks;

namespace Parse {
  internal class GcmRegistrar {
    private const string LogTag = "parse.GcmRegistrar";

    private const string ExtraRegistrationId = "registration_id";

    private const string ExtraSenderId = "com.parse.push.gcm_sender_id";
    private const string ParseGcmSenderId = "1076345567071";

    public const string IntentRegisterAction = "com.google.android.c2dm.intent.REGISTER";

    private readonly Object mutex = new Object();
    private Request request;
    private Context context;

    public static GcmRegistrar GetInstance() {
      return Singleton.Instance;
    }

    private static class Singleton {
      public static readonly GcmRegistrar Instance = new GcmRegistrar(Application.Context);
    }

    private GcmRegistrar(Context context) {
      this.context = context;
    }

    private string getActualSenderIdFromExtra(Object senderIdExtra) {
            if (senderIdExtra == null ) {
        return null;
      }
      string senderId = senderIdExtra.ToString();
      if (!senderId.StartsWith("id:")) {
        return null;
      }

      return senderId.Substring(3);
    }

    public void Register() {
      ParseInstallation installation = ParseInstallation.CurrentInstallation;

      lock (mutex) {
        if (installation.DeviceToken == null && request == null) {
          var metadata = ManifestInfo.GetApplicationMetaData();
          object senderIdExtra = null;
          if (metadata != null) {
            senderIdExtra = metadata.Get(ExtraSenderId);
          }

          string senderIds = ParseGcmSenderId;
          if (senderIdExtra != null) {
            string senderId = getActualSenderIdFromExtra(senderIdExtra);

            if (senderId != null) {
              senderIds += "," + senderId;
            } else {
              Android.Util.Log.Error("parse.GcmRegistrar", "Found " + ExtraSenderId + " <meta-data> element with value \""
                + senderIdExtra.ToString() + "\", but the value is missing the expected \"id:\" prefix");
            }
          }
          request = Request.CreateAndSend(this.context, senderIds);
        }
      }
    }

    /// <summary>
    /// Handles GCM registration intent from <see cref="ParsePushBroadcastReceiver"/> and saves the GCM registration
    /// id as <see cref="ParseInstallation.CurrentInstallation"/> device token.
    /// </summary>
    /// <remarks>
    /// Should be called by a broadcast receiver or service to handle GCM registration response
    /// intent (com.google.android.c2dm.intent.REGISTRATION).
    /// </remarks>
    /// <param name="intent"></param>
    public Task HandleRegistrationIntentAsync(Intent intent) {
      if (intent.Action == ParsePushBroadcastReceiver.ActionGcmRegisterResponse) {
        string registrationId = intent.GetStringExtra(ExtraRegistrationId);

        if (registrationId != null && registrationId.Length > 0) {
          Android.Util.Log.Info(LogTag, "GCM registration successful. Registration Id: " + registrationId);
          ParseInstallation installation = ParseInstallation.CurrentInstallation;

          // Set `pushType` via internal `Set` method since we want to skip mutability check.
          installation.Set("pushType", "gcm");
          installation.DeviceToken = registrationId;

          return installation.SaveAsync();
        }
      }
      return Task.FromResult(0);
    }

    /// <summary>
    /// Encapsulates the GCM registration request-response, potentially using <c>AlarmManager</c> to
    /// schedule retries if the GCM service is not available.
    /// </summary>
    private class Request {
      private Context context;
      private string senderId;
      private PendingIntent appIntent;

      public static Request CreateAndSend(Context context, string senderId) {
        Request request = new Request(context, senderId);
        request.Send();

        return request;
      }

      private Request(Context context, string senderId) {
        this.context = context;
        this.senderId = senderId;
        appIntent = PendingIntent.GetBroadcast(context, 0, new Intent(), 0);
      }

      private void Send() {
        Intent intent = new Intent(IntentRegisterAction);
        intent.SetPackage("com.google.android.gsf");
        intent.PutExtra("sender", senderId);
        intent.PutExtra("app", appIntent);

        ComponentName name = null;
        try {
          name = context.StartService(intent);
        } catch (Exception) {
          // Do nothing.
        }
      }
    }
  }
}