From 3465a8baff8c119f1cf441f9e82d6eda3cbc6bf0 Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sat, 15 Aug 2026 16:42:30 -0400 Subject: [PATCH 1/8] - converted everything to the new API --- src/cli.ts | 1 + src/geosearch.ts | 6 +++--- src/menus.ts | 4 ++++ src/routing.ts | 14 ++++++++++---- src/settings.ts | 45 ++++++++++++++++++++++++++++++++++++++++++- src/settingsTab.ts | 47 ++++++++++++++++++++++++--------------------- src/urlConvertor.ts | 5 ++++- 7 files changed, 91 insertions(+), 31 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index 75f497d..54c89a0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -170,6 +170,7 @@ export function registerCliHandlers( 'graphhopper', { profile: params.profile }, settings, + app, ); return [ `Profile: ${result.profileUsed}`, diff --git a/src/geosearch.ts b/src/geosearch.ts index 7222b3e..72ad206 100644 --- a/src/geosearch.ts +++ b/src/geosearch.ts @@ -46,7 +46,7 @@ export class GeoSearcher { }); } else if (settings.searchProvider == 'google') { this.searchProvider = new geosearch.GoogleProvider({ - apiKey: settings.geocodingApiKey, + apiKey: settings.geocodingApiKeySecret, }); } } @@ -76,7 +76,7 @@ export class GeoSearcher { if ( this.settings.searchProvider == 'google' && this.settings.useGooglePlacesNew2025 && - this.settings.geocodingApiKey + this.settings.geocodingApiKeySecret ) { try { const placesResults = await googlePlacesSearch( @@ -132,7 +132,7 @@ export async function googlePlacesSearch( ): Promise { if (settings.searchProvider != 'google' || !settings.useGooglePlacesNew2025) return []; - const googleApiKey = settings.geocodingApiKey; + const googleApiKey = settings.geocodingApiKeySecret; // Request body for the new Places API const requestBody = { diff --git a/src/menus.ts b/src/menus.ts index 96edc3e..8686070 100644 --- a/src/menus.ts +++ b/src/menus.ts @@ -507,6 +507,7 @@ export function populateRouting( geolocation, submenu, settings, + app, ); }); } @@ -538,6 +539,7 @@ export function populateRouting( geolocation, menu, settings, + app, ); menu.showAtMouseEvent(originalEvent); } @@ -553,6 +555,7 @@ export function populateRouteToPoint( geolocation: leaflet.LatLng, menu: Menu, settings: settings.PluginSettings, + app: App, ) { // The first priority is to choose the user-selected routing source. // If there isn't any, we try the real-time (GPS) location. @@ -586,6 +589,7 @@ export function populateRouteToPoint( { profile: cleanedProfile }, mapContainer, settings, + app, ); }); }); diff --git a/src/routing.ts b/src/routing.ts index 1081933..10a85b9 100644 --- a/src/routing.ts +++ b/src/routing.ts @@ -2,7 +2,7 @@ import { MapContainer } from 'src/mapContainer'; import MapViewPlugin from 'src/main'; import { type PluginSettings } from 'src/settings'; import * as leaflet from 'leaflet'; -import { request, Notice } from 'obsidian'; +import { request, Notice, App } from 'obsidian'; import { type GeoJSON } from 'geojson'; type RoutingProvider = 'graphhopper'; @@ -26,8 +26,12 @@ export async function calcRoute( provider: RoutingProvider, params: RoutingParams, settings: PluginSettings, + app: App, ): Promise { - if (!settings.routingGraphHopperApiKey) { + const apiKey = app.secretStorage.getSecret( + settings.routingGraphHopperApiKeySecret, + ); + if (!apiKey) { throw new Error( 'No GraphHopper API key configured in Map View settings.', ); @@ -44,7 +48,7 @@ export async function calcRoute( ...settings.routingGraphHopperExtra, }; const resultContent: any = await request({ - url: `https://graphhopper.com/api/1/route?key=${settings.routingGraphHopperApiKey}`, + url: `https://graphhopper.com/api/1/route?key=${settings.routingGraphHopperApiKeySecret}`, method: 'POST', body: JSON.stringify(requestBody), headers: { @@ -78,8 +82,9 @@ export async function doRouting( params: RoutingParams, map: MapContainer, settings: PluginSettings, + app: App, ) { - if (!settings.routingGraphHopperApiKey) { + if (!settings.routingGraphHopperApiKeySecret) { new Notice( 'You must first provide a GraphHopper API key in the settings.', ); @@ -92,6 +97,7 @@ export async function doRouting( provider, params, settings, + app, ); map.addFloatingRoute(routingResult); } catch (e) { diff --git a/src/settings.ts b/src/settings.ts index dc99599..36c9ad6 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,5 +1,5 @@ import { LatLng, type PathOptions } from 'leaflet'; -import { type SplitDirection, Notice } from 'obsidian'; +import { type SplitDirection, App, Notice } from 'obsidian'; import { type MapState, type LegacyMapState, mergeStates } from 'src/mapState'; import type MapViewPlugin from 'src/main'; import * as consts from 'src/consts'; @@ -52,7 +52,11 @@ export type PluginSettings = { searchProvider: 'osm' | 'google'; osmUser: string; searchDelayMs: number; + /** + * @deprecated - Use "geocodingApiKeySecret" + */ geocodingApiKey: string; + geocodingApiKeySecret: string; useGooglePlacesNew2025: boolean; googlePlacesDataFields: string; saveHistory: boolean; @@ -72,7 +76,11 @@ export type PluginSettings = { zoomOnGeolinkPreview: number; handleGeolinkContextMenu: boolean; routingUrl: string; + /** + * @deprecated - use "routingGraphHopperApiKeySecret" + */ routingGraphHopperApiKey: string; + routingGraphHopperApiKeySecret: string; routingGraphHopperProfiles: string; routingGraphHopperExtra: any; cacheAllTiles: boolean; @@ -92,6 +100,8 @@ export type DepracatedFields = { defaultTags?: string[]; snippetLines?: number; useGooglePlaces?: boolean; + routingGraphHopperApiKey: string; + geocodingApiKey: string; }; export type MapLightDark = 'auto' | 'light' | 'dark'; @@ -285,6 +295,7 @@ export const DEFAULT_SETTINGS: PluginSettings = { osmUser: '', searchDelayMs: 250, geocodingApiKey: '', + geocodingApiKeySecret: '', useGooglePlacesNew2025: false, googlePlacesDataFields: '', mapSources: [ @@ -317,6 +328,7 @@ export const DEFAULT_SETTINGS: PluginSettings = { routingUrl: 'https://www.google.com/maps/dir/?api=1&origin={x0},{y0}&destination={x1},{y1}', routingGraphHopperApiKey: '', + routingGraphHopperApiKeySecret: '', routingGraphHopperProfiles: 'foot, bike, car', routingGraphHopperExtra: {}, cacheAllTiles: true, @@ -484,6 +496,30 @@ export function convertLegacyGooglePlaces(settings: PluginSettings): boolean { return changed; } +export function convertLegacyAPIKeysToSecretStorage( + settings: PluginSettings & DepracatedFields, + app: App, +): boolean { + let changed = false; + if (settings.geocodingApiKey) { + app.secretStorage.setSecret( + settings.geocodingApiKeySecret, + settings.geocodingApiKey, + ); + delete settings.geocodingApiKey; + changed = true; + } + if (settings.routingGraphHopperApiKey) { + app.secretStorage.setSecret( + settings.routingGraphHopperApiKeySecret, + settings.routingGraphHopperApiKey, + ); + delete settings.routingGraphHopperApiKey; + changed = true; + } + return changed; +} + export function convertMarkerIconRulesToDisplayRules( settings: PluginSettings & DepracatedFields, ) { @@ -606,6 +642,13 @@ export async function convertLegacySettings( ); } + if (convertLegacyAPIKeysToSecretStorage(settings, this.app)) { + changed = true; + new Notice( + 'Map View: Legacy API keys for Geocoding and/or converted to the new Secret Storage API.', + ); + } + completePartialSavedStates(settings); if (changed) plugin.saveSettings(); diff --git a/src/settingsTab.ts b/src/settingsTab.ts index 767825c..75b0842 100644 --- a/src/settingsTab.ts +++ b/src/settingsTab.ts @@ -5,6 +5,8 @@ import { TextAreaComponent, Setting, DropdownComponent, + SecretComponent, + Component, } from 'obsidian'; import MapViewPlugin from 'src/main'; @@ -23,6 +25,8 @@ import { DEFAULT_MAX_TILE_ZOOM, MAX_ZOOM } from 'src/consts'; import { openManagerDialog } from 'src/offlineTiles.svelte'; import { SvelteModal } from 'src/svelte'; import DisplayRules from './components/DisplayRules.svelte'; +import { value } from 'happy-dom/lib/PropertySymbol'; +import { V } from 'vitest/dist/chunks/reporters.d.DVUYHHhe'; export class SettingsTab extends PluginSettingTab { plugin: MapViewPlugin; @@ -142,21 +146,15 @@ export class SettingsTab extends PluginSettingTab { .setDesc( 'If using Google as the geocoding search provider, paste the API key here. See the plugin documentation for more details. Changes are applied after restart.', ) - .addText((component) => { - component - .setValue(this.plugin.settings.geocodingApiKey) - .onChange(async (value) => { - this.plugin.settings.geocodingApiKey = value; - await this.plugin.saveSettings(); - component.inputEl.style.borderColor = value - ? '' - : 'red'; - }); - component.inputEl.style.borderColor = this.plugin.settings - .geocodingApiKey - ? '' - : 'red'; - }); + .addComponent((component) => + new SecretComponent(this.app, component) + .setValue(this.plugin.settings.geocodingApiKeySecret) + .onChange((value) => { + this.plugin.settings.geocodingApiKeySecret = value; + this.plugin.saveSettings(); + component.style.borderColor = value ? '' : 'red'; + }), + ); let googlePlacesControl = new Setting(containerEl) .setName('Use Google Places for searches') .setDesc( @@ -818,19 +816,24 @@ export class SettingsTab extends PluginSettingTab { this.plugin.saveSettings(); }); }); + new Setting(containerEl) .setName('GraphHopper API key') .setDesc( 'You may obtain a free or a paid key from GraphHopper to enable native routing in Map View.', ) - .addText((component) => { - component - .setValue(this.plugin.settings.routingGraphHopperApiKey) - .onChange(async (value: string) => { - this.plugin.settings.routingGraphHopperApiKey = value; + .addComponent((component) => + new SecretComponent(this.app, component) + .setValue( + this.plugin.settings.routingGraphHopperApiKeySecret, + ) + .onChange((value) => { + this.plugin.settings.routingGraphHopperApiKeySecret = + value; this.plugin.saveSettings(); - }); - }); + }), + ); + new Setting(containerEl) .setName('GraphHopper profiles') .setDesc( diff --git a/src/urlConvertor.ts b/src/urlConvertor.ts index 365f49b..7696dce 100644 --- a/src/urlConvertor.ts +++ b/src/urlConvertor.ts @@ -126,6 +126,7 @@ export class UrlConvertor { async getGeolocationFromGoogleLink( url: string, settings: PluginSettings, + app: App, ): Promise { const content = await request({ url: url }); if (this.settings.debug) console.log('Google link: searching url', url); @@ -136,7 +137,9 @@ export class UrlConvertor { const placeName = placeNameMatch[1]; if (this.settings.debug) console.log('Google link: found place name = ', placeName); - const googleApiKey = settings.geocodingApiKey; + const googleApiKey = app.secretStorage.getSecret( + settings.geocodingApiKeySecret, + ); const params = { query: placeName, key: googleApiKey, From 213a6d65adcaee4ae0a4ebc34b7b71803329a1cd Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sun, 16 Aug 2026 13:07:23 -0400 Subject: [PATCH 2/8] - fixed stray import found during review --- src/settingsTab.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/settingsTab.ts b/src/settingsTab.ts index 75b0842..50c405e 100644 --- a/src/settingsTab.ts +++ b/src/settingsTab.ts @@ -25,8 +25,6 @@ import { DEFAULT_MAX_TILE_ZOOM, MAX_ZOOM } from 'src/consts'; import { openManagerDialog } from 'src/offlineTiles.svelte'; import { SvelteModal } from 'src/svelte'; import DisplayRules from './components/DisplayRules.svelte'; -import { value } from 'happy-dom/lib/PropertySymbol'; -import { V } from 'vitest/dist/chunks/reporters.d.DVUYHHhe'; export class SettingsTab extends PluginSettingTab { plugin: MapViewPlugin; From 81a536bcc6d877c8530f8bf5e5b1e3595bcae843 Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sun, 16 Aug 2026 13:30:35 -0400 Subject: [PATCH 3/8] - settings fixup for redundant key --- src/settings.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/settings.ts b/src/settings.ts index 36c9ad6..36a4ea2 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -502,18 +502,16 @@ export function convertLegacyAPIKeysToSecretStorage( ): boolean { let changed = false; if (settings.geocodingApiKey) { - app.secretStorage.setSecret( - settings.geocodingApiKeySecret, - settings.geocodingApiKey, - ); + const key = 'obsidian-map-view-geocoding-apikey'; + app.secretStorage.setSecret(key, settings.geocodingApiKey); + settings.geocodingApiKeySecret = key; delete settings.geocodingApiKey; changed = true; } if (settings.routingGraphHopperApiKey) { - app.secretStorage.setSecret( - settings.routingGraphHopperApiKeySecret, - settings.routingGraphHopperApiKey, - ); + const key = 'obsidian-map-view-routing-graphhopper-apikey'; + app.secretStorage.setSecret(key, settings.routingGraphHopperApiKey); + settings.geocodingApiKeySecret = key; delete settings.routingGraphHopperApiKey; changed = true; } @@ -642,7 +640,7 @@ export async function convertLegacySettings( ); } - if (convertLegacyAPIKeysToSecretStorage(settings, this.app)) { + if (convertLegacyAPIKeysToSecretStorage(settings, plugin.app)) { changed = true; new Notice( 'Map View: Legacy API keys for Geocoding and/or converted to the new Secret Storage API.', From be639270eb2aaafeec8578704f476a66e2393a78 Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sun, 16 Aug 2026 13:33:23 -0400 Subject: [PATCH 4/8] - updated copy --- src/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings.ts b/src/settings.ts index 36a4ea2..e2d3f49 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -643,7 +643,7 @@ export async function convertLegacySettings( if (convertLegacyAPIKeysToSecretStorage(settings, plugin.app)) { changed = true; new Notice( - 'Map View: Legacy API keys for Geocoding and/or converted to the new Secret Storage API.', + 'Map View: Legacy API keys for Geocoding and/or converted to the new Secret Storage API. You may need to re-link the secret in settings afterwards.', ); } From ec356f05c02285ab7bede62617bc3855cef5c3bb Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sun, 16 Aug 2026 13:39:06 -0400 Subject: [PATCH 5/8] - fixed place where the API key secret name was passed --- src/routing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routing.ts b/src/routing.ts index 10a85b9..ed03e60 100644 --- a/src/routing.ts +++ b/src/routing.ts @@ -48,7 +48,7 @@ export async function calcRoute( ...settings.routingGraphHopperExtra, }; const resultContent: any = await request({ - url: `https://graphhopper.com/api/1/route?key=${settings.routingGraphHopperApiKeySecret}`, + url: `https://graphhopper.com/api/1/route?key=${apiKey}`, method: 'POST', body: JSON.stringify(requestBody), headers: { From a6e20542535f7eb80a22c92fa97e13bb4360e892 Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sun, 16 Aug 2026 13:42:29 -0400 Subject: [PATCH 6/8] - fixed some more bad spots --- src/geosearch.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/geosearch.ts b/src/geosearch.ts index 72ad206..6567a15 100644 --- a/src/geosearch.ts +++ b/src/geosearch.ts @@ -45,8 +45,11 @@ export class GeoSearcher { }, }); } else if (settings.searchProvider == 'google') { + //TODO: this can be improved so that it auto-updates when `apiKey` is changed this.searchProvider = new geosearch.GoogleProvider({ - apiKey: settings.geocodingApiKeySecret, + apiKey: app.secretStorage.getSecret( + settings.geocodingApiKeySecret, + ), }); } } @@ -129,10 +132,13 @@ export async function googlePlacesSearch( query: string, settings: PluginSettings, centerOfSearch: leaflet.LatLng | null, + app: App, ): Promise { if (settings.searchProvider != 'google' || !settings.useGooglePlacesNew2025) return []; - const googleApiKey = settings.geocodingApiKeySecret; + const googleApiKey = app.secretStorage.getSecret( + settings.geocodingApiKeySecret, + ); // Request body for the new Places API const requestBody = { From 2c965c8f1c9077e40a5b54df11005a038651fb38 Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sun, 16 Aug 2026 13:49:13 -0400 Subject: [PATCH 7/8] - updated how API keys populated `googlePlacesSearch` --- src/geosearch.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/geosearch.ts b/src/geosearch.ts index 6567a15..1d1d23f 100644 --- a/src/geosearch.ts +++ b/src/geosearch.ts @@ -27,10 +27,13 @@ export class GeoSearcher { | geosearch.GoogleProvider = null; private settings: PluginSettings; private urlConvertor: UrlConvertor; + private app: App; constructor(app: App, settings: PluginSettings) { this.settings = settings; this.urlConvertor = new UrlConvertor(app, settings); + this.app = app; + if (settings.searchProvider == 'osm') { if (!settings.osmUser) { new Notice( @@ -86,6 +89,9 @@ export class GeoSearcher { query, this.settings, searchArea?.getCenter(), + this.app.secretStorage.getSecret( + this.settings.geocodingApiKeySecret, + ), ); for (const result of placesResults) { results.push({ @@ -132,13 +138,10 @@ export async function googlePlacesSearch( query: string, settings: PluginSettings, centerOfSearch: leaflet.LatLng | null, - app: App, + googleApiKey: string, ): Promise { if (settings.searchProvider != 'google' || !settings.useGooglePlacesNew2025) return []; - const googleApiKey = app.secretStorage.getSecret( - settings.geocodingApiKeySecret, - ); // Request body for the new Places API const requestBody = { From 685d16345c600d79d25a9c3828272bc1f4b6530a Mon Sep 17 00:00:00 2001 From: Mark Beznos Date: Sun, 16 Aug 2026 14:01:42 -0400 Subject: [PATCH 8/8] - more fixes for missing args --- src/mapContainer.ts | 1 + src/viewControls.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/mapContainer.ts b/src/mapContainer.ts index b38d732..a3b65ea 100644 --- a/src/mapContainer.ts +++ b/src/mapContainer.ts @@ -1832,6 +1832,7 @@ export class MapContainer { marker.location, menu, this.settings, + this.app, ); menu.showAtMouseEvent(ev.originalEvent); } diff --git a/src/viewControls.ts b/src/viewControls.ts index 72095f7..96a6c08 100644 --- a/src/viewControls.ts +++ b/src/viewControls.ts @@ -604,6 +604,7 @@ export class RoutingControl extends leaflet.Control { marker.location, menu, this.settings, + this.app, ); menu.showAtMouseEvent(ev); }