Ensure generating server IDs work even if server URLs are invalid

This commit is contained in:
Alejandro Celaya 2024-11-01 12:48:54 +01:00
parent 645abea72a
commit b31949b468
3 changed files with 64 additions and 3 deletions

View file

@ -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*

View file

@ -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 {

View file

@ -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' }),
]);
});
});
});