Skip to main content

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(`\nTarget: ${TARGET_HANDLE}\n`);
    if (target) {
        console.log(formatLocation(target, "  "));
    } else {
        console.log("  Not sharing location");
    }

    // List all friends
    console.log(`\nAll 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:
handle
string
Phone number or email of the person sharing location
coordinates
[number, number]
Latitude and longitude: [lat, lng]
long_address
string
Full address (e.g., “123 Main St, City, State 12345”)
short_address
string
Shortened address (e.g., “City, State”)
status
string
Location status indicator
last_updated
number
Unix timestamp of when location was last updated
expiry
number
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'}\nhttps://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();

Formatting Helpers

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

Distance Format

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

  1. No locations returned - Verify:
    • iCloud is signed in on the server
    • Find My Friends is enabled
    • Contacts are sharing location with you
  2. Stale location data - Call refreshFindMyFriends() to force an update
  3. 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

Build docs developers (and LLMs) love