Use Apple Push Notification Service through Java - java

Am trying to implement a Java program which sends an Apple Push Notification to an iPhone client app... Found the following library: Java APNs
Provider code:
Created the following code (from Javapns) to use in my app:
try {
PayLoad payLoad = new PayLoad();
payLoad.addAlert("My alert message");
payLoad.addBadge(45);
payLoad.addSound("default");
PushNotificationManager pushManager = PushNotificationManager.getInstance();
pushManager.addDevice("iPhone", "f4201f5d8278fe39545349d0868a24a3b60ed732");
log.warn("Initializing connectiong with APNS...");
// Connect to APNs
pushManager.initializeConnection(HOST, PORT,
"/etc/Certificates.p12", "password",
SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
Device client = pushManager.getDevice("Lambo");
// Send Push
log.warn("Sending push notification...");
PushNotificationManager.getInstance().sendNotification(client, payLoad);
}
catch (Exception e) {
throw new ApnsPushNotificationException("Unable to send push " + e);
}
When I run this app (as you can see through the Log4j statements) there's no exceptions which occur:
WARN [MyCode] Initializing connectiong with APNS...
WARN [MyCode] Sending push notification...
But my client app doesn't receive any notifications!
IDPP Registration Process:
Also, did the following on the iPhone Developer Program Portal (IDPP):
Created the APNS based SSL Certificate and Keys
Created and installed the provisioning profile
Installed the SSL Certificate and Key on the server.
Have read over the Apple Push Notification Service Guide several times and noticed a few things:
(1) On page 15, it states that the device token is not the same as the device UDID (which I am currently incorrectly passing in as the second parameter inside the PushNotificationManager.addDevice() method (see above)).
On page 17, it states:
"APNs generates a device token using information contained in the unique device certificate. The device token contains an identifier of the device. It then encrypts the device token with a token key and returns it to the device. The device returns the device token to the requesting application as an NSData object. The application then must deliver the device token to its provider in either binary or hexidecimal format."
iPhone OS Client Implementation
(2) After reading pages 33 - 34, I discovered that I didn't include the Objective-C code to have the app register with APNs.
Am not an Objective-C developer, so is this where I can recover the device code or do I have to get it from the certificate?
Where do I obtain the device token (sorry, someone else wrote the Objective-C client app and I am a Java Developer)?
Question(s):
(1) With the exception of not knowing where to get the device token and the mobile client code registration, is there anything else that I have not looked over or missed?
(2) Am I using the Javapns library the right way?
Thank you for taking the time to read this...

As a shameful self-advertising, I encourage to use java-apns library. Your code will look like:
ApnsService service =
APNS.newService()
.withCert("/etc/Certificates.p12", "password")
.withSandboxDestination() // or .withProductionDestination()
.build();
String payload =
APNS.newPayload()
.alertBody("My alert message")
.badge(45)
.sound("default")
.build();
String deviceToken = "f4201f5d8278fe39545349d0868a24a3b60ed732";
log.warn("Sending push notification...");
service.push(deviceToken, payload);

Just a little tip, in order to convert your received token into a format suitable for registration with javapns, this code will do the trick:
- (NSString *)convertTokenToDeviceID:(NSData *)token {
NSMutableString *deviceID = [NSMutableString string];
// iterate through the bytes and convert to hex
unsigned char *ptr = (unsigned char *)[token bytes];
for (NSInteger i=0; i < 32; ++i) {
[deviceID appendString:[NSString stringWithFormat:#"%02x", ptr[i]]];
}
return deviceID;
}

I tried this and I keep getting hanged when sending the notification, and nothing gets sent.
The issue stems from the following function:
public void sendNotification(Device device, PayLoad payload)
It seems that the bufferedreader has NULL
BufferedReader in =
new BufferedReader(new InputStreamReader(this.socket.getInputStream() ) );
So when this portion of the code gets hit it just hangs there in endless loop
logger.debug( "In: [" + in.readLine() + "]" );
This output is [null]
So then right after then the loops get executed:
while ( ! this.socket.isInputShutdown() ) {
while( in.ready() ) {
logger.debug("ready now");
logger.debug(in.readLine());
System.out.println( this.socket.getInputStream().read() );
}
}
The code enters the first while loop and waits for the BufferedReader in to be ready
and just keeps waiting..... ad that is your hanging

Your Java code looks solid! However, don't forget to close the connection, through PushNotificationManager.closeConnection(). It's important to cleanup after yourself.
As a side comment, I notice that you are adding the device 'iPhone' but querying for 'Lambo' afterwards. This is an indication of a bug.
The device token shown in the code is incorrect. Device tokens, currently, as 32-bit long value, which gets hexed into 64 characters. I assume that the server is failing silently when pushing the notification to invalid token!
The only way to get the device token is from the app itself. As provided by the Push Notification guide suggests, the iPhone app needs to register for notification upon launch. In the application:didRegisterForRemoteNotificationsWithDeviceToken:, the iPhone needs to send the device token to your java provider server. (For debugging purposes, you can just NSLog the device token and use it; it never changes across runs).
I would recommend that you create a server in your java provider server to receive device tokens. Set up a ServerSocket to receive connections from the iPhone and their device token (and any additional info you need) and insert the tokens in the database.

JavaPNS was recently updated to 2.0, and fixed ALL reported issues up to the release date. It does fix the issue you are describing, and using the library is MUCH simpler than it ever was (you can push a notification with a single line of code now).

You seem to be missing the token
pushManager.addDevice("iPhone", "f4201f5d8278fe39545349d0868a24a3b60ed732");
Takes id and token check:
https://github.com/o-sam-o/javapns/blob/master/src/javapns/notification/PushNotificationManager.java#L501
The only way to get a token is from the iphone app. A valid token looks something like this:
1d2d6f34 c5028bca c50df5f9 1992c912 ce7deae8 3bbe7da5 447f6a68 cfecdc0e

Regarding the comment for notnoop here:
If you are landing on this post in 2022, you'll find that the java-apns library doesn't work since 2021. Instead they recommend using pushy library.
I have tried this one just by following their example in the README file and it works really well. They have added examples for both authorisation types: by certificate or by token.

Related

How check my device is connected (IOTCore - GCP) [duplicate]

Does anybody know of an easy way to trigger an event when a device on Google Core IoT goes offline? Before I switched to Google's IoT implementation, this was very easily handled by triggering an event when MQTT disconnects, but it seems Google has no easy way of doing this.
Does anybody know if there is something planned for this?
Who's back do I need to scratch to get them to see that something like this is a basic requirement for IoT device management!
Other platforms like AWS and Microsoft already have this implemented (or some way to handle it easily):
https://docs.aws.amazon.com/iot/latest/developerguide/life-cycle-events.html
Device connectivity(online/offline)status with the Auzure iot hub
I wish I had known this before writing all my code and implementing my setup using Google's IoT platform, I guess that's my fault for assuming something so simple and that should be standard for IoT devices would be available.
How are you going to compete with other IoT providers if you can't even provide basic offline/online events?!
My reply in this SO question shows how I had to write 100+ lines of code just to create a firebase function to check if a device is online (but that still doesn't handle offline events, and is just a hack for something that should be native to ANY IoT service provider!):
https://stackoverflow.com/a/54609628/378506
I'm hoping someone else has figured out a way to do this, as i've spent numerous days searching SO, Google, Google Core IoT Documentation, and still have not found anything.
Even if MQTT Last Will was supported we could make that work, but even that IS NOT SUPPORTED by Google (https://cloud.google.com/iot/docs/requirements) ... come on guys!
Your cloud project does have access to the individual MQTT connect/disconnect events, but currently they only show up in the Stackdriver logs. Within the cloud console, you can create an exporter that will publish these events to a Pub/Sub topic:
Visit the Stackdriver Logs in the
Cloud Console.
Enter the following advanced filter:
resource.type="cloudiot_device"
jsonPayload.eventType="DISCONNECT" OR "CONNECT"
Click CREATE EXPORT
Enter a value for Sink Name
Select Cloud Pub/Sub for Sink Service
Create a new Cloud Pub/Sub topic as the Sink Destination
The exporter publishes the full LogEntry, which you can then consume from a cloud function subscribed to the same Pub/Sub topic:
export const checkDeviceOnline = functions.pubsub.topic('online-state').onPublish(async (message) => {
const logEntry = JSON.parse(Buffer.from(message.data, 'base64').toString());
const deviceId = logEntry.labels.device_id;
let online;
switch (logEntry.jsonPayload.eventType) {
case 'CONNECT':
online = true;
break;
case 'DISCONNECT':
online = false;
break;
default:
throw new Error('Invalid message type');
}
// ...write updated state to Firebase...
});
Note that in cases of connectivity loss, the time lag between the device being unreachable and an actual DISCONNECT event could be as long the MQTT keep-alive interval. If you need an immediate check on whether a device is reachable, you can send a command to that device.
The best solution i think is that
We need 3 things
cloud sheduler ,
and 2 cloud functions
The first function will be the #devunwired answer but instant of
// ...write updated state to Firebase... schedule a second function to trigger in 2-3 min (let device to recconect)
the seccond function will send a command to device
if the device resposne to command
if stored status is connected dont do nothing
else if the stored status is disconnected then update the status to connected and do what ever you want maybe email
else
if stored status is disconnected dont do nothing
if stored status is connected change the status alert by email or something

How to push notification with Cloud Messaging Firebase from the server

I already have an app and I want to start sending notification to the users. I already set up everything in the app(using react native) and I checked manually that I can send notification to the devices and it works.
Now I want to run a job in the server who will push the message (with the device token) to the cloud messaging in firebase.
I can't find a lot of details about how to do it. I would like if someone can give me any guide I can use with. my server is in Kotlin(java can be good too) and I m working with gradle.
Thank you so much for the help
From a Java server you can use the Firebase Admin SDK to send messages. From that documentation comes this minimal example:
// This registration token comes from the client FCM SDKs.
String registrationToken = "YOUR_REGISTRATION_TOKEN";
// See documentation on defining a message payload.
Message message = Message.builder()
.putData("score", "850")
.putData("time", "2:45")
.setToken(registrationToken)
.build();
// Send a message to the device corresponding to the provided
// registration token.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Note that this sends a data message, so that will always be delivered to your code, where you can decide to display a notification or not. To send a notification message, which is what the Firebase console does, you'd use:
Message message = Message.builder()
.setNotification(new Notification("This is the title", "This is the body"))
.setToken(registrationToken)
.build();
Both of these send the message to a specific registration token, so only to a single device/app instance. This means you will need to maintain a list of these tokens, in a way that allows you to send the messages to fit your needs. E.g. a common way is to store the tokens per user. For an example of that, see the functions-samples repo. While this example is in Node.js, the same logic could be applied to a Java server.
Finally: you can also send message to topics. For an example of that (again: using a Node.js server), have a look at this blog post Sending notifications between Android devices with Firebase Database and Cloud Messaging.

Sending Push Notification to device using parse4j in java

I am using parse4j library for server side coding and on client side I have iOS device. Now I want to send the push notification from my web browser page I developed in JAVA in which I am using parse4j library to communicate with iOS device through Parse cloud. I am using gwt for coding the server side.
public void sendPushtoIOS() {
Parse.initialize("appId", "restApiId");
ParsePush parsePushObj = new ParsePush();
parsePushObj.sendInBackground("hello from server",null);
}
I am trying to send the notification with the above code, but nothing happens and iOS device doesn't receive any notification. Please could someone guide the code I written is correct or not, If not, how can I send the notification then?
try {
String rawJSON = "{\"aps\":{\"alert\":\""+"your message"+"\"},\"alerts\":{\"others\":\""+others+"\"}}";
PushNotificationPayload payload=PushNotificationPayload.fromJSON(rawJSON);
Push.payload(payload, "path of certificate.p12", password,false, appleDeviceToken);
}
catch (Exception e) {
}
As far as I understand correct, you are using the Parse4j library to send Push Notification. Did you control the current version of parse4j if it can send Push notification? Or it is pending enhancement? One suggestion; in order to send push notification write a cloud code and trigger this cloud code from parse4j. Then cloud code will send the Push notification.
Hope this helps,
Regards.
This library works fine. You can check pushes in parse dashboard, most likely you collected push requests. The only problem was that library works with channels. Maybe you do not set channels.
If you want to work with conditions, add this lines of code in library
JSONObject local = new JSONObject();
local.put("deviceType", "android");
data.put("where", local);
and remove
data.put("channel", "");
data.put("channels", new JSONArray(this.channelSet));
all of these changes must be done in method getJSONData() in class ParsePush
instead of android you can set your devices

Apple push notification services in java, linux development machine

I'm developing a mobile application with a mac, but it connects to a test server I'm running on linux and I'd like to enable APN on this server with a developer certificate, the question is, is it possible to install this certificate on my test server or I'll have to setup the test server on my linux machine?
To make this a little bit clearer:
My development machine: A Mac.
The test server: A linux machine running liferay 6.0.6
I want to install the development certificate on the test server so I can test push notifications.
Thanks a lot!
I know nothing about 'liferay' but this his how I setup a APN connection (on a Rails server) using the certificate. Note that you need to convert the certificate to a .pem file (using 'openssl pkcs12 -in myfile.p12 -out myfile.pem'):
##apn_cert = nil
APN_SSL_KEY_FILE = 'lib/SSLCert_Private_Key.pem'
APN_SSL_HOST = 'gateway.sandbox.push.apple.com'
# APN_SSL_HOST = 'gateway.push.apple.com'
APN_SSL_PORT = 2195
APN_SSL_PASSWORD = '<password>'
def configure_apn_cert
puts "APN Service: Configuring APN cert"
##apn_cert = File.read(File.join(RAILS_ROOT, APN_SSL_KEY_FILE))
##apn_context = OpenSSL::SSL::SSLContext.new
##apn_context.key = OpenSSL::PKey::RSA.new(##apn_cert, APN_SSL_PASSWORD)
##apn_context.cert = OpenSSL::X509::Certificate.new(##apn_cert)
end
def create_and_configure_apn_server
configure_apn_cert if not ##apn_cert
puts "APN Service: Configuring APN SOCKET and SSL connection"
#apn_socket = TCPSocket.new(APN_SSL_HOST, APN_SSL_PORT)
#apn_ssl = OpenSSL::SSL::SSLSocket.new(#apn_socket, ##apn_context)
#apn_ssl.sync = true
#apn_ssl.connect
end
def close_apn_server
#apn_ssl.close
#apn_socket.close
end
def package_build_for_apn( token, content )
"\0\0 #{token}\0#{content.length.chr}#{content}"
end
def package_send_to_apn( package )
puts "APN Service: Sending #{package}"
bytes_written = #apn_ssl.write( package )
if bytes_written != package.length then
puts "APN Service: SSL write failed"
package_to_apn_show_write( bytes_written, package)
end
end
def apn_deliver_payload( token, payload )
# Convert the device string back into a byte string
tokenBinary = Base64.decode64( token )
# Transform the payload into an APN byte string
apn_content = payload.to_hash.to_json
apn_content_len = apn_content.length
# Build the apn_package per APN specification
apn_package = "\0\0 #{tokenBinary}\0#{apn_content_len.chr}#{apn_content}"
# Actually send it.
package_send_to_apn( apn_package )
end
def package_to_apn_show_write( bytes, package)
puts "Wrote: #{bytes_written}/ Tried: #{package.length}"
puts "Package: '#{package}'"
end
def package_to_apn_debug( token, content, package )
puts "Token(#{token.length}): #{token}"
puts "Content(#{content.length}): #{content}"
puts "Package(#{package.length}): #{package}"
end
I'd like to answer my own question because it might help someone, first of all, I had a terrible confusion between the certificates I needed to generate and how to get the device token among other things.
These are the steps I went through to make this work:
Created an application ID.
Created a Certificate Signing Request using "Keychain access" and got the development certificate for push notifications which I installed using again Keychain access.
Exported the certificate as a .p12 file.
Used the notnoop java library (which is on the central maven repository) to send push notifications.
Here is a sample snippet that shows where the certificate fits in:
ApnsService service =
APNS.newService()
.withCert("/path/to/certificate.p12", "MyCertPassword")
.withSandboxDestination()
.build();
The second argument of the method is the password for the p12 file that keychain access makes you to set.
Finally to glue things, I used the [UIApplication sharedApplication] instance to register to receive push notifications and by implementing a method on the AppDelegate I get the token I needed which was in NSData format, so you need to converted into a hex string (there is sample code for that in many sites and questions on this site).
And that's it, that's the process.
Hope this helps!!

How to use Javapns to Support Apple's Enhanced Notification Format

Greetings,
I am creating a Java based server to create push notifications for Apple's iOS APNs service. I have found Javapns on google code which seems to provide a simple basic framework to communicate with APNs, and which seems to be fairly wide used.
http://code.google.com/p/javapns/
However, reading Apple's docs, there is an "enhanced format" for notifications which supports "expiry" i.e. setting a time (well, in seconds) for a notification to expire if it hasn't yet been delivered. I do not see any way to set this using Javapns, and I am unsure how the APNs service handles expiry of notifications if you do not explicitly set it. So,
Does anyone know how to support the enhanced notification format of APNs specifically how to set the expiry?
Does anyone know how Apple handles notification expiry if it isn't explicitly set?
Does anyone have any suggestions that don't require me to start from scratch, as the server is currently functional as is?
Thanks in advance.
Andrew
I have recently made substantial contributions to the JavaPNS project, which lead to the release of JavaPNS 2.0 a few days ago. That version provides full support for the enhanced notification format, including the ability to set your own expiry.
Sylvain
Nice that you found the java library... to bad you didn't read the docs there.
I'll post some of the highlights below:
The existing code uses the 'Simple notification format' which does not return an error EVER.
See docs at:
http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html
I've tried updating to the 'Enhanced notification format' which is supposed to return an error, but I'm unable to get any errors back from the APNS. (also in the link above)
With the Enhanced format, the connection isn't being dropped immediately after sending data, but I'm not getting anything back from my socket.getInputSocket.read() call.
This issue will have to be tabled until I have more time to troubleshoot.
(Someone else commented)
Thanks a lot for looking into it.
I got the same result as yours. Maybe it has something to do with Apple Gateway.
So... you could:
1) Build your own
2) Help improve the existing library
3) Try another library like: https://github.com/notnoop/java-apns
4) Do nothing
Enhanced ios push here.
To send a notification, you can do it in three steps:
Setup the connection
ApnsService service =
APNS.newService()
.withCert("/path/to/certificate.p12", "MyCertPassword")
.withSandboxDestination()
.build();
Create and send the message
String payload = APNS.newPayload().alertBody("Can't be simpler than this!").build();
String token = "fedfbcfb....";
service.push(token, payload);
To query the feedback service for inactive devices:
Map<String, Date> inactiveDevices = service.getInactiveDevices();
for (String deviceToken : inactiveDevices.keySet()) {
Date inactiveAsOf = inactiveDevices.get(deviceToken);
...
}

Categories