diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 743bd0263..ede0c14b3 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -472,8 +472,15 @@ export const placesApi = { if (opts?.paths !== undefined) fd.append('importPaths', String(opts.paths)) return postMultipart(`/trips/${tripId}/places/import/map`, fd) }, + // A longer timeout than the shared 8 s, like the other routes here that wait + // on somebody else's service. A directions link whose stops are only named + // has to be geocoded one at a time behind a 1.1 s throttle, so a route with + // eight stops needs about ten seconds. Giving up at eight left the server + // finishing the import and writing the places while the browser reported a + // failure, and a retry then spent the whole geocoding budget again only to + // have the dedupe skip every stop. importGoogleList: (tripId: number | string, url: string, enrich?: boolean) => - apiClient.post(`/trips/${tripId}/places/import/google-list`, { url, enrich } satisfies PlaceImportListRequest).then(r => r.data), + apiClient.post(`/trips/${tripId}/places/import/google-list`, { url, enrich } satisfies PlaceImportListRequest, { timeout: 60000 }).then(r => r.data), importNaverList: (tripId: number | string, url: string, enrich?: boolean) => apiClient.post(`/trips/${tripId}/places/import/naver-list`, { url, enrich } satisfies PlaceImportListRequest).then(r => r.data), bulkDelete: (tripId: number | string, ids: number[]) => diff --git a/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx b/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx index 6a990f938..07b35b3ae 100644 --- a/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx +++ b/client/src/components/Roadtrip/RoadtripStopPopup.test.tsx @@ -234,6 +234,38 @@ describe('RoadtripStopPopup', () => { expect(onSave).toHaveBeenCalled() }) + it('FE-ROADTRIP-STOPPOPUP-026: a scheme-less OSM website still leaves the app', () => { + // OSM `website` tags are routinely written without a scheme. As a raw href + // `www.hotel.de` is a RELATIVE url, so the click navigated the planner to + // /trips//www.hotel.de and the user lost the page they were on. + wrap( + , + ) + + expect(screen.getByRole('link', { name: 'Website' })).toHaveAttribute('href', 'https://www.hotel-adlon.de') + }) + + it('FE-ROADTRIP-STOPPOPUP-027: a website that is not a link is not rendered as one', () => { + // Anything that is not http(s) loses the link rather than being handed to + // the browser. The CSP stops a javascript: navigation on a stock deployment, + // but this is defended everywhere else in the app and belongs here too. + for (const bad of ['javascript:alert(1)', 'data:text/html,