From 6b12d1122cb12c68698162be7a1d9ee9785b4779 Mon Sep 17 00:00:00 2001 From: "f.gonzalez" Date: Wed, 4 Feb 2026 03:43:36 +0100 Subject: [PATCH 1/6] feat: Add fetchAppCheckToken prop to APIProvider --- .../__tests__/api-provider.test.tsx | 24 +++++++++++++++++++ src/components/api-provider.tsx | 18 +++++++++++++- types/google.maps.d.ts | 10 ++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/components/__tests__/api-provider.test.tsx b/src/components/__tests__/api-provider.test.tsx index dac9eaf3..9494b3ca 100644 --- a/src/components/__tests__/api-provider.test.tsx +++ b/src/components/__tests__/api-provider.test.tsx @@ -220,6 +220,30 @@ test('calls onError when loading the Google Maps JavaScript API fails', async () await waitFor(() => expect(onErrorMock).toHaveBeenCalled()); }); +test('sets fetchAppCheckToken on google.maps.Settings after API loads', async () => { + const mockFetchToken = jest + .fn() + .mockResolvedValue({token: 'test-token'}); + + // Set up the Settings mock + const settingsInstance = {fetchAppCheckToken: null as (() => Promise) | null}; + google.maps.Settings = { + getInstance: () => settingsInstance + } as unknown as typeof google.maps.Settings; + + render( + + + + ); + + await act(async () => { + triggerMapsApiLoaded(); + }); + + expect(settingsInstance.fetchAppCheckToken).toBe(mockFetchToken); +}); + describe('internalUsageAttributionIds', () => { test('provides default attribution IDs in context', () => { render( diff --git a/src/components/api-provider.tsx b/src/components/api-provider.tsx index 9570b733..da8ac543 100644 --- a/src/components/api-provider.tsx +++ b/src/components/api-provider.tsx @@ -106,6 +106,12 @@ export type APIProviderProps = PropsWithChildren<{ * A function that will be called if there was an error when loading the Google Maps JavaScript API. */ onError?: (error: unknown) => void; + /** + * A function that returns a Promise resolving to an App Check token. + * When provided, it will be set on `google.maps.Settings.getInstance().fetchAppCheckToken` + * after the Google Maps JavaScript API has been loaded. + */ + fetchAppCheckToken?: () => Promise; }>; // loading the Maps JavaScript API can only happen once in the runtime, so these @@ -198,7 +204,8 @@ function useGoogleMapsApiLoader(props: APIProviderProps) { language, authReferrerPolicy, channel, - solutionChannel + solutionChannel, + fetchAppCheckToken } = props; const [status, setStatus] = useState(loadingStatus); @@ -360,6 +367,15 @@ function useGoogleMapsApiLoader(props: APIProviderProps) { [currentSerializedParams, onLoad, onError, importLibraryCallback, libraries] ); + useEffect(() => { + if (status !== APILoadingStatus.LOADED) return; + + if (fetchAppCheckToken) { + google.maps.Settings.getInstance().fetchAppCheckToken = + fetchAppCheckToken; + } + }, [status, fetchAppCheckToken]); + return { status, loadedLibraries, diff --git a/types/google.maps.d.ts b/types/google.maps.d.ts index 10078403..b017c85a 100644 --- a/types/google.maps.d.ts +++ b/types/google.maps.d.ts @@ -800,6 +800,16 @@ declare namespace google.maps { } } + interface MapsAppCheckTokenResult { + token: string; + } + + interface Settings { + fetchAppCheckToken: + | (() => Promise) + | null; + } + /** * Maps3D Library interface for use with importLibrary('maps3d'). */ From e066a3faf9ca415994788cfb3fc755e6c7e5ba8f Mon Sep 17 00:00:00 2001 From: "f.gonzalez" Date: Wed, 4 Feb 2026 03:43:36 +0100 Subject: [PATCH 2/6] feat: Add fetchAppCheckToken prop to APIProvider - https://github.com/visgl/react-google-maps/issues/759 --- .../__tests__/api-provider.test.tsx | 24 +++++++++++++++++++ src/components/api-provider.tsx | 18 +++++++++++++- types/google.maps.d.ts | 10 ++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/components/__tests__/api-provider.test.tsx b/src/components/__tests__/api-provider.test.tsx index dac9eaf3..9494b3ca 100644 --- a/src/components/__tests__/api-provider.test.tsx +++ b/src/components/__tests__/api-provider.test.tsx @@ -220,6 +220,30 @@ test('calls onError when loading the Google Maps JavaScript API fails', async () await waitFor(() => expect(onErrorMock).toHaveBeenCalled()); }); +test('sets fetchAppCheckToken on google.maps.Settings after API loads', async () => { + const mockFetchToken = jest + .fn() + .mockResolvedValue({token: 'test-token'}); + + // Set up the Settings mock + const settingsInstance = {fetchAppCheckToken: null as (() => Promise) | null}; + google.maps.Settings = { + getInstance: () => settingsInstance + } as unknown as typeof google.maps.Settings; + + render( + + + + ); + + await act(async () => { + triggerMapsApiLoaded(); + }); + + expect(settingsInstance.fetchAppCheckToken).toBe(mockFetchToken); +}); + describe('internalUsageAttributionIds', () => { test('provides default attribution IDs in context', () => { render( diff --git a/src/components/api-provider.tsx b/src/components/api-provider.tsx index 9570b733..da8ac543 100644 --- a/src/components/api-provider.tsx +++ b/src/components/api-provider.tsx @@ -106,6 +106,12 @@ export type APIProviderProps = PropsWithChildren<{ * A function that will be called if there was an error when loading the Google Maps JavaScript API. */ onError?: (error: unknown) => void; + /** + * A function that returns a Promise resolving to an App Check token. + * When provided, it will be set on `google.maps.Settings.getInstance().fetchAppCheckToken` + * after the Google Maps JavaScript API has been loaded. + */ + fetchAppCheckToken?: () => Promise; }>; // loading the Maps JavaScript API can only happen once in the runtime, so these @@ -198,7 +204,8 @@ function useGoogleMapsApiLoader(props: APIProviderProps) { language, authReferrerPolicy, channel, - solutionChannel + solutionChannel, + fetchAppCheckToken } = props; const [status, setStatus] = useState(loadingStatus); @@ -360,6 +367,15 @@ function useGoogleMapsApiLoader(props: APIProviderProps) { [currentSerializedParams, onLoad, onError, importLibraryCallback, libraries] ); + useEffect(() => { + if (status !== APILoadingStatus.LOADED) return; + + if (fetchAppCheckToken) { + google.maps.Settings.getInstance().fetchAppCheckToken = + fetchAppCheckToken; + } + }, [status, fetchAppCheckToken]); + return { status, loadedLibraries, diff --git a/types/google.maps.d.ts b/types/google.maps.d.ts index 10078403..b017c85a 100644 --- a/types/google.maps.d.ts +++ b/types/google.maps.d.ts @@ -800,6 +800,16 @@ declare namespace google.maps { } } + interface MapsAppCheckTokenResult { + token: string; + } + + interface Settings { + fetchAppCheckToken: + | (() => Promise) + | null; + } + /** * Maps3D Library interface for use with importLibrary('maps3d'). */ From 07e0537143a545ef919574e7ccfb391e51957d40 Mon Sep 17 00:00:00 2001 From: "f.gonzalez" Date: Wed, 4 Feb 2026 16:27:26 +0100 Subject: [PATCH 3/6] style: fix prettier formatting in api-provider test --- src/components/__tests__/api-provider.test.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/__tests__/api-provider.test.tsx b/src/components/__tests__/api-provider.test.tsx index 9494b3ca..e82cbc62 100644 --- a/src/components/__tests__/api-provider.test.tsx +++ b/src/components/__tests__/api-provider.test.tsx @@ -221,12 +221,14 @@ test('calls onError when loading the Google Maps JavaScript API fails', async () }); test('sets fetchAppCheckToken on google.maps.Settings after API loads', async () => { - const mockFetchToken = jest - .fn() - .mockResolvedValue({token: 'test-token'}); + const mockFetchToken = jest.fn().mockResolvedValue({token: 'test-token'}); // Set up the Settings mock - const settingsInstance = {fetchAppCheckToken: null as (() => Promise) | null}; + const settingsInstance = { + fetchAppCheckToken: null as + | (() => Promise) + | null + }; google.maps.Settings = { getInstance: () => settingsInstance } as unknown as typeof google.maps.Settings; From 03ada800a5ade01428829465039c0f0647747145 Mon Sep 17 00:00:00 2001 From: Martin Schuhfuss Date: Fri, 6 Feb 2026 14:37:40 +0100 Subject: [PATCH 4/6] fix(api-provider): handle `fetchAppCheckToken` properly in `useEffect` - Ensure `fetchAppCheckToken` is set or cleared correctly in `google.maps.Settings`. - Clarify comments to improve code readability. --- src/components/api-provider.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/api-provider.tsx b/src/components/api-provider.tsx index da8ac543..45b69ea1 100644 --- a/src/components/api-provider.tsx +++ b/src/components/api-provider.tsx @@ -270,7 +270,7 @@ function useGoogleMapsApiLoader(props: APIProviderProps) { }; }, []); - // effect: + // effect: set and store options useEffect( () => { (async () => { @@ -367,12 +367,15 @@ function useGoogleMapsApiLoader(props: APIProviderProps) { [currentSerializedParams, onLoad, onError, importLibraryCallback, libraries] ); + // set the fetchAppCheckToken if provided useEffect(() => { if (status !== APILoadingStatus.LOADED) return; + const settings = google.maps.Settings.getInstance(); if (fetchAppCheckToken) { - google.maps.Settings.getInstance().fetchAppCheckToken = - fetchAppCheckToken; + settings.fetchAppCheckToken = fetchAppCheckToken; + } else if (settings.fetchAppCheckToken) { + settings.fetchAppCheckToken = null; } }, [status, fetchAppCheckToken]); From 9cbef163f5984a6eb5b30077307b08f405c50e73 Mon Sep 17 00:00:00 2001 From: Martin Schuhfuss Date: Fri, 6 Feb 2026 14:58:44 +0100 Subject: [PATCH 5/6] docs(api-provider): add instructions for using `fetchAppCheckToken` with Firebase App Check --- docs/api-reference/components/api-provider.md | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/api-reference/components/api-provider.md b/docs/api-reference/components/api-provider.md index ecc47c9b..fdc96372 100644 --- a/docs/api-reference/components/api-provider.md +++ b/docs/api-reference/components/api-provider.md @@ -131,6 +131,57 @@ used even when this option is enabled. Read more in the [documentation][gmp-usage-attribution]. +#### `fetchAppCheckToken`: () => Promise\ + +A function that returns a Promise resolving to a Firebase App Check token. +When provided, this function will be set on `google.maps.Settings.getInstance().fetchAppCheckToken` +after the Google Maps JavaScript API has loaded. + +[Firebase App Check][firebase-app-check] helps protect your Google Maps Platform API key by +blocking traffic from unauthorized sources, preventing malicious requests and +unauthorized API calls that could incur charges. It works by validating that requests +come from legitimate apps using attestation providers like [reCAPTCHA Enterprise][recaptcha-enterprise]. + +**Example usage with Firebase:** + +A custom wrapper component that initializes Firebase App Check and passes +the token fetcher to `APIProvider`: + +```tsx +import React, {PropsWithChildren, useCallback} from 'react'; +import {APIProvider, APIProviderProps} from '@vis.gl/react-google-maps'; +import {initializeApp} from 'firebase/app'; +import { + getToken, + initializeAppCheck, + ReCaptchaEnterpriseProvider +} from 'firebase/app-check'; + +// Firebase and App Check initialization +const app = initializeApp({/* ... */}); +const appCheck = initializeAppCheck(firebaseApp, { + provider: new ReCaptchaEnterpriseProvider(RECAPTCHA_SITE_KEY), + isTokenAutoRefreshEnabled: true +}); + +// custom wrapper for the APIProvider +export function CustomAPIProvider({children, ...props}) { + const fetchAppCheckToken = useCallback(() => getToken(appCheck, false), []); + + return ( + + {children} + + ); +} +``` + +For more information, see: + +- [Using App Check with Maps JavaScript API][gmp-app-check] +- [Firebase App Check documentation][firebase-app-check] +- [App Check codelab][gmp-app-check-codelab] + ### Events #### `onLoad`: () => void {#onLoad} @@ -179,6 +230,10 @@ The following hooks are built to work with the `APIProvider` Component: [gmp-lang]: https://developers.google.com/maps/documentation/javascript/localization [gmp-solutions-usage]: https://developers.google.com/maps/reporting-and-monitoring/reporting#solutions-usage [gmp-usage-attribution]: https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.internalUsageAttributionIds +[gmp-app-check]: https://developers.google.com/maps/documentation/javascript/maps-app-check +[gmp-app-check-codelab]: https://developers.google.com/codelabs/maps-platform/maps-platform-firebase-appcheck +[firebase-app-check]: https://firebase.google.com/docs/app-check +[recaptcha-enterprise]: https://cloud.google.com/security/products/recaptcha [api-provider-src]: https://github.com/visgl/react-google-maps/blob/main/src/components/api-provider.tsx [rgm-new-issue]: https://github.com/visgl/react-google-maps/issues/new/choose [gmp-channel-usage]: https://developers.google.com/maps/reporting-and-monitoring/reporting#usage-tracking-per-channel From f78a72fc3f76da8f3a73abc71f03b35fce75aab9 Mon Sep 17 00:00:00 2001 From: Martin Schuhfuss Date: Fri, 6 Feb 2026 15:24:25 +0100 Subject: [PATCH 6/6] test(api-provider): mock `google.maps.Settings` in all tests --- src/components/__tests__/api-provider.test.tsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/__tests__/api-provider.test.tsx b/src/components/__tests__/api-provider.test.tsx index e82cbc62..abdcba5d 100644 --- a/src/components/__tests__/api-provider.test.tsx +++ b/src/components/__tests__/api-provider.test.tsx @@ -25,6 +25,8 @@ let importLibraryPromise: Promise; let resolveImportLibrary: (value: ImportLibraryResult) => void; let rejectImportLibrary: (reason?: unknown) => void; +let settingsInstance: google.maps.Settings; + const resetImportLibraryPromise = () => { ({ promise: importLibraryPromise, @@ -71,6 +73,12 @@ beforeEach(() => { window.google.maps.importLibrary = undefined; resetAPIProviderState(); resetImportLibraryPromise(); + + // Mock google.maps.Settings (missing in @googlemaps/jest-mocks) + settingsInstance = {fetchAppCheckToken: null} as google.maps.Settings; + google.maps.Settings = { + getInstance: () => settingsInstance + } as unknown as typeof google.maps.Settings; }); afterEach(() => { @@ -223,16 +231,6 @@ test('calls onError when loading the Google Maps JavaScript API fails', async () test('sets fetchAppCheckToken on google.maps.Settings after API loads', async () => { const mockFetchToken = jest.fn().mockResolvedValue({token: 'test-token'}); - // Set up the Settings mock - const settingsInstance = { - fetchAppCheckToken: null as - | (() => Promise) - | null - }; - google.maps.Settings = { - getInstance: () => settingsInstance - } as unknown as typeof google.maps.Settings; - render(