iOS Push Notification Error: {reason: 'TopicDisallowed'} - ios

[Please read carefully before answering.]
I'm struggling to resolve notifications for more than a week and still, it's halfway resolved.
I'm using the production certificate.
It's working fine sometimes and suddenly starts throwing: { reason: 'TopicDisallowed' }
It's working perfectly fine on localhost.
It works fine sometimes on the server too (the issue is unstable). It automatically starts working and stops.
Here is the provider config:
const apn = require('apn'); // version: "apn": "^2.2.0"
const iosOptions = {
token: {
key: path.resolve('./lib/AuthK*********.p8'),
keyId: '*********',
teamId: '*******'
},
production: true
};
const apnProvider = new apn.Provider(iosOptions);
const apnNotification = new apn.Notification();
apnNotification.sound = 'default';
apnNotification.title = 'Hello';
apnNotification.body = 'Hello message';
apnNotification.aps.threadId = 'thread_id';
apnNotification.topic = topic;
apnNotification.payload = payload;
return apnProvider.send(apnNotification, token)
Is there anything wrong with the config?
Error message: reason: 'TopicDisallowed'
Please help if someone knows the solution.

Finally, I've resolved it by removing the package and writing a custom notification sender with Apple's official documentation for https://api.push.apple.com.
Here's more: https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW3

Related

How to transition from an APNS development to production provider?

The API is written in NodeJS
Currently,
The app is capable of send a push notification in Apple's development environment using the code below.
Getting a BadDeviceToken 400 Error
Also, note development notifications were working (SO will not format my code below):
module.exports.sendNotification = (deviceToken, msg, payload) => {
const jwt = require("jsonwebtoken");
const http2 = require("http2");
const fs = require("fs");
const key = fs.readFileSync(process.env.APNS_KEY, "utf8");
// "iat" should not be older than 1 hr from current time or will get rejected
const token = jwt.sign(
{
iss: process.env.APNS_TEAM_ID, // "team ID" of your developer account
iat: Date.now() / 1000 // Replace with current unix epoch time
},
key,
{
header: {
alg: "ES256",
kid: process.env.APNS_KEY_ID // issuer key which is "key ID" of your p8 file
}
}
);
/*
Use 'https://api.production.push.apple.com' for production build
*/
const host = process.env.APNS_HOST;
const path = `/3/device/${deviceToken}`;
const client = http2.connect(host);
client.on("error", (err) => console.error(err));
const body = {
aps: {
alert: msg,
"content-available": 1,
payload
}
};
const headers = {
":method": "POST",
"apns-topic": process.env.APNS_TOPIC, // your application bundle ID
":scheme": "https",
":path": path,
authorization: `bearer ${token}`
};
const request = client.request(headers);
request.on("response", (headers, flags) => {
for (const name in headers) {
console.log(`${name}: ${headers[name]}`);
}
return {
headers
}
});
request.setEncoding("utf8");
let data = "";
request.on("data", (chunk) => { data += chunk; });
request.write(JSON.stringify(body));
request.on("end", () => {
console.log(`\n${data}`);
client.close();
});
request.end();
};
The desired outcome is to send a push notification using Apple's production APNS environment. My best try at solving this has been swapping the development url for the production, that returns a, "{"reason":"BadDeviceToken"} :status: 400 apns-id: "
Tries so far...
Here are the hosts I'm using:
Production = "https://api.push.apple.com" also tried "https://api.push.apple.com:443"
Development = "https://api.sandbox.push.apple.com"
Other things I've tried:
Certificates instead of tokens; not sure if I'm doing it right. So if you know, please drop the code it.
ChatGPT's sol'n:
const apn = require('apn');
// Path to the certificate file and passphrase (if any) const cert = '/path/to/cert.pem'; const key = '/path/to/key.pem'; const passphrase = 'your_certificate_passphrase';
// Create the APN provider with the certificate and key const provider = new apn.Provider({ cert: cert, key: key, passphrase: passphrase, production: true // set to false for development environment });
// Create the notification payload const payload = new apn.Payload({ alert: 'Hello World!', sound: 'default', badge: 1 });
// Send the notification to a device token const deviceToken = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; const note = new apn.Notification(); note.expiry = Math.floor(Date.now() / 1000) + 3600; note.payload = payload; note.topic = 'com.example.myapp'; provider.send(note, deviceToken).then(result => { console.log(result); });
I'm not sure where to find the passphrase for above, so I just removed it. The key, I found in the .pem file and just cut and pasted it out of that from the key beginning and end and replaced the old .p8 file text with it.
Also tried, SO answers: How do I switch the certificate from development to production?
Also tried, SO answers: iOS push notification device token for development and production
Also tried, iOS APNS Development [sandbox] vs Production
Also tried, changing the environment under the 'APS Environment' key in the Entitlements File from 'development' to 'production' and then generated a new device token.
When transitioning your APNS API/Backend Side Provider you need to construct the Provider like so...
const fs = require("fs");
const apn = require('apn');
// Create the APN provider with the certificate and key
const key = fs.readFileSync(process.env.APNS_KEY, "utf8");
const cert = fs.readFileSync(process.env.APNS_CERT, "utf8");
const provider = new apn.Provider({
token: {
cert: cert,
key: key,
teamId: process.env.APNS_TEAM_ID,
keyId: process.env.APNS_KEY_ID
},
production: true // set to false for development environment
});
Building the Provider, Cert and Key Look
Note that I'm using the cert from Developer portal AND the key to sign it. This is in the same section of the Apple Developer portal as the Development token (eg the .p8 file). These files (.pem and .p8) begin and end with, "-----BEGIN CERTIFICATE-----
", "-----END CERTIFICATE-----", "-----BEGIN PRIVATE KEY-----
", "-----END PRIVATE KEY-----". You will often see "token" in the instructions online, that is the object you pass into the Provider constructor as an arg. The token includes the cert and key, along with the other fields you see in the code.
NEXT, this took days for me to figure out. You cannot force the Simulator or side loading to create a production Device Token by changing the Entitlements File in XCode to, 'production' instead of 'development'. You MUST (as in there were no Push Notifications making it to my side loaded app through my API to APNS to my device, and I was getting "BadDeviceToken" instead) at least install the TestFlight version of your app in order to get a production Device Token. Once you have that you may pass it to your API, then use it send APNs to device(s).
Init Provider Options
You may also init your Provider like above instead of the "jwt" method. I've tried both and the above way seems the easiest and cleanest (less code).
WATCH OUT for tutorials that claim there is a way to determine between these two tokens, with "
const firstByte = parseInt(deviceToken.substr(0, 2), 16);
// Successfully determines the aps environment
const apsEnvironment = (firstByte & 0x80) ? 'production' : 'development';
"
Notification Content and Delivery
The following goes below the code snippet above...
const notification = new apn.Notification();
notification.alert = {
title: 'Hello World',
body: 'This is a test notification'
};
notification.topic = process.env.APNS_TOPIC;
console.log(`notification ${JSON.stringify(notification)}`)
provider.send(notification, deviceToken).then((result) => {
console.log(JSON.stringify(result));
});
This seems self exclamatory and did not hold me up. Also, there if you go to the definition of Notification, there is a payload attribute which you may define as other tutorials mentions.
Cheers! Happy Hacking!

How to prepare APN for production

Im trying to deploy my app with notifications but it's giving me the biggest headache in the world. All other questions ive seen with regards to this seem outdated.
I set up APNs to be sent from a nodeJS script that I have running. When running in my sandbox everything was working well. As soon as I sent my app to TestFlight, notifications stopped sending. My script is still Successfully sending to the Notification Id registered with my phone but im assuming its not the correct production Id. If anyone canhelp get me sending production notifications it would be greatly appreciated! Thank you
APN Server code
var options = {
token: {
key: "AuthKey_6V27D43P5R.p8",
keyId: "3Z6SEF7GE5",
teamId: "ASQJ3L7765"
},
production: true
};
var apnProvider = new apn.Provider(options);
function SendIOSNotification(token, message, sound, payload, badge){
var deviceToken = token; //phone notification id
var notification = new apn.Notification(); //prepare notif
notification.topic = 'com.GL.Greek-Life'; // Specify your iOS app's Bundle ID (accessible within the project editor)
notification.expiry = Math.floor(Date.now() / 1000) + 3600; // Set expiration to 1 hour from now (in case device is offline)
notification.badge = badge; //selected badge
notification.sound = sound; //sound is configurable
notification.alert = message; //supports emoticon codes
notification.payload = {id: payload}; // Send any extra payload data with the notification which will be accessible to your app in didReceiveRemoteNotification
apnProvider.send(notification, deviceToken).then(function(result) { //send actual notifcation
// Check the result for any failed devices
var subToken = token.substring(0, 6);
console.log("Succesfully sent message to ", subToken);
}).catch( function (error) {
console.log("Faled to send message to ", subToken);
})
}

APN Status '400,' How can I get more data about error?

I'm using node-apn to send push notifications to my device. Whenever I try, I get the following:
{ sent: [],
failed:
[ { device: '****',
status: '400',
response: [Object] } ] }
I'm fairly certain my device token is correct. Is there any way to find out more info about why this error occurs. Is there info in the "response"—if so how do I get it? It would be helpful to get one of the error strings listed here (https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) like "BadCollapseId"
Here is my node.js code for reference.
var deviceToken = "***";
var notification = new apn.Notification();
notification.topic = '*****';
notification.alert = "HI DER";
notification.payload = {id: 3};
apnProvider.send(notification, deviceToken).then(function(result) {
console.log(result);
});
The app is built using ionic 2, but I don't think that would make a difference.
Thanks!
Basically all I needed was this line:
console.log(result.failed);
instead of
console.log(result);
This gave me the code "DeviceTokenNotForTopic" and I was able to go from there!

Send notification from web to android device using Firebase

I am trying for a while now to implement this flow: When user adds some files on server app, notification should trigger and send from server to FCM and that from there to pass message saying something like: 'New file has been added'.
Basically I want to inform mobile device user that something on server has been changed.
I have tried many things, but nothing seems to work as I would expect, at least.
On the mobile side I have set up Firebase inside my Xamarin.Android project, and when I am sending notifications directly from Firebase console, I get notifications, and everything is good.
But I don't want to send notifications via Firebase console, I would rather send notification from server (which is ASP.NET MVC project) to Firebase console and then pass it from there to android device.
My first question would be: Has anybody got an idea how can I inform web app about device_id? Is there some way that android device send this information on server? And maybe from there I can store that data and update it occasionally, since it is basically a refresh token.
My second problem is this: Even when I hard code current device_id of an active android device and try to send a message from server whit this code:
public class FirebaseService : IFirebaseService
{
public void SendMessageToClientApplication(string message, string serverApiKey, string senderId, string deviceId)
{
AndroidFCMPushNotificationStatus result = new AndroidFCMPushNotificationStatus();
try
{
result.Successful = false;
result.Error = null;
deviceId = "eMk6mD8P8Dc:APA91bG5Lmqn4Hwb4RZJ1Mkdl8Rf_uYQsQCEfDJK334tzSvIGzdao7o2X6VmtcTEp_Li0mG8iUoUT7-_RnZxQKocHosZwx6ITWdpmQyCwUv60IIIy0vxNlEaccT6RqK6c-cE1C6I3FTT";
var value = message;
WebRequest tRequest = WebRequest.Create("https://fcm.googleapis.com/fcm/send");
tRequest.Method = "post";
tRequest.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
tRequest.Headers.Add(string.Format("Authorization: key={0}", serverApiKey));
tRequest.Headers.Add(string.Format("Sender: id={0}", senderId));
string postData = "collapse_key=score_update&time_to_live=108&delay_while_idle=1&data.message="
+ value + "&data.time=" + DateTime.Now.ToString() + "&registration_id=" + deviceId + "";
Byte[] byteArray = Encoding.UTF8.GetBytes(postData);
tRequest.ContentLength = byteArray.Length;
using (Stream dataStream = tRequest.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
using (WebResponse tResponse = tRequest.GetResponse())
{
using (Stream dataStreamResponse = tResponse.GetResponseStream())
{
using (StreamReader tReader = new StreamReader(dataStreamResponse))
{
String sResponseFromServer = tReader.ReadToEnd();
result.Response = sResponseFromServer;
}
}
}
}
}
catch (Exception ex)
{
result.Successful = false;
result.Response = null;
result.Error = ex;
}
}
}
I get nothing both in Firebase console and of course nothing on device as well.
I have tried to implement Firebase web as javascript on my server app like this:
<script>
var config = {
apiKey: "mykey",
authDomain: "myauthdomain",
databaseURL: "mydatabaseurl",
projectId: "myprojectid",
storageBucket: "mystoragebucket",
messagingSenderId: "mysenderid"
};
window.onload = function () {
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging.requestPermission()
.then(function () {
console.log('Notification permission granted.');
return messaging.getToken()
})
.then(function (token) {
console.log(token);
})
.catch(function (err) {
console.log('Unable to get permission to notify.', err);
});
messaging.onMessage(function (payload) {
console.log('onMessage: ', payload);
});
}
</script>
But this code gets some kind of a different device_id(aka token), probably one generated for that server machine.
Does anybody has experience with sending device_id to server app and from there sending notification message to Firebase console? I would appreciate some code examples, tutorials or anything that can help, since I was unable to find something useful during my google search.
My first question would be: Has anybody got an idea how can I inform web app about device_id?
The most common approach is to store the list of device tokens (each device that uses FCM has such a token) in a database, such as the Firebase Database. There is an example of this in the Cloud Functions for Firebase documentation. In this example the devices receiving the messages are web pages, but the approach is the same for iOS and Android.
I also recommend reading Sending notifications between Android devices with Firebase Database and Cloud Messaging. In this article, instead of sending to a device token, each user subscribes to a topic. That prevents having to manage the device tokens in your code.

push notification using node.js in ios app while develepment

I am trying to send push notification from my app ,I have configured everything.After configuration i got cert.pem and key.pem. I have followed this https://blog.engineyard.com/2013/developing-ios-push-notifications-nodejs link . I am able to connect with apple server but not able to send the notification .can anyone help me what can be the issue as i have tried alot but did not get an appropriate solution for my problem. I am using the following code for communicating the apple server
Thanks in advance.
var http = require('http');
var apn = require('apn');
var url = require('url');
var myPhone = "f0e56d999e14ab0504b403efd39b90deb49306045b4ed698e33602abaa862111";
var myDevice = new apn.Device(myPhone);
var note = new apn.Notification();
note.badge = 1;
note.sound = "r.mp3";
note.alert = { "body" : "Your turn!", "action-loc-key" : "Play" , "launch-image" : ""};
note.payload = {'messageFrom': 'Holly'};
note.device = myDevice;
var callback = function(errorNum, notification){
console.log('Error is: %s', errorNum);
console.log("Note " + notification);
}
var options = {
gateway: 'gateway.sandbox.push.apple.com', // this URL is different for Apple's Production Servers and changes when you go to production
errorCallback: callback,
cert: __dirname+'/_certs/cert.pem',
key: __dirname+'/_certs/key.pem',
passphrase: null,
port: 2195,
enhanced: true,
cacheLength: 100
}
var apnsConnection = new apn.Connection(options);
apnsConnection.sendNotification(note);

Resources