UNPKG

google-places-core

Version:

A lightweight Google Places API logic library.

237 lines (229 loc) 8.96 kB
'use strict'; const debounce = (func, delay) => { let timeoutId; const debouncedFunction = (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); }; debouncedFunction.cancel = () => { clearTimeout(timeoutId); }; return debouncedFunction; }; class GooglePlacesManager { constructor(service, debounceTime = 300) { this.state = { predictions: [], isLoading: false, error: null, }; this.subscribers = new Set(); this.fetchPredictionsInternal = async (input) => { if (!input) { this.setState({ predictions: [], isLoading: false, error: null }); return; } this.setState({ isLoading: true, error: null }); try { const predictions = await this.placesService.fetchPredictions(input); this.setState({ predictions, isLoading: false }); } catch (error) { console.error('Error fetching predictions:', error); this.setState({ predictions: [], isLoading: false, error }); } }; this.placesService = service; this.debouncedGetPredictions = debounce(this.fetchPredictionsInternal, debounceTime); } setState(newState) { this.state = Object.assign(Object.assign({}, this.state), newState); this.subscribers.forEach(callback => callback(this.state)); } updateSearchInput(input) { this.debouncedGetPredictions(input); } async getPlaceDetails(placeId) { try { return await this.placesService.fetchPlaceDetails(placeId); } catch (error) { console.error('Error fetching place details:', error); this.setState({ error }); throw error; } } subscribe(callback) { this.subscribers.add(callback); callback(this.state); return () => { this.subscribers.delete(callback); }; } getState() { return Object.assign({}, this.state); } destroy() { this.debouncedGetPredictions.cancel(); this.subscribers.clear(); } } class Helpers { constructor(apiKey) { this.apiKey = apiKey; } checkBrowserEnvironment() { return typeof window !== 'undefined' && typeof document !== 'undefined'; } isLoaded() { var _a, _b; return !!((_b = (_a = window.google) === null || _a === void 0 ? void 0 : _a.maps) === null || _b === void 0 ? void 0 : _b.places); } isScriptInjected() { return Array.from(document.scripts).some((s) => s.src.includes('maps.googleapis.com/maps/api/js')); } injectScript() { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = `https://maps.googleapis.com/maps/api/js?key=${this.apiKey}&libraries=places`; script.async = true; script.defer = true; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Google Maps API')); document.head.appendChild(script); }); } waitForLoad() { return new Promise((resolve) => { const check = () => this.isLoaded() ? resolve() : setTimeout(check, 100); setTimeout(() => resolve(), 10000); // Timeout after 10s check(); }); } } class GooglePlacesWebService extends Helpers { constructor(apiKey) { if (!apiKey) throw new Error('GoogleMapService: API Key must be provided.'); super(apiKey); this.loadPromise = null; this.isBrowser = this.checkBrowserEnvironment(); } // Public API Methods async fetchPredictions(input) { await this.ensureReady(); return this.getPredictions(input); } async fetchPlaceDetails(placeId) { await this.ensureReady(); return this.getPlaceDetails(placeId); } // Core Implementation async ensureReady() { if (!this.isBrowser) throw new Error('Browser environment required'); if (this.isLoaded()) return; if (!this.loadPromise) this.loadPromise = this.loadGoogleMaps(); await this.loadPromise; } async loadGoogleMaps() { if (!this.isScriptInjected()) await this.injectScript(); await this.waitForLoad(); this.initServices(); } getPredictions(input) { return new Promise((resolve, reject) => { this.autocompleteService.getPlacePredictions({ input }, (predictions, status) => { status === window.google.maps.places.PlacesServiceStatus.OK ? resolve(this.transformPredictions(predictions || [])) : reject(new Error(`Places API error: ${status}`)); }); }); } getPlaceDetails(placeId) { return new Promise((resolve, reject) => { this.placesService.getDetails({ placeId, fields: ['geometry', 'formatted_address'] }, (place, status) => { status === window.google.maps.places.PlacesServiceStatus.OK ? resolve(this.transformPlaceDetails(place)) : reject(new Error(`Places details error: ${status}`)); }); }); } initServices() { this.autocompleteService = new window.google.maps.places.AutocompleteService(); this.placesService = new window.google.maps.places.PlacesService(document.createElement('div')); } transformPredictions(predictions) { return predictions.map(p => ({ description: p.description, place_id: p.place_id, structured_formatting: { main_text: p.structured_formatting.main_text, secondary_text: p.structured_formatting.secondary_text, }, types: p.types || [], })); } transformPlaceDetails(place) { return { formatted_address: place.formatted_address, geometry: { location: { lat: place.geometry.location.lat(), lng: place.geometry.location.lng(), }, viewport: place.geometry.viewport ? { south: place.geometry.viewport.getSouthWest().lat(), west: place.geometry.viewport.getSouthWest().lng(), north: place.geometry.viewport.getNorthEast().lat(), east: place.geometry.viewport.getNorthEast().lng(), } : undefined, }, name: place.name, place_id: place.place_id, }; } } // Here, we can use the HTTP API for the native implementation class GooglePlacesNativeService { constructor(apiKey) { this.fetchPredictions = async (input) => { const response = await fetch(`https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${encodeURIComponent(input)}&language=en&key=${this.API_KEY}`); if (!response.ok) { const errorData = await response.json(); console.error('Google Places Autocomplete Error:', errorData); throw new Error(`Failed to fetch predictions: ${errorData.error_message || response.statusText}`); } const data = await response.json(); return data.predictions || []; }; this.fetchPlaceDetails = async (placeId) => { const response = await fetch(`https://maps.googleapis.com/maps/api/place/details/json?place_id=${placeId}&fields=geometry,formatted_address&key=${this.API_KEY}`); if (!response.ok) { const errorData = await response.json(); console.error('Google Places Details Error:', errorData); throw new Error(`Failed to fetch place details: ${errorData.error_message || response.statusText}`); } const data = await response.json(); return data.result; }; if (!apiKey) throw new Error('GoogleMapService: API Key must be provided.'); this.API_KEY = apiKey; } } function createGooglePlacesManager(apiKey, platform = 'web', options) { const service = platform === 'web' ? new GooglePlacesWebService(apiKey) : new GooglePlacesNativeService(apiKey); return new GooglePlacesManager(service, options === null || options === void 0 ? void 0 : options.debounceTime); } exports.GooglePlacesManager = GooglePlacesManager; exports.GooglePlacesNativeService = GooglePlacesNativeService; exports.GooglePlacesWebService = GooglePlacesWebService; exports.createGooglePlacesManager = createGooglePlacesManager; //# sourceMappingURL=index.js.map