Overview
The SDK provides access to Apple’s Find My network, allowing you to retrieve location data for friends who are sharing their location with you and track your own devices.
Find My integration requires iCloud authentication and proper location sharing permissions. Users must be sharing their location with you through Find My Friends.
Find My Friends
Retrieve location data for friends sharing their location:
import type { FindMyLocationItem } from "../types/findmy" ;
import { createSDK } from "./utils" ;
const TARGET_HANDLE = process . env . TARGET_HANDLE || "+1234567890" ;
function formatLocation ( loc : FindMyLocationItem , indent = "" ) : string {
const lines = [
` ${ indent } Coordinates: ${ loc . coordinates [ 0 ] } , ${ loc . coordinates [ 1 ] } ` ,
` ${ indent } Maps: https://maps.google.com/?q= ${ loc . coordinates [ 0 ] } , ${ loc . coordinates [ 1 ] } ` ,
];
if ( loc . long_address ) lines . push ( ` ${ indent } Address: ${ loc . long_address } ` );
if ( loc . expiry ) {
const remaining = Math . floor (( loc . expiry - Date . now ()) / 60000 );
lines . push ( ` ${ indent } Expires in: ${ remaining } minutes` );
}
return lines . join ( " \n " );
}
async function main () {
const sdk = createSDK ();
await sdk . connect ();
const locations = await sdk . icloud . refreshFindMyFriends ();
// Check target handle
const target = locations . find (( l ) => l . handle === TARGET_HANDLE );
console . log ( ` \n Target: ${ TARGET_HANDLE } \n ` );
if ( target ) {
console . log ( formatLocation ( target , " " ));
} else {
console . log ( " Not sharing location" );
}
// List all friends
console . log ( ` \n All Friends ( ${ locations . length } ):` );
for ( const loc of locations ) {
console . log ( ` \n ${ loc . handle } ` );
console . log ( formatLocation ( loc , " " ));
}
process . exit ( 0 );
}
main (). catch ( console . error );
Location Data Structure
Each location object contains:
Phone number or email of the person sharing location
Latitude and longitude: [lat, lng]
Full address (e.g., “123 Main St, City, State 12345”)
Shortened address (e.g., “City, State”)
Location status indicator
Unix timestamp of when location was last updated
Unix timestamp when location data expires
Real-Time Location Updates
Listen for location updates as they happen:
import type { FindMyLocationItem } from "../types/findmy" ;
import { createSDK , handleExit } from "./utils" ;
function formatTime ( timestamp : number ) : string {
return new Date ( timestamp ). toLocaleTimeString ();
}
function formatLocation ( loc : FindMyLocationItem , indent = " " ) : string {
const parts = [
` ${ indent } Coordinates: ${ loc . coordinates [ 0 ] } , ${ loc . coordinates [ 1 ] } ` ,
` ${ indent } Maps: https://maps.google.com/?q= ${ loc . coordinates [ 0 ] } , ${ loc . coordinates [ 1 ] } ` ,
];
if ( loc . long_address ) parts . push ( ` ${ indent } Address: ${ loc . long_address } ` );
if ( loc . short_address ) parts . push ( ` ${ indent } Short: ${ loc . short_address } ` );
parts . push ( ` ${ indent } Status: ${ loc . status } ` );
parts . push ( ` ${ indent } Updated: ${ formatTime ( loc . last_updated ) } ` );
if ( loc . expiry ) {
const remaining = Math . floor (( loc . expiry - Date . now ()) / 60000 );
parts . push ( ` ${ indent } Expires in: ${ remaining } minutes` );
}
return parts . join ( " \n " );
}
async function main () {
const sdk = createSDK ();
sdk . on ( "ready" , async () => {
console . log ( " \n --- Initial locations ---" );
try {
const locations = await sdk . icloud . refreshFindMyFriends ();
if ( locations . length === 0 ) {
console . log ( " No friends sharing location." );
}
for ( const loc of locations ) {
console . log ( ` \n ${ loc . handle } ` );
console . log ( formatLocation ( loc ));
}
} catch ( err ) {
console . error ( "Failed to fetch initial locations:" , err );
}
console . log ( " \n --- Watching for updates (Ctrl+C to stop) ---" );
console . log ( " Server auto-refreshes every 30s. \n " );
});
sdk . on ( "new-findmy-location" , ( location : FindMyLocationItem ) => {
console . log ( `[ ${ formatTime ( Date . now ()) } ] ${ location . handle } updated:` );
console . log ( formatLocation ( location ));
console . log ();
});
await sdk . connect ();
handleExit ( sdk );
}
main (). catch ( console . error );
Events
Fired when a friend’s location updates: sdk . on ( "new-findmy-location" , ( location : FindMyLocationItem ) => {
console . log ( ` ${ location . handle } is at:` );
console . log ( ` ${ location . coordinates [ 0 ] } , ${ location . coordinates [ 1 ] } ` );
console . log ( ` ${ location . long_address } ` );
});
Common Use Cases
Location Tracker
const locationHistory = new Map < string , FindMyLocationItem []>();
sdk . on ( "new-findmy-location" , ( location ) => {
const handle = location . handle ;
const history = locationHistory . get ( handle ) || [];
history . push ( location );
locationHistory . set ( handle , history );
console . log ( ` ${ handle } has ${ history . length } recorded locations` );
});
Proximity Alert
const HOME_COORDS = [ 37.7749 , - 122.4194 ]; // San Francisco
const ALERT_RADIUS = 1 ; // km
function getDistance ( lat1 : number , lon1 : number , lat2 : number , lon2 : number ) : number {
const R = 6371 ; // Earth's radius in km
const dLat = ( lat2 - lat1 ) * Math . PI / 180 ;
const dLon = ( lon2 - lon1 ) * Math . PI / 180 ;
const a =
Math . sin ( dLat / 2 ) * Math . sin ( dLat / 2 ) +
Math . cos ( lat1 * Math . PI / 180 ) * Math . cos ( lat2 * Math . PI / 180 ) *
Math . sin ( dLon / 2 ) * Math . sin ( dLon / 2 );
const c = 2 * Math . atan2 ( Math . sqrt ( a ), Math . sqrt ( 1 - a ));
return R * c ;
}
sdk . on ( "new-findmy-location" , async ( location ) => {
const [ lat , lng ] = location . coordinates ;
const distance = getDistance ( HOME_COORDS [ 0 ], HOME_COORDS [ 1 ], lat , lng );
if ( distance < ALERT_RADIUS ) {
console . log ( `🏠 ${ location . handle } is near home!` );
// Send notification
const chat = await findChatForHandle ( location . handle );
if ( chat ) {
await sdk . messages . sendMessage ({
chatGuid: chat . guid ,
message: "Welcome home!" ,
});
}
}
});
Location Sharing Bot
sdk . on ( "new-message" , async ( message ) => {
if ( message . isFromMe ) return ;
const text = message . text ?. toLowerCase () || "" ;
const sender = message . handle ?. address ;
const chat = message . chats ?.[ 0 ];
if ( ! chat || ! sender ) return ;
if ( text . includes ( "where are you" ) || text === "location?" ) {
const locations = await sdk . icloud . refreshFindMyFriends ();
const myLocation = locations . find ( l => l . handle === sender );
if ( myLocation ) {
const [ lat , lng ] = myLocation . coordinates ;
await sdk . messages . sendMessage ({
chatGuid: chat . guid ,
message: `I'm at: ${ myLocation . long_address || 'Unknown' } \n https://maps.google.com/?q= ${ lat } , ${ lng } ` ,
});
} else {
await sdk . messages . sendMessage ({
chatGuid: chat . guid ,
message: "Location sharing not enabled." ,
});
}
}
});
ETA Calculator
const DESTINATIONS = {
"office" : [ 37.7749 , - 122.4194 ],
"home" : [ 37.8044 , - 122.2712 ],
};
sdk . on ( "new-message" , async ( message ) => {
const text = message . text ?. toLowerCase () || "" ;
const sender = message . handle ?. address ;
const chat = message . chats ?.[ 0 ];
if ( ! chat || ! sender ) return ;
const etaMatch = text . match ( /eta to ( \w + ) / i );
if ( etaMatch ) {
const destination = etaMatch [ 1 ]. toLowerCase ();
const destCoords = DESTINATIONS [ destination ];
if ( destCoords ) {
const locations = await sdk . icloud . refreshFindMyFriends ();
const userLoc = locations . find ( l => l . handle === sender );
if ( userLoc ) {
const distance = getDistance (
userLoc . coordinates [ 0 ],
userLoc . coordinates [ 1 ],
destCoords [ 0 ],
destCoords [ 1 ]
);
// Assume average speed of 50 km/h
const eta = Math . round ( distance / 50 * 60 ); // minutes
await sdk . messages . sendMessage ({
chatGuid: chat . guid ,
message: `ETA to ${ destination } : ~ ${ eta } minutes ( ${ distance . toFixed ( 1 ) } km away)` ,
});
}
}
}
});
Refresh Interval
The server automatically refreshes Find My locations every 30 seconds. You can manually trigger a refresh with: const locations = await sdk . icloud . refreshFindMyFriends ();
Google Maps Link
function getMapsLink ( lat : number , lng : number ) : string {
return `https://maps.google.com/?q= ${ lat } , ${ lng } ` ;
}
const link = getMapsLink ( location . coordinates [ 0 ], location . coordinates [ 1 ]);
Time Remaining
function getTimeRemaining ( expiry : number ) : string {
const minutes = Math . floor (( expiry - Date . now ()) / 60000 );
if ( minutes > 60 ) {
return ` ${ Math . floor ( minutes / 60 ) } h ${ minutes % 60 } m` ;
}
return ` ${ minutes } m` ;
}
function formatDistance ( km : number ) : string {
if ( km < 1 ) {
return ` ${ Math . round ( km * 1000 ) } m` ;
}
return ` ${ km . toFixed ( 1 ) } km` ;
}
Best Practices
Only access location data when necessary and with proper consent: // Store user preferences
const allowedUsers = new Set ([ "+1234567890" ]);
sdk . on ( "new-findmy-location" , ( location ) => {
if ( allowedUsers . has ( location . handle )) {
// Process location
}
});
Check if location data is still valid: if ( location . expiry && Date . now () > location . expiry ) {
console . log ( "Location data expired, refreshing..." );
await sdk . icloud . refreshFindMyFriends ();
}
Reduce API calls by caching: const locationCache = new Map ();
const CACHE_TTL = 30000 ; // 30 seconds
async function getLocation ( handle : string ) {
const cached = locationCache . get ( handle );
if ( cached && Date . now () - cached . timestamp < CACHE_TTL ) {
return cached . data ;
}
const locations = await sdk . icloud . refreshFindMyFriends ();
const location = locations . find ( l => l . handle === handle );
if ( location ) {
locationCache . set ( handle , {
data: location ,
timestamp: Date . now (),
});
}
return location ;
}
Location sharing must be enabled in iCloud settings. Users must explicitly share their location with your Apple ID.
Use the long_address field for human-readable addresses instead of raw coordinates when displaying location info to users.
Troubleshooting
No locations returned - Verify:
iCloud is signed in on the server
Find My Friends is enabled
Contacts are sharing location with you
Stale location data - Call refreshFindMyFriends() to force an update
Missing addresses - Some locations may not have address data, always check:
const address = location . long_address || location . short_address || "Unknown location" ;
Next Steps
Auto-Reply Bot Combine with messaging
Scheduled Messages Send location-based reminders