How to transition from an APNS development to production provider? - ios

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!

Related

{"reason":"BadDeviceToken"} http2 IOS notifications from Nodejs

I am trying to send push notifications using http2 api from my node backend.
I have the following with me from the IOS team .
.p8 AuthKey
Team ID
Key ID
We have generated the build from the production environment.
Key is generated using the AppStore selection.
I dont see any environment mismatch in the key, Device token and the build.
But still I get
:status: 400 apns-id: 91XXXX-XXXX-XXXX-XXXX-3E8XXXXXX7EC
{"reason":"BadDeviceToken"}
Code from Node backend :
const jwt = require('jsonwebtoken');
const http2 = require('http2');
const fs = require('fs');
const key = fs.readFileSync(__dirname + "/AuthKey_XXXXXXXXXX.p8", 'utf8')
const unix_epoch = Math.round(new Date().getTime() / 1000);
const token = jwt.sign(
{
iss: "XXXXXXXXXX", //"team ID" of developer account
iat: unix_epoch
},
key,
{
header: {
alg: "ES256",
kid: "XXXXXXXXXX", //issuer key "key ID" of p8 file
}
}
)
const host = 'https://api.push.apple.com'
const path = '/3/device/<device_token>'
const client = http2.connect(host);
client.on('error', (err) => console.error(err));
const body = {
"aps": {
"alert": "hello",
"content-available": 1
}
}
const headers = {
':method': 'POST',
'apns-topic': 'com.xxxxxx.xxxxxx', //your application bundle ID
':scheme': 'https',
':path': path,
'authorization': `bearer ${token}`
}
const request = client.request(headers);
// request.on('response', response => {
// console.log("apnresponse",JSON.stringify(response));
// });
request.on('response', (headers, flags) => {
for (const name in headers) {
console.log(`${name}: ${headers[name]}`);
}
});
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();
IOS team is able to successfully send notifications to the device using the firebase console.
PUsh notifications fail only when I try from the node backend.
According to the Apple documentation, neither the device token is invalid, nor I am using production certificate for the development server or vice versa;
neither of which are the case here.
How can I make this work?

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

[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

Signing into Cognito app through Cypress programmatically , giving userpoolId and clientId is loading the app with out user data

I am trying to automate Sign In flow for an app with Cognito Identity Provider in Cypress.
This is the code I am using
cypress.json :
"username": :<userName>,
"password":<password>,
"userPoolId":<userPoolId>,
"clientId":<clientId>
commands.js
const Auth = require ( "aws-amplify" ).Auth;
import "cypress-localstorage-commands";
const username = Cypress.env("username");
const password = Cypress. env("password");
const userPoolId = Cypress. env("userPoolId");
const clientId = Cypress. env ("clientId");
const awsconfig = {
aws_user_pools_web_client_id: clientId,
aws_user_pools_id: userPoolId
};
Auth. configure (awsconfig) ;
Cypress.Commands.add("signIn", () => {
cy.then(() => Auth.signIn(username, password)).then((cognitoUser) => {
console.log("SIGNIN AUTH",cognitoUser)
const idToken = cognitoUser.signInUserSession.idToken.jwtToken;
console.log("IDTOKEN", idToken)
const accessToken = cognitoUser.signInUserSession.accessToken.jwtToken;
const makeKey = (name) => `CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.${cognitoUser.username}.${name}`;
cy.setLocalStorage(makeKey("accessToken"), accessToken);
cy.setLocalStorage(makeKey("idToken"), idToken);
cy.setLocalStorage(
`CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.LastAuthUser`,
cognitoUser.username
);
cy.setLocalStorage("amplify-signin-with-hostedUI","true")
cy.visit("<loginUrl>");
});
cy.saveLocalStorage();
})
TestFile.js :
cy.signIn()
App is loading but user data is not loaded. It launches the app with message - "Contact your administrator to start using the app", which is the message shown when the user is not added . But the user is added to the app. Could some one help me understand If I am missing something.User access token , idToken , refreshToken is stored correctly in the local Storage.

iOS push notifications and Testflight using ..p8 certificate and apn

I'm trying to send Push Notifications to my ios app. They work fine for the app I download via the App store, but they don't work when I install a beta version using Testflight. This sort of defeats the purpose, as I want to test out new types of notifications and do the right thing on the app.
I think I'm doing all the right things:
Register for notifications on the app and get a device token
send the device token to my server
send the notification to the device using APNS in node.
The problem is that when I send the notification from the server to an app downloaded from Testflight, I get BadDeviceToken error. From the App Store, works perfectly.
The code in Node looks something like this:
let util = require('util');
let apn = require('apn');
class PushNotification {
constructor(production) {
production = !!production;
this.apnProvider = new apn.Provider({
token: {
key: './apns.p8', // Path to the key p8 file
keyId: 'xxxxxxxxx', // The Key ID of the p8 file (available at https://developer.apple.com/account/ios/certificate/key)
teamId: 'YYYYYYYY' // The Team ID of your Apple Developer Account (available at https://developer.apple.com/account/#/membership/)
},
production: production // Set to true if sending a notification to a production iOS app
});
if (production) {
process.env.NODE_ENV = "production";
}
}
sendNotificationApple(deviceToken, alert, payload, badge) {
if (deviceToken && (alert || badge)) {
let note = new apn.Notification();
note.topic = 'com.xxx.xxx';
note.expiry = Math.floor(Date.now() / 1000) + 3600 * 24 * 2; // Expires 2 days from now
if (badge != undefined && badge != -1) {
note.badge = badge;
}
note.alert = alert;
if (payload) {
note.payload = payload;
}
this.apnProvider.send(note, deviceToken).then(function (result) {
console.log(util.inspect(result, false, null));
});
}
}
}

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);
})
}

Resources