UNPKG

findagrave-client

Version:

TypeScript client for FindAGrave GraphQL and REST APIs

493 lines (491 loc) 14.6 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { FindAGraveClient: () => FindAGraveClient, browseContinent: () => browseContinent, browseCountry: () => browseCountry, browseFromTop: () => browseFromTop, createFindAGraveClient: () => createFindAGraveClient, default: () => index_default, getCemeteriesNear: () => getCemeteriesNear, searchCemeteries: () => searchCemeteries, searchLocations: () => searchLocations, searchMemorials: () => searchMemorials, searchMemorialsGeneral: () => searchMemorialsGeneral }); module.exports = __toCommonJS(index_exports); var import_core = require("@urql/core"); var PERSISTED_QUERIES = { locationTypeahead: "48272a8b942641e1b3041ac159efd11d5ce4cda9d869a6f9f53468e04fab8ba5", browse: "bfb78b85f6be61da441f58ab1e538e6b36ba50f5437b177f7826953eb9ee02c8", browseStart: "aaba96c146d4145d9a29a44e13cf1a0db9f71276a24ec7a0e6d67d2f3968279e", locationCoordinates: "4c83ce4a0faca6f9b4d38ba441a83eebc72fff8018d365093bc1960594825c8b", getCemeteriesInBbox: "edb98695eaea673b2c57597571c51d2477019b5bb547d350b50393d5b9ee6dd4" }; var FindAGraveClient = class { client; baseUrl = "https://www.findagrave.com"; constructor() { this.client = new import_core.Client({ url: `${this.baseUrl}/orc/graphql`, exchanges: [import_core.cacheExchange, import_core.fetchExchange], fetchOptions: { headers: { "ancestry-clientpath": "findagrave-frontend", "apollographql-client-name": "findagrave-cemetery-landing", accept: "*/*", "content-type": "application/json", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" } } }); } /** * Search for cemeteries by name */ async searchCemeteries(name, returnHighlight = true) { const variables = { search: { name, autocomplete: "name", categories: ["cem"], returnHighlight } }; const query = import_core.gql` query LocationTypeahead($search: LocationSearchInput!) { locationsByTypeahead(search: $search) { total locations { id names { name language } locations { cemetery cemeteryId city cityId county countyId state stateId country countryId continent continentId } coordinates { lat lon } highlight ... on Cemetery { memorialCount photographedCount photoRequestCount gpsCount coordinatePrecision } } } } `; const result = await this.client.query(query, variables, { fetchOptions: { method: "GET" } }); if (result.error) { throw new Error(`Cemetery search failed: ${result.error.message}`); } return result.data.locationsByTypeahead; } /** * Search for locations (cities, counties, states, etc.) */ async searchLocations(name, returnHighlight = true) { const variables = { search: { name, autocomplete: "location", categories: ["yes"], returnHighlight } }; const query = import_core.gql` query LocationTypeahead($search: LocationSearchInput!) { locationsByTypeahead(search: $search) { total locations { id names { name language } locations { city cityId county countyId state stateId country countryId continent continentId } coordinates { lat lon } highlight } } } `; const result = await this.client.query(query, variables, { fetchOptions: { method: "GET" } }); if (result.error) { throw new Error(`Location search failed: ${result.error.message}`); } return result.data.locationsByTypeahead; } /** * Get cemeteries within a geographic bounding box */ async getCemeteriesInBoundingBox(searchParams) { const query = import_core.gql` query GetCemeteriesInBbox($search: CemeterySearchInput!) { bulkCemeterySearch(search: $search) { cemeteries { id names { name language } locations { cemetery city county state country continent } coordinates { lat lon } coordinatePrecision memorialCount photographedCount photoRequestCount gpsCount } } } `; const result = await this.client.query( query, { search: searchParams }, { fetchOptions: { method: "GET" } } ); if (result.error) { throw new Error(`Bounding box search failed: ${result.error.message}`); } return result.data.bulkCemeterySearch; } /** * Browse locations hierarchically (continents -> countries -> states -> counties -> cities) */ async browseLocations(parents, ignoreCemeteries = true, hasCemeteries = true) { const query = import_core.gql` query Browse($parents: [String!]!, $ignoreCemeteries: Boolean, $hasCemeteries: Boolean) { browse(parents: $parents, ignoreCemeteries: $ignoreCemeteries, hasCemeteries: $hasCemeteries) { id locations { id names { name } locations { city county state country continent } coordinates { lat lon } } } } `; const result = await this.client.query( query, { parents, ignoreCemeteries, hasCemeteries }, { fetchOptions: { method: "GET" } } ); if (result.error) { throw new Error(`Browse locations failed: ${result.error.message}`); } return result.data.browse; } /** * Get location details by ID */ async getLocationById(ids) { const query = import_core.gql` query GetLocationById($ids: [String!]!) { locationsById(ids: $ids) { locations { id locations { city cityId county countyId state stateId country countryId continentId } coordinates { lat lon } } } } `; const result = await this.client.query( query, { ids }, { fetchOptions: { method: "GET" } } ); if (result.error) { throw new Error(`Get location by ID failed: ${result.error.message}`); } return result.data.locationsById; } /** * Get coordinates for location IDs */ async getLocationCoordinates(ids) { const query = import_core.gql` query GetLocationCoordinates($ids: [String!]!) { locations(ids: $ids) { locations { id coordinates { lat lon } } } } `; const result = await this.client.query( query, { ids }, { fetchOptions: { method: "GET" } } ); if (result.error) { throw new Error(`Get location coordinates failed: ${result.error.message}`); } return result.data.locations; } /** * Search for memorials/individuals using the REST endpoint * This uses the native fetch API since memorial searches use REST, not GraphQL */ async searchMemorials(cemeteryId, params) { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== void 0 && value !== null) { searchParams.append(key, value.toString()); } }); const url = `${this.baseUrl}/cemetery/${cemeteryId}/memorial-search?${searchParams.toString()}`; const response = await fetch(url, { method: "GET", headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language": "en-US,en;q=0.5", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", dnt: "1" } }); if (!response.ok) { throw new Error(`Memorial search failed: ${response.status} ${response.statusText}`); } return await response.text(); } /** * Search for memorials/individuals across all cemeteries (general search) * This uses the main memorial search endpoint without specifying a cemetery */ async searchMemorialsGeneral(params) { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== void 0 && value !== null) { searchParams.append(key, value.toString()); } }); const url = `${this.baseUrl}/memorial-search?${searchParams.toString()}`; const response = await fetch(url, { method: "GET", headers: { accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "accept-language": "en-US,en;q=0.5", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", dnt: "1" } }); if (!response.ok) { throw new Error(`General memorial search failed: ${response.status} ${response.statusText}`); } return await response.text(); } /** * Alternative method using direct URL construction with persisted queries * This mimics exactly how the browser makes requests */ async makePersistedQuery(operationName, variables) { const encodedVariables = encodeURIComponent(JSON.stringify(variables)); const encodedExtensions = encodeURIComponent( JSON.stringify({ persistedQuery: { version: 1, sha256Hash: PERSISTED_QUERIES[operationName] } }) ); const url = `${this.baseUrl}/orc/graphql?operationName=${operationName}&variables=${encodedVariables}&extensions=${encodedExtensions}`; const response = await fetch(url, { method: "GET", headers: { "ancestry-clientpath": "findagrave-frontend", "apollographql-client-name": "findagrave-cemetery-landing", accept: "*/*", "content-type": "application/json", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" } }); if (!response.ok) { throw new Error(`Persisted query failed: ${response.status} ${response.statusText}`); } return await response.json(); } }; function createFindAGraveClient() { return new FindAGraveClient(); } async function searchCemeteries(name) { const client = createFindAGraveClient(); const result = await client.searchCemeteries(name); return result.locations; } async function searchLocations(name) { const client = createFindAGraveClient(); const result = await client.searchLocations(name); return result.locations; } async function searchMemorials(cemeteryId, firstname, lastname, cemeteryName) { const client = createFindAGraveClient(); return await client.searchMemorials(cemeteryId, { firstname, lastname, cemeteryName }); } async function searchMemorialsGeneral(firstname, lastname, location) { const client = createFindAGraveClient(); return await client.searchMemorialsGeneral({ firstname, lastname, location }); } async function getCemeteriesNear(centerLat, centerLon, radiusDegrees = 0.1) { const client = createFindAGraveClient(); const result = await client.getCemeteriesInBoundingBox({ from: 0, size: 9999, boundingBox: { top_left: { lat: centerLat + radiusDegrees, lon: centerLon - radiusDegrees }, bottom_right: { lat: centerLat - radiusDegrees, lon: centerLon + radiusDegrees } } }); return result.cemeteries; } async function browseFromTop() { const client = createFindAGraveClient(); const result = await client.browseLocations(["top"]); return result.length > 0 ? result[0].locations : []; } async function browseContinent(continentId) { const client = createFindAGraveClient(); const result = await client.browseLocations(["top", continentId]); return result.find((r) => r.id === continentId)?.locations || []; } async function browseCountry(continentId, countryId) { const client = createFindAGraveClient(); const result = await client.browseLocations(["top", continentId, countryId]); return result.find((r) => r.id === countryId)?.locations || []; } var index_default = FindAGraveClient; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { FindAGraveClient, browseContinent, browseCountry, browseFromTop, createFindAGraveClient, getCemeteriesNear, searchCemeteries, searchLocations, searchMemorials, searchMemorialsGeneral });