94 lines
2.6 KiB
TypeScript
94 lines
2.6 KiB
TypeScript
/**
|
|
* @ai-summary Google Maps JavaScript API loader
|
|
* Handles dynamic loading and singleton pattern
|
|
*/
|
|
|
|
import { getGoogleMapsApiKey } from '@/core/config/config.types';
|
|
|
|
let mapsPromise: Promise<void> | null = null;
|
|
|
|
/**
|
|
* Load Google Maps JavaScript API dynamically
|
|
* Uses singleton pattern - only loads once
|
|
*
|
|
* @returns Promise that resolves when Google Maps is loaded
|
|
*/
|
|
export function loadGoogleMaps(): Promise<void> {
|
|
// Return cached promise if already loading/loaded
|
|
if (mapsPromise) {
|
|
return mapsPromise;
|
|
}
|
|
|
|
// Create loading promise
|
|
mapsPromise = new Promise((resolve, reject) => {
|
|
// Check if already loaded in window
|
|
if ((window as any).google?.maps) {
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
// Get API key from runtime config
|
|
const apiKey = getGoogleMapsApiKey();
|
|
|
|
if (!apiKey) {
|
|
reject(new Error('Google Maps API key is not configured'));
|
|
return;
|
|
}
|
|
|
|
// Create a global callback for Google Maps to call when ready
|
|
const callbackName = `gmapsCallback_${Date.now()}`;
|
|
(window as any)[callbackName] = () => {
|
|
if ((window as any).google?.maps) {
|
|
resolve();
|
|
} else {
|
|
reject(
|
|
new Error('Google Maps loaded but window.google.maps not available')
|
|
);
|
|
}
|
|
// Clean up callback
|
|
delete (window as any)[callbackName];
|
|
};
|
|
|
|
// Create script tag with callback
|
|
// The callback parameter tells Google Maps to call our function when ready
|
|
// Using async + callback ensures Google Maps initializes asynchronously
|
|
const script = document.createElement('script');
|
|
script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&callback=${callbackName}&libraries=places,marker&loading=async`;
|
|
script.async = true;
|
|
script.defer = true; // Load asynchronously without blocking parsing (per Maps best practices)
|
|
|
|
script.onerror = () => {
|
|
// Reset promise so retry is possible
|
|
mapsPromise = null;
|
|
delete (window as any)[callbackName];
|
|
reject(new Error('Failed to load Google Maps script'));
|
|
};
|
|
|
|
// Add to document
|
|
document.head.appendChild(script);
|
|
});
|
|
|
|
return mapsPromise;
|
|
}
|
|
|
|
/**
|
|
* Get Google Maps API (after loading)
|
|
*
|
|
* @returns Google Maps object
|
|
* @throws Error if Google Maps not loaded
|
|
*/
|
|
export function getGoogleMapsApi(): typeof google.maps {
|
|
if (!(window as any).google?.maps) {
|
|
throw new Error('Google Maps not loaded. Call loadGoogleMaps() first.');
|
|
}
|
|
|
|
return (window as any).google.maps;
|
|
}
|
|
|
|
/**
|
|
* Reset loader (for testing)
|
|
*/
|
|
export function resetMapsLoader(): void {
|
|
mapsPromise = null;
|
|
}
|