Coldfusion FindMyiPhone Component

Yet another requirement of my current project is to be able to periodically report back the physical location of the device as the drivers deliver their loads wherever they are deliverying them. Also handy to be able to detect if the device is charging and the battery status — and send an alert to the user if battery condition is a problem.

At first I looked at doing all of this within the browser, but the problem is that the driver’s will have to allow location status during during every login session. No, thanks. First, there will be problems when they accidentally hit “no.” Second, there will be problems when they intentionally hit “no.”

So I did some googling into ways to locate an iPad or iPhone remotely (since we’re standardizing this project on iPad’s). Surely there must be some app that can find an iphone. Maybe … find … my … iPhone.

Right. I’ve used that before, when I left my phone in a cab in Chicago last summer, as a matter of fact. A little bit of Googling later and I discovered PHP-FindMyiPhone by Alan Beebe (https://github.com/albeebe/PHP-FindMyiPhone/blob/master/class.findmyiphone.php). The PHP script appeared to do everything I needed it to and then some, but of course I’m not working on PHP servers, I’m working on Coldfusion servers. So my next step was to take Alan’s source code (which tested as working when I ran it with PHP on my Macbook) and rewrite it as a Coldfusion component.

My task here was to adapt the code and see if I could make it work. So I set up a CFC, then copied and pasted the PHP code into a comment. Then I began to work out the logic to make it work.

In Alan Beebe’s class, the first function is to initialize the class and authenticate. The PHP code looks like this:

private $client = array(
						"app-version" => "4.0",
						"user-agent" => "FindMyiPhone/472.1 CFNetwork/711.1.12 Darwin/14.0.0",
						"headers" => array(
							"X-Apple-Realm-Support" => "1.0",
							"X-Apple-Find-API-Ver" => "3.0",
							"X-Apple-AuthScheme" => "UserIdGuest"
						)
					  );
	private $debug;
	private $username;
	private $password;
	private $Apple_MMe_Host;
	private $Apple_MMe_Scope;
	public $devices = array();
	
	/**
     * This is where you initialize FindMyiPhone with your iCloud credentials
     * Example: $fmi = new FindMyiPhone("you@example.com", "MyPassWord123");
     *
     * @param username	iCloud username
     * @param password	iCloud password
     * @param debug		(Optional) Set to TRUE and all the API requests and responses will be printed out
     * @return          FindMyiPhone instance 
     */
	public function __construct($username, $password, $debug = false) {
		$this->username = $username;
		$this->password = $password;
		$this->debug = $debug;
		$this->authenticate();
	}

As you can see, at the end of the _construct function, the code calls authenticate().

private function authenticate() {
		$url = "https://fmipmobile.icloud.com/fmipservice/device/".$this->username."/initClient";
		list($headers, $body) = $this->curlPOST($url, "", $this->username.":".$this->password);
		$this->Apple_MMe_Host = $headers["X-Apple-MMe-Host"];
		$this->Apple_MMe_Scope = $headers["X-Apple-MMe-Scope"];
		if ($headers["http_code"] == 401) {
			throw new Exception('Your iCloud username and/or password are invalid');
		}
	}

The authentication code goes out to the website and logs in, then returns the host future calls need to be made to as well as the “X-Apple-MMe-Scope” which I believe refers to the specific user.

I put the various parameters in the CFHTTP calls, so my init looks like this:

<cffunction name="init" output="true">
	<cfargument name="iCloudID">
	<cfargument name="iCloudPass">
	<cfset iCLoudObj = authenticate(iCloudID, iCLoudPass)>
</cffunction>

Just like the PHP component, I’m calling an authenticate message, in this case passing through the iCloud email and password.

The Coldfusion authenticate() method looks like this:

	<cffunction name="authenticate" output="true">
		<cfargument name="iCloudID">
		<cfargument name="iCloudPass">
		
		<cfset var iCloudObj = structnew()>
		
		<cfhttp url="https://fmipmobile.icloud.com/fmipservice/device/#iCloudID#/initClient" method="post" username="#iCloudID#" password="#iCloudPass#" useragent="FindMyiPhone/472.1 CFNetwork/711.5.6 Darwin/14.0.0">
			<cfhttpparam type="header" name="X-Apple-Realm-Support" value="1.0">
			<cfhttpparam type="header" name="X-Apple-Find-API-Ver" value="3.0">
			<cfhttpparam type="header" name="X-Apple-AuthScheme" value="UserIDGuest">
			<cfhttpparam type="header" name="Accept" value="*/*">
		</cfhttp>
		
		<cfset Responseheader = cfhttp.Responseheader>
		<cfset iCloudObj.Apple_MMe_Host = Responseheader["X-Apple-MMe-Host"]>
		<cfset iCloudObj.Apple_MMe_Scope = Responseheader["X-Apple-MMe-Scope"]>
		<cfset iCloudObj.iCloudID = arguments.iCloudID>
		<cfset iCloudObj.iCloudPass = arguments.iCloudPass>
		<cfset iCloudObj.Cookies = GetResponseCookies(cfhttp)>
		<cfset iCloudObj.set_cookie = Responseheader["Set-Cookie"]>
	
		<cfreturn iCloudObj>		
	</cffunction>

I basically had to iterate through to get this working. First I did the query, got the CFHTTP response, then started assogmomg values. At the end of the authenticate method, we return an iCloudObj to the init function, and that’s now available throughout the component.

The calling Coldfusion page only needs to pass in the username and password like this:

<cfset iCloudObject.init(“email@domain.net”,”password”)>

But we don’t want to just initialize it. We want to get the devices. So the next line calls the getDevices method: <cfset deviceList = iCloudObject.getDevices()>

This is actually going to be two functions — first, getDevices() which will make the http request, then generateDevice() which returns a query containing the objects. Not that the logic here is a little different than the PHP class, but it meets my needs for this function. Here’s the code:

<cffunction name="getDevices">
  <cfset var thisUrl = "https://" & iCloudObj.Apple_MMe_Host & "/fmipservice/device/" & iCloudObj.Apple_MMe_Scope & "/initClient">
  <cfset var objCookies = iCloudObj.cookies>
  
  <cfhttp url="#thisUrl#" method="post" username="#iCloudObj.iCloudID#" password="#iCloudObj.iCloudPass#" useragent="FindMyiPhone/472.1 CFNetwork/711.1.12 Darwin/14.0.0">
   <cfloop item="strCookie" collection="#objCookies#">
    <cfhttpparam type="COOKIE" name="#strCookie#" value="#objCookies[ strCookie ].Value#" />
   </cfloop>
   <cfhttpparam type="header" name="X-Apple-Realm-Support" value="1.0">
   <cfhttpparam type="header" name="X-Apple-Find-API-Ver" value="3.0">
   <cfhttpparam type="header" name="X-Apple-AuthScheme" value="UserIDGuest">
   <cfhttpparam type="header" name="host" value="#iCloudObj.Apple_MMe_Host#">
      
  </cfhttp>
   
  <cfset returnObject = DeSerializeJSON(cfhttp.filecontent)>
  <cfset returnObject = generateDevice(returnObject.content)>
  
  <cfreturn ReturnObject>
 </cffunction>
 
 
 <cffunction name="generateDevice">
  <cfargument name="DeviceList">
  
  <cfset var Device = querynew("deviceClass, deviceDisplayName, deviceStatus, id, isLocating, rawDeviceModel, name, latitude, longitude, isInaccurate, positiontype")>
  <cfloop array="#DeviceList#" index="Device_index">
   <cfset thisDevice = device_index>
   <cfset queryaddrow(device,1)>
   <cfset querysetcell(device, "deviceclass", thisdevice.deviceclass)>
   <cfset querysetcell(device, "deviceDisplayName", thisdevice.deviceDisplayName)>
   <cfset querysetcell(device, "deviceStatus", thisdevice.deviceStatus)>
   <cfset querysetcell(device, "id", thisdevice.id)>
   <cfset querysetcell(device, "isLocating", thisdevice.isLocating)>
   <cfset querysetcell(device, "rawDeviceModel", thisdevice.rawDeviceModel)>
   <cfset querysetcell(device, "name", thisdevice.name)>
   <cfset i = thisdevice.location>
    <cfset querysetcell(device, "latitude", i.latitude)>
    <cfset querysetcell(device, "longitude", i.longitude)>
    <cfset querysetcell(device, "isInaccurate", i.isInaccurate)>
    <cfset querysetcell(device, "positiontype", i.positiontype)>
  </cfloop>
  <cfreturn device>
 </cffunction>

Now we’re getting somewhere. Back on my text page, I cfdumped the returned object from getDevices and here is what I got:

Screen Shot 2015-08-31 at 11.17.33 AM

After multiple tries, it looks like I don’t get results 100% of the time — maybe 80 to 90. So some error correction is in order. But all in all, I was pleased to have a working findmyiphone function from Coldfusion.

Leave a Reply

Your email address will not be published. Required fields are marked *