So I'm not a big fan of guest posts (I'll admit to being something of a blog diva), but after talking about this with Erik Madsen last week I thought it would be great to share. He doesn't have a blog so I offered to post his text here. (I get 1% credit for pointing him towards one URL.) Anyway, enjoy, and thank you Erik for sharing!
Assuming yesterdays news from Apple that third-party development tools for iOS app development are no longer banned, the Adobe developer community will probably be a rapidly growing segment of iOS app developers in the coming months. If you're a FlashFlex developer who's excited about the opportunity to develop for iOS, you'll probably run into the need to provide push notifications for your app's users. I assumed that producing these would be an easy process. I quickly discovered I was somewhat wrong.Now, I consider myself a developer who manages to do pretty well without actually being as big-brained as some of the folks I look up to. This is what led me to ColdFusion 15 years ago in the first place. Since then I've learned a lot but I still run into my limits now and then. When I run into a road-block, I spend a lot of time on the Google, stackoverflow.com and pestering folks like Ray to lend some of that grey matter.
Anyway, the reason I say all of this is because I hit one of those brick walls of mine when I was faced with producing push notifications from ColdFusion. You see, Apple doesn't provide your typical run-of-the-mill type of restful or wsdl service to create these. Nope: it's a binary, streaming interface that requires TLS or SSL and you have to use present a trusted certificate from their iOS developer program in order to make the connection. This really left me stumped. The Google didn't reveal much of anything. I think one other CF developer was asking around for a wrapper to the service but ended up in the same place as me - empty handed. Ray, being all big-brained, Googled for a Java library for Apple Push Notification Service (APNs) and found what I was looking for. About 15 minutes of work later I had my first push notifications being delivered from ColdFusion to my app on my iPhone 4. Gratifying!
The first thing you need to do is enable APNs (development, production of both) for your app on the iOS Provisioning Portal. Push notifications will not work in the iPhone Simulator. They'll only work for on a provisioned device with an app that has been registered with the iOS Developer Program. Generate a Certificate Signing Request with the Certificate Assistant in Keychain Access, save it to disk and have Apple sign it. A few seconds later you can download it and install it on your development machine.

Then you'll need to export the certificate (option-click the certificate) as a Personal Information Exchange (.p12) file. Place this file in a location accessible to ColdFusion. Remember the password you set as well.

Enabling push notifications within the iOS app itself is really easy. You'll need to register the app for push notifications...
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
and have your App Delegate respond to successes or failures for this request...
-
(void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
//The device token is available from [devToken description]
//Send this token to your ColdFusion server to track in the database or whatever
}
-
(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
//The request failed for whatever reason, tell your ColdFusion server if necessary
}
Next you'll need the Java library for communicating with APNs. There's actually two libraries to choose from, but this article will only focus on one - notnoop's java-apns available at http://github.com/notnoop/java-apns. I downloaded the jar file with dependencies. Place this file in a location accessible to ColdFusion and create a Java classpath in the Cold Fusion Administrator to the file, then restart CF. You're pretty much ready to go now.
The first line of CFML will instantiate the APNS object from the library using your APNs certificate and either the production or sandbox destination. My app is actually already running in production mode and waiting for Apple review, so I'll use production.
<cfset APNSService = createObject( "java", "com.notnoop.apns.APNS" ).newService()
.withCert("THE-PATH-TO-YOUR-P12-CERT", "YOUR-P12-PASSWORD")
.withProductionDestination()
.build() />
You'll next need to create a payload that defines the push notification to be sent. The badge, alertBody and sound properties are all optional, but you need to provide at least one. My app uses all three...
<cfset payload = createObject( "java", "com.notnoop.apns.APNS" ).newPayload()
.badge(3)
.alertBody("Hello, world.")
.sound("PushNotification.caf")
.build()/>
The third and final line will send the notification to APNs using the device token I saved earlier.
<cfset APNSService.push(token, payload) />
Voila! Push notifications! I had already stubbed out some methods related to push notifications in my ColdFusion app's components, so modifying this code to work with my actual application was quick and painless. My app's users were all getting push notifications in seconds of my publishing the update.
The Java library also provides the ability to request inactive device tokens, which you will want to occasionally check for and maintain in your database, as Apple doesn't want you trying to notify devices that have opted out of push notifications for your app or altogether. From stackoverflow : "Apple will silently drop your connection if it receives an invalid device token or a message that's too long. The next couple of messages after that will fail because they're just sent into the ether, essentially - the connection is closed, but the TCP window isn't exhausted." http://stackoverflow.com/questions/1759101/multiple-iphone-apn-messages-single-connection
Archived Comments
Don't suppose you know one for Blackberry Push do you?!
Now, I have to say, this is pretty cool. Ray, we'll give you a little more than 1% since you host all this great information in 1 place for all of us CF-ers. Thanks for the work Erik, I can see where this will come in handy to many of us in the future.
Great Post from 2 of my CF inspirations. I have a feeling this will become quite the search engine fodder in the days to come. Nice work Erik!
Ray,
This is a dumb question, I'm sure but I thought I'd ask anyway! In a multi-home environment, what should be entered as the server web root value when installing plugins? My setup is:
C:\\Jrun4\
\servers\
\cfusion\
D:\\Inetpub\
\apps\
\CFIDE\
\site1\
\site2\
\site3\
\extensions\ (CFCs outside root for security)
\....
\wwwroot\
\{EMPTY}
I've tried 3 or 4 extensions and have the same issue. I've been entering the path to my "apps" directory where the CFIDE lives, and then I usually install the extension inside the apps directory, sometimes in a "site" directory. The extension installs. Then when I try to run the extension (yours or anybody's), I'll get an error that pages can;t be found. In this case I, I'm in site1, right-click and choose JSONView>Do It, and I get: File not found: /site3/JSON View/handlers/getjson.cfm
There doesn't seem to be any info in the extensions docs about multi-home setups. I'm hoping the answer isn;t that I need to install it for every site I want to use it in (b/c I have about 40 sites).
Can you suggest what path I might enter so I can use extensions?
That isn't on topic for this thread. Please send in the question via my Contact link above.
Ray, no problem. Doing that now. Thx!
Hi guys,
this looks indeed really cool (and I thank you for that because we'll need this very soon), but I've heard apple servers'd prefer lot of info at the same time rather than lot of 'calls' with one info at the time. How would you do to group everything in the same payload, or send everything in a 'batch file'?
Thanks in advance,
Stéphane
You could store the messages in a db and use a scheduled task to send them out. Ie, if you sent them hourly you can get the messages from the past 60 minutes.
Thanks for your quick reply Ray!
thus something like
<cfloop query="qMessages">
<cfset APNSService.push(qMessages.token, qMessages.payload) />
</cfloop>
?
I was wondering if it'd do the trick; I'll give it a try.
Thanks again!
Yeah, that's what I meant. You may also want to include logic to 'mark' the db row when it is sent so you don't accidentally resend.
Hello i'm here again : I've tested but get an issue with the push method : errors says it's not defined (actually it is, but when I dump the object initially created, this method has got one argument, whereas I'm trying to call it with two : token and payload).
I've tried to javacast token and payload as strings so that no conflict with datatypes can occur, but doesn't help... Any clue?
No idea - sorry. This was a guest post - I haven't done it myself.
Any examples on using coldfusion with this and the request for inactive devices poll?
I'm also wondering if anyone has successfully used this with the request for inactive devices. Any example would be very much appreciated.
You can poll for inactive devices with this method...
APNSService.getInactiveDevices()
You'll get back a struct of inactive APNsTokens you can use to manage any records you are managing on those devices.
Does this work with CF8? Our CF9 test box works fine, CF8 says it can't find the push method on the last send line.
Are you sure the Java ob is correct? Try dumping it to see if the push method is there, and if so, what args it takes.
We have tried it on 2 CF9's and 2 CF8's. 9's work fine. 8's give us this:
The push method was not found.
Either there are no methods with the specified method name and argument types, or the push method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that matched the provided arguments. If this is a Java object and you verified that the method exists, you may need to use the javacast function to reduce ambiguity.
Looking further into it. I'll post if I find it.
Seems CF8 needs things a little differently. Team effort, one of our guys and aqx over at experts-exchange.
<cfset APNS = createObject( "java", "com.notnoop.apns.APNS" )>
<cfset APNSService = createObject( "java", "com.notnoop.apns.APNS" ).newService()
.withCert("c:\websites\apns-dev-cert.p12", "joemeade")
.withSandboxDestination()
.build() />
<cfset payload = createObject( "java", "com.notnoop.apns.APNS" ).newPayload()
.badge(0)
.alertBody("Just Checking.")
.sound("PushNotification.caf")
.build()/>
<cfset token = "264ffac53653a20ef657dcff02d5ce70aeb7c53bce0f517a8af08952539987f2">
<cfset APNSNotification = createObject ("java", "com.notnoop.apns.SimpleApnsNotification").init(javacast("string",token),javacast("string",payload)) />
<cfset APNSService.push(APNSNotification)>
My bad: AGX over at experts-exchanged helped debug this.
@Tim
Thanks a lot! That seems to work.
It looks like CF8 isn't recognizing the methods of the abstract class (AbstractApnsService), though, because getInactiveDevices() is also returning a "method not found" error.
Is this a problem unique to this library or is there a better workaround for CF8? My next step will be to re-compile it with an overriding method in ApnsServiceImpl that just calls the abstract class' getInactiveDevices().
Any updates to the getInactiveDevices() "method not found" issue?
I did some additional research and found that the issue wasn't with CF8. There were some changes with an update to the Java-apns library that caused it to stop working.
I wrote a UDF that works around it, though. You're free to use it if you'd like, although I can't offer any support. At the very least, I hope it gets you closer. I've uploaded the code to my personal site here:
http://www.bennlinger.com/u...
It should return a struct with a mapping of device tokens to dates (representing when they became inactive).
Just wanted to pass on my gratitude to you all for this post and the speedy response to my question!!!
Special thanks to Raymond Camden, Tim, Benn.
One suggestion would be to incorporate Tim's CF8 code in the article or note that CF8 code is below.
Hello, thank you for this interesting topic and also for the comments. I'm working with CF 8 on Win 2008 / JRE 1.6.24 and tried the code from comments no 19+23, but keep getting this error when performing the final function call:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
What am I missing here? Thanks in advance!
I got that message when my certificates were bad. In my case, they were either expired or invalidated through the provisioning portal.
In your situation, I'd first try re-downloading the certificates from the provisioning portal (and converting to .p12 as shown above). If that doesn't work, I'd create an entirely new App ID with push notifications enabled and try its certificates as a sanity check. If that works, then you know it's an issue with the other app's configuration on the provisioning portal.
Aside from that, I'm not sure what to tell you. That error is just telling you that Apple is rejecting your certificate.
Thank you - turred out that i had the wrong certificate, now everything is running fine!
I have been struggling getting this to work. I finally have all my classpaths correct and I'm getting this error:
The push method was not found.
Either there are no methods with the specified method name and argument types...
Here is all the code in my testPush.cfm file:
<cfset APNSService = createObject( "java", "com.notnoop.apns.APNS" ).newService()
.withCert("C:\apns-dev-cert.p12", "000000")
.withProductionDestination()
.build() />
<cfset payload = createObject( "java", "com.notnoop.apns.APNS" ).newPayload()
.badge(3)
.alertBody("Hello, world.")
.sound("PushNotification.caf")
.build()/>
<cfset APNSService.push("8367252c 52b31eee 9fcd2c00 7500725f 42520649 c5d8889a e10cc066 6bd73efd", payload) />
Help, please...
I actually didn't read a few posts up. I copied their code and it worked perfect. Thanks.
Hi,
What will be the element names in the structure returned by getInactiveDevices() method? Does anybody implemented this?
With regard to comment 23,
Benn,
I tried to implement the inactive device code posted in your site(bennlinger.com).
A push notification was sent initially and uninstalled the application from the device later. Then , I just passed the certificate path and password used to the function for checking inactive device, but it is returning an empty structure.
Any idea?
Has anyone tried using the "deviceTokens" parameter to send to multiple devices at once? The JavaDocs show that it is possible by passing in a "Collection<String>".
What encoding is the CF api expecting for the device token? On the Objective-C side, I Base64 encoded the NSData and then passed that off to my server. Should the CF decode it and re-encode it as something else? The api accepts a String token and String payload, how should the String token be encoded?
You could check the JavaDocs for the third-party library to be sure, but if it's accepting strings, I'm fairly sure they're Base64 encoded strings.
It turns out that the API above is expecting hex encoded strings.
http://stackoverflow.com/qu...
Hello,
First, thanks for your code.
Second, I had a problem with the push method. I used the Tim code and it work better. However, now, i've got this error :
"java.net.NoRouteToHostException: No route to host: connect "
and I really don't know why. It's first time for me to use a java class and I don't understand this error. Is there a network problem?
Path of my p12 is right. I tried the easy way with c:\mycert.p12, I tried a path accessible directly from my website and an other a little bit more secure but no way...
If someone have any idea, i'll be very grateful.
Thanks for your answers
Maël
Assumed if I have an alerts table with message (checked = 1 or 0 = new). Can you please show me to write a simple Apple App would would display the message with checked flag = 0 that would call APN to send push notification.
Would be highly appreciate if you could show us how to write this simple app, assume that we have everything is setup or as needed (as far as the development concern).
Thank you in advance. Awesome works on the APN push notification via iPhone app.
Did anyone figure out APNSService.getInactiveDevices()?
I'm still getting back method not found.
Finally got inactive devices to work!! Thanks to this blog and the link posted above: http://www.bennlinger.com/u...
Also, if it returns an empty struct, that's probably good -- It won't return anything until after you attempt a notification. IE, You send notifications out and then they will return which ones fail. They don't collect those ids as the person uninstalls, they collect them after you attempt to deliver a message.
we can't call this method mapper.writeValueAsString() in org.codehaus.jackson.ObjectCodec package
please tell some solution what i will do
How to do the same for android please? Is there any tutorial same as above for android push using coldfusion ?
Thanks a lot Raymond
I couldn't find any JAR file at: http://github.com/notnoop/j...
Can you please help me with that?
Here you go!
https://github.com/notnoop/...
@Jim Hankins Thanks
Can you please let me know if I have to do anything in CFAdmin except creating classpath? Do I need to import p12 file as we do in case of pem files?
Because currently I am getting the following error:
Security: The requested template has been denied access to ../JAR/DevPushCertificates.p12
I fixed the Access Rights issue. But now when I run the CF code mentioned above, it shows me plain while screen and no code bellow this code is executed. It seems there is some exception but I cannot see that. I have written the code in try catch but still I cannot see the exception. Can you please let me know how to debug this issue?
Just a note incase anyone is having the same problem I had. When you export your .p12 file from the Keychain Access. You may need to select both the certificate and the key at the same time and then select 'Export' 2 items to make a .p12 file.
I'm just starting a new project to enable Push Notifications on an existing iOS app, that another consulting firm developed. In addition, the app is actually compiled and distributed as a enterprise app by one of our clients. So, this is not "my app", and I'm not sure where to start. I also need to be able to send certain messages to certain users - how can I link device ids to users?
Ignore my last question. I'm actually starting to develop this now, and right off the bat I'm getting a strange exception:
<cfset LOCAL.APNS = createObject( "java", "com.notnoop.apns.APNS" ) />
<cfset LOCAL.APNSService =
CreateObject(
"java",
"com.notnoop.apns.APNS"
).newService(
).withCert(
"MyCert.p12",
""
).withSandboxDestination().build() />
java.io.IOException: failed to decrypt safe contents entry: java.lang.ArithmeticException: / by zero
Stack Trace:
com.notnoop.exceptions.InvalidSSLConfig: java.io.IOException: failed to decrypt safe contents entry: java.lang.ArithmeticException: / by zero
at com.notnoop.apns.internal.Utilities.newSSLContext(Utilities.java:102)
at com.notnoop.apns.ApnsServiceBuilder.withCert(ApnsServiceBuilder.java:139)
at com.notnoop.apns.ApnsServiceBuilder.withCert(ApnsServiceBuilder.java:114)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at coldfusion.runtime.StructBean.invoke(StructBean.java:511)
at coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2300)
Any ideas?
Nevermind, it was due to the fact that the apns lib requires the cert to have a password.
I am unable to call getInactiveDevices() in ColdFusion 8. The link above no longer works. I've started a SO post (http://stackoverflow.com/qu.... Really need help here.
My test doesn't work :(
Just in the line where I try to initialize the pusher component:
<cfset pusher = createObject("component","dasien.componentes.pushservices.pusher").init(
mode = "development",
appleCertificatePath = "#EXPANDPATH('componentes/pushservices/Certificados.p12')#",
appleCertificatePassword = "XXXXXX",
googleAPIKey = "XXXXXX"
)/>
The script stops and the server returns a blank page with no detail about the fail but with error code 500.
When I go to the Application server log I find this:
"Could not initialize class com.notnoop.apns.internal.Utilities"
However if I extract all the content of the jar file, the Utilities class is actually there. Hope you can help me :)
Erik and Ray. Thank you so much! Heros! ;-)
I know this is old (i need to write a new article with new libs), but this response may confuse people who still see this through google searches. #sigh ..
Before coding coldfusion apps, you should read the 101 docs for coldfusion about where to put things.
No such thing as extensions directories (as in a fixed place). In this case just setup 'custom tag paths' perhaps (not necessary but for 40 sites might be worth it). BTW path should be either relative to executing dir or a mapped path. All in basic CF docs.