From fa8300b5df42f6afd7925cc8c0e103e2bf01c977 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Tue, 3 Feb 2026 23:32:01 -0800 Subject: [PATCH] fix(Maps): ensure asset urls resolve correctly --- admin/app/controllers/maps_controller.ts | 4 ++-- admin/app/services/map_service.ts | 20 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/admin/app/controllers/maps_controller.ts b/admin/app/controllers/maps_controller.ts index 451271f..909cca7 100644 --- a/admin/app/controllers/maps_controller.ts +++ b/admin/app/controllers/maps_controller.ts @@ -69,7 +69,7 @@ export default class MapsController { return await this.mapService.listRegions() } - async styles({ response }: HttpContext) { + async styles({ request, response }: HttpContext) { // Automatically ensure base assets are present before generating styles const baseAssetsExist = await this.mapService.ensureBaseAssets() if (!baseAssetsExist) { @@ -79,7 +79,7 @@ export default class MapsController { }) } - const styles = await this.mapService.generateStylesJSON() + const styles = await this.mapService.generateStylesJSON(request.host()) return response.json(styles) } diff --git a/admin/app/services/map_service.ts b/admin/app/services/map_service.ts index 54e9420..2a9eea7 100644 --- a/admin/app/services/map_service.ts +++ b/admin/app/services/map_service.ts @@ -234,7 +234,7 @@ export class MapService implements IMapService { } } - async generateStylesJSON() { + async generateStylesJSON(host: string | null = null): Promise { if (!(await this.checkBaseAssetsExist())) { throw new Error('Base map assets are missing from storage/maps') } @@ -248,9 +248,15 @@ export class MapService implements IMapService { const rawStyles = JSON.parse(baseStyle.toString()) as BaseStylesFile const regions = (await this.listRegions()).files - const sources = this.generateSourcesArray(regions) - const baseUrl = this.getPublicFileBaseUrl(this.basemapsAssetsDir) + /** If we have the host, use it to build public URLs, otherwise we'll fallback to defaults + * This is mainly useful because we need to know what host the user is accessing from in order to + * properly generate URLs in the styles file + * e.g. user is accessing from "example.com", but we would by default generate "localhost:8080/..." so maps would + * fail to load. + */ + const sources = this.generateSourcesArray(host, regions) + const baseUrl = this.getPublicFileBaseUrl(host, this.basemapsAssetsDir) const styles = await this.generateStylesFile( rawStyles, @@ -341,9 +347,9 @@ export class MapService implements IMapService { return await listDirectoryContentsRecursive(this.baseDirPath) } - private generateSourcesArray(regions: FileEntry[]): BaseStylesFile['sources'][] { + private generateSourcesArray(host: string | null, regions: FileEntry[]): BaseStylesFile['sources'][] { const sources: BaseStylesFile['sources'][] = [] - const baseUrl = this.getPublicFileBaseUrl('pmtiles') + const baseUrl = this.getPublicFileBaseUrl(host, 'pmtiles') for (const region of regions) { if (region.type === 'file' && region.name.endsWith('.pmtiles')) { @@ -414,7 +420,7 @@ export class MapService implements IMapService { /* * Gets the appropriate public URL for a map asset depending on environment */ - private getPublicFileBaseUrl(childPath: string): string { + private getPublicFileBaseUrl(specifiedHost: string | null, childPath: string): string { function getHost() { try { const localUrlRaw = env.get('URL') @@ -427,7 +433,7 @@ export class MapService implements IMapService { } } - const host = getHost() + const host = specifiedHost || getHost() const withProtocol = host.startsWith('http') ? host : `http://${host}` const baseUrlPath = process.env.NODE_ENV === 'production' ? childPath : urlJoin(this.mapStoragePath, childPath)