From b31949b468d02525aa3fd70ff1a9a75a2c9575b3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 1 Nov 2024 12:48:54 +0100 Subject: [PATCH] Ensure generating server IDs work even if server URLs are invalid --- CHANGELOG.md | 21 +++++++++++++++++++++ src/servers/helpers/index.ts | 20 +++++++++++++++++--- test/servers/helpers/index.test.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e546260..b94d351b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] +### Added +* [#1360](https://github.com/shlinkio/shlink-web-client/issues/1360) Added ability for server IDs to be generated based on the server name and URL, instead of generating a random UUID. + + This can improve sharing a predefined set of servers cia servers.json, env vars, or simply export and import your servers in some other device, and then be able to share server URLs which continue working. + + All existing servers will keep their generated IDs in existing devices for backwards compatibility, but newly created servers will use the new approach. + +### Changed +* *Nothing* + +### Deprecated +* *Nothing* + +### Removed +* *Nothing* + +### Fixed +* *Nothing* + + ## [4.2.2] - 2024-10-19 ### Added * *Nothing* diff --git a/src/servers/helpers/index.ts b/src/servers/helpers/index.ts index 589b93c7..6a5dca4a 100644 --- a/src/servers/helpers/index.ts +++ b/src/servers/helpers/index.ts @@ -6,9 +6,23 @@ import type { ServerData, ServersMap, ServerWithId } from '../data'; * in lowercase and replacing invalid URL characters with hyphens. */ function idForServer(server: ServerData): string { - // TODO Handle invalid URLs. If not valid url, use the value as is - const url = new URL(server.url); - return `${server.name} ${url.host}`.toLowerCase().replace(/[^a-zA-Z0-9-_.~]/g, '-'); + let urlSegment = server.url; + try { + const { host, pathname } = new URL(urlSegment); + urlSegment = host; + + // Remove leading slash from pathname + const normalizedPathname = pathname.substring(1); + + // Include pathname in the ID, if not empty + if (normalizedPathname.length > 0) { + urlSegment = `${urlSegment} ${normalizedPathname}`; + } + } catch { + // If the server URL is not valid, use the value as is + } + + return `${server.name} ${urlSegment}`.toLowerCase().replace(/[^a-zA-Z0-9-_.~]/g, '-'); } export function serversListToMap(servers: ServerWithId[]): ServersMap { diff --git a/test/servers/helpers/index.test.ts b/test/servers/helpers/index.test.ts index cd1bd5cb..370f6c6c 100644 --- a/test/servers/helpers/index.test.ts +++ b/test/servers/helpers/index.test.ts @@ -39,5 +39,31 @@ describe('index', () => { expect.objectContaining({ id: 'baz-s.test' }), ]); }); + + it('includes server paths when not empty', () => { + const result = ensureUniqueIds({}, [ + fromPartial({ name: 'Foo', url: 'https://example.com' }), + fromPartial({ name: 'Bar', url: 'https://s.test/some/path' }), + fromPartial({ name: 'Baz', url: 'https://s.test/some/other-path-here/123' }), + ]); + + expect(result).toEqual([ + expect.objectContaining({ id: 'foo-example.com' }), + expect.objectContaining({ id: 'bar-s.test-some-path' }), + expect.objectContaining({ id: 'baz-s.test-some-other-path-here-123' }), + ]); + }); + + it('uses server URL verbatim when it is not a valid URL', () => { + const result = ensureUniqueIds({}, [ + fromPartial({ name: 'Foo', url: 'invalid' }), + fromPartial({ name: 'Bar', url: 'this is not a URL' }), + ]); + + expect(result).toEqual([ + expect.objectContaining({ id: 'foo-invalid' }), + expect.objectContaining({ id: 'bar-this-is-not-a-url' }), + ]); + }); }); });