@sisense/sdk-common 2.4.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/utils/index.d.ts +1 -1
- package/dist/cjs/utils/index.js +4 -3
- package/dist/cjs/utils/url.d.ts +19 -0
- package/dist/cjs/utils/url.js +84 -0
- package/dist/cjs/utils/url.test.js +96 -0
- package/dist/tsconfig.prod.cjs.tsbuildinfo +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/url.d.ts +19 -0
- package/dist/utils/url.js +79 -0
- package/dist/utils/url.test.js +94 -0
- package/package.json +1 -1
- package/src/utils/index.ts +1 -1
- package/src/utils/url.test.ts +148 -0
- package/src/utils/url.ts +91 -0
- package/tsconfig.prod.tsbuildinfo +1 -1
- package/dist/cjs/utils/normalizeUrl.d.ts +0 -1
- package/dist/cjs/utils/normalizeUrl.js +0 -12
- package/dist/cjs/utils/normalizeUrl.test.js +0 -12
- package/dist/utils/normalizeUrl.d.ts +0 -1
- package/dist/utils/normalizeUrl.js +0 -8
- package/dist/utils/normalizeUrl.test.js +0 -10
- package/src/utils/normalizeUrl.test.ts +0 -13
- package/src/utils/normalizeUrl.ts +0 -10
- /package/dist/cjs/utils/{normalizeUrl.test.d.ts → url.test.d.ts} +0 -0
- /package/dist/utils/{normalizeUrl.test.d.ts → url.test.d.ts} +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes a URL string by ensuring it has a trailing slash and optionally includes search parameters.
|
|
3
|
+
* @param url - The URL string to normalize
|
|
4
|
+
* @param includeSearchString - Whether to include search parameters in the result
|
|
5
|
+
* @returns The normalized URL string
|
|
6
|
+
* @throws Error if the URL is invalid, empty, or not a string
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeUrl(url, includeSearchString) {
|
|
9
|
+
if (typeof url !== 'string') {
|
|
10
|
+
throw new Error('URL must be a string');
|
|
11
|
+
}
|
|
12
|
+
if (!url.trim()) {
|
|
13
|
+
throw new Error('URL cannot be empty');
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const urlObject = new URL(url);
|
|
17
|
+
return (urlObject.origin +
|
|
18
|
+
(urlObject.pathname.endsWith('/') ? urlObject.pathname : urlObject.pathname + '/') +
|
|
19
|
+
(includeSearchString && urlObject.search ? urlObject.search : ''));
|
|
20
|
+
}
|
|
21
|
+
catch (_a) {
|
|
22
|
+
throw new Error(`Connection string ${url} is not a valid URL`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Merges two URLs, combining their paths and query parameters.
|
|
27
|
+
* If loginUrlStr is an absolute URL, it takes precedence over baseUrlStr.
|
|
28
|
+
* If loginUrlStr is relative, it's merged with baseUrlStr.
|
|
29
|
+
*
|
|
30
|
+
* @param baseUrlStr - The base URL to merge with
|
|
31
|
+
* @param loginUrlStr - The login URL (can be absolute or relative)
|
|
32
|
+
* @returns The merged URL string
|
|
33
|
+
* @throws Error if baseUrlStr is not a valid URL
|
|
34
|
+
*/
|
|
35
|
+
export function mergeUrlsWithParams(baseUrlStr, loginUrlStr) {
|
|
36
|
+
if (!baseUrlStr) {
|
|
37
|
+
throw new Error('Base URL is required');
|
|
38
|
+
}
|
|
39
|
+
// Parse base URL first to validate it
|
|
40
|
+
let baseUrl;
|
|
41
|
+
try {
|
|
42
|
+
baseUrl = new URL(baseUrlStr);
|
|
43
|
+
}
|
|
44
|
+
catch (_a) {
|
|
45
|
+
throw new Error('Base URL is not valid');
|
|
46
|
+
}
|
|
47
|
+
// If no login URL provided or it's just a slash, return base URL
|
|
48
|
+
if (!loginUrlStr || loginUrlStr === '/') {
|
|
49
|
+
return baseUrl.toString();
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
// Check if loginUrlStr is an absolute URL
|
|
53
|
+
const loginUrl = new URL(loginUrlStr);
|
|
54
|
+
return loginUrl.toString();
|
|
55
|
+
}
|
|
56
|
+
catch (_b) {
|
|
57
|
+
// loginUrlStr is relative, proceed with merging
|
|
58
|
+
}
|
|
59
|
+
// Create a URL by joining base origin with login path
|
|
60
|
+
const resultUrl = new URL(baseUrl.toString());
|
|
61
|
+
// Split login URL into path and query parts
|
|
62
|
+
const [loginPath = '', loginQuery = ''] = loginUrlStr.split(/\?(.+)/);
|
|
63
|
+
// Handle the path merging
|
|
64
|
+
const basePath = baseUrl.pathname.replace(/\/$/, ''); // Remove trailing slash
|
|
65
|
+
const cleanLoginPath = loginPath
|
|
66
|
+
.replace(/^\/+/, '') // Remove leading slashes
|
|
67
|
+
.replace(/\/+$/, '') // Remove trailing slashes
|
|
68
|
+
.replace(/\/+/g, '/'); // Replace multiple slashes with single slash
|
|
69
|
+
resultUrl.pathname = cleanLoginPath ? `${basePath}/${cleanLoginPath}` : basePath;
|
|
70
|
+
// Parse and merge query parameters from both URLs
|
|
71
|
+
if (loginQuery) {
|
|
72
|
+
const loginParams = new URLSearchParams(loginQuery);
|
|
73
|
+
for (const [key, value] of loginParams.entries()) {
|
|
74
|
+
resultUrl.searchParams.set(key, value);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Normalize the final URL by removing trailing slash
|
|
78
|
+
return normalizeUrl(resultUrl.toString(), true);
|
|
79
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mergeUrlsWithParams, normalizeUrl } from './url.js';
|
|
3
|
+
describe('normalizeUrl', () => {
|
|
4
|
+
it("should add a trailing slash if url doesn't have it", () => {
|
|
5
|
+
expect(normalizeUrl('https://example.com')).toBe('https://example.com/');
|
|
6
|
+
});
|
|
7
|
+
it('should not add an extra trailing slash if url does have it', () => {
|
|
8
|
+
expect(normalizeUrl('https://example.com/')).toBe('https://example.com/');
|
|
9
|
+
});
|
|
10
|
+
it('should include search string when specified', () => {
|
|
11
|
+
expect(normalizeUrl('https://example.com?param=value', true)).toBe('https://example.com/?param=value');
|
|
12
|
+
});
|
|
13
|
+
it('should throw error for invalid URL', () => {
|
|
14
|
+
expect(() => normalizeUrl('not-a-url')).toThrow();
|
|
15
|
+
});
|
|
16
|
+
it('should throw error for empty string', () => {
|
|
17
|
+
expect(() => normalizeUrl('')).toThrow('URL cannot be empty');
|
|
18
|
+
expect(() => normalizeUrl(' ')).toThrow('URL cannot be empty');
|
|
19
|
+
});
|
|
20
|
+
it('should throw error for non-string input', () => {
|
|
21
|
+
// @ts-expect-error Testing runtime type check
|
|
22
|
+
expect(() => normalizeUrl(null)).toThrow('URL must be a string');
|
|
23
|
+
// @ts-expect-error Testing runtime type check
|
|
24
|
+
expect(() => normalizeUrl(undefined)).toThrow('URL must be a string');
|
|
25
|
+
// @ts-expect-error Testing runtime type check
|
|
26
|
+
expect(() => normalizeUrl(123)).toThrow('URL must be a string');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe('mergeUrlsWithParams', () => {
|
|
30
|
+
describe('input validation', () => {
|
|
31
|
+
it('should throw error when base URL is not provided', () => {
|
|
32
|
+
expect(() => mergeUrlsWithParams('')).toThrow('Base URL is required');
|
|
33
|
+
});
|
|
34
|
+
it('should throw error when base URL is invalid', () => {
|
|
35
|
+
expect(() => mergeUrlsWithParams('not-a-url')).toThrow('Base URL is not valid');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('base URL handling', () => {
|
|
39
|
+
it('should return base URL when login URL is not provided', () => {
|
|
40
|
+
expect(mergeUrlsWithParams('https://example.com')).toBe('https://example.com/');
|
|
41
|
+
});
|
|
42
|
+
it('should return base URL when login URL is just a slash', () => {
|
|
43
|
+
expect(mergeUrlsWithParams('https://example.com', '/')).toBe('https://example.com/');
|
|
44
|
+
});
|
|
45
|
+
it('should preserve base URL port if specified', () => {
|
|
46
|
+
expect(mergeUrlsWithParams('https://example.com:8080', 'login')).toBe('https://example.com:8080/login/');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe('absolute URL handling', () => {
|
|
50
|
+
it('should use absolute login URL when provided', () => {
|
|
51
|
+
expect(mergeUrlsWithParams('https://example.com', 'https://login.example.com/')).toBe('https://login.example.com/');
|
|
52
|
+
});
|
|
53
|
+
it('should preserve query params in absolute login URL', () => {
|
|
54
|
+
expect(mergeUrlsWithParams('https://example.com', 'https://login.example.com?token=123')).toBe('https://login.example.com/?token=123');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('relative path handling', () => {
|
|
58
|
+
it('should merge relative login path with base URL', () => {
|
|
59
|
+
expect(mergeUrlsWithParams('https://example.com', 'login')).toBe('https://example.com/login/');
|
|
60
|
+
});
|
|
61
|
+
it('should handle relative login path with leading slash', () => {
|
|
62
|
+
expect(mergeUrlsWithParams('https://example.com', '/login')).toBe('https://example.com/login/');
|
|
63
|
+
});
|
|
64
|
+
it('should handle relative login path with trailing slash', () => {
|
|
65
|
+
expect(mergeUrlsWithParams('https://example.com', 'login/')).toBe('https://example.com/login/');
|
|
66
|
+
});
|
|
67
|
+
it('should handle multiple slashes in path', () => {
|
|
68
|
+
expect(mergeUrlsWithParams('https://example.com', '//login///path//')).toBe('https://example.com/login/path/');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('query parameter handling', () => {
|
|
72
|
+
it('should merge query parameters from both URLs', () => {
|
|
73
|
+
expect(mergeUrlsWithParams('https://example.com?a=1', 'login?b=2')).toBe('https://example.com/login/?a=1&b=2');
|
|
74
|
+
});
|
|
75
|
+
it('should override base query parameters with login query parameters', () => {
|
|
76
|
+
expect(mergeUrlsWithParams('https://example.com?param=1', 'login?param=2')).toBe('https://example.com/login/?param=2');
|
|
77
|
+
});
|
|
78
|
+
it('should handle multiple question marks in login URL', () => {
|
|
79
|
+
expect(mergeUrlsWithParams('https://example.com', 'login?param1=1?param2=2')).toBe('https://example.com/login/?param1=1%3Fparam2%3D2');
|
|
80
|
+
});
|
|
81
|
+
it('should handle complex paths and query parameters', () => {
|
|
82
|
+
expect(mergeUrlsWithParams('https://example.com/api?token=123', '/auth/login/?user=test&scope=full')).toBe('https://example.com/api/auth/login/?token=123&user=test&scope=full');
|
|
83
|
+
});
|
|
84
|
+
it('should handle alternative SSO host with relative path', () => {
|
|
85
|
+
const result = mergeUrlsWithParams('http://sso.app.client.com/auth?app=staging', '/users/login/?tracking=true&role=view');
|
|
86
|
+
const url = new URL(result);
|
|
87
|
+
expect(url.origin).toBe('http://sso.app.client.com');
|
|
88
|
+
expect(url.pathname).toBe('/auth/users/login/');
|
|
89
|
+
expect(url.searchParams.get('app')).toBe('staging');
|
|
90
|
+
expect(url.searchParams.get('tracking')).toBe('true');
|
|
91
|
+
expect(url.searchParams.get('role')).toBe('view');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
package/package.json
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { escapeSingleQuotes } from './escapeSingleQuotes.js';
|
|
2
|
-
export { normalizeUrl } from './
|
|
2
|
+
export { mergeUrlsWithParams, normalizeUrl } from './url.js';
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { mergeUrlsWithParams, normalizeUrl } from './url.js';
|
|
4
|
+
|
|
5
|
+
describe('normalizeUrl', () => {
|
|
6
|
+
it("should add a trailing slash if url doesn't have it", () => {
|
|
7
|
+
expect(normalizeUrl('https://example.com')).toBe('https://example.com/');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should not add an extra trailing slash if url does have it', () => {
|
|
11
|
+
expect(normalizeUrl('https://example.com/')).toBe('https://example.com/');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should include search string when specified', () => {
|
|
15
|
+
expect(normalizeUrl('https://example.com?param=value', true)).toBe(
|
|
16
|
+
'https://example.com/?param=value',
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should throw error for invalid URL', () => {
|
|
21
|
+
expect(() => normalizeUrl('not-a-url')).toThrow();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should throw error for empty string', () => {
|
|
25
|
+
expect(() => normalizeUrl('')).toThrow('URL cannot be empty');
|
|
26
|
+
expect(() => normalizeUrl(' ')).toThrow('URL cannot be empty');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should throw error for non-string input', () => {
|
|
30
|
+
// @ts-expect-error Testing runtime type check
|
|
31
|
+
expect(() => normalizeUrl(null)).toThrow('URL must be a string');
|
|
32
|
+
// @ts-expect-error Testing runtime type check
|
|
33
|
+
expect(() => normalizeUrl(undefined)).toThrow('URL must be a string');
|
|
34
|
+
// @ts-expect-error Testing runtime type check
|
|
35
|
+
expect(() => normalizeUrl(123)).toThrow('URL must be a string');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('mergeUrlsWithParams', () => {
|
|
40
|
+
describe('input validation', () => {
|
|
41
|
+
it('should throw error when base URL is not provided', () => {
|
|
42
|
+
expect(() => mergeUrlsWithParams('')).toThrow('Base URL is required');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw error when base URL is invalid', () => {
|
|
46
|
+
expect(() => mergeUrlsWithParams('not-a-url')).toThrow('Base URL is not valid');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('base URL handling', () => {
|
|
51
|
+
it('should return base URL when login URL is not provided', () => {
|
|
52
|
+
expect(mergeUrlsWithParams('https://example.com')).toBe('https://example.com/');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should return base URL when login URL is just a slash', () => {
|
|
56
|
+
expect(mergeUrlsWithParams('https://example.com', '/')).toBe('https://example.com/');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should preserve base URL port if specified', () => {
|
|
60
|
+
expect(mergeUrlsWithParams('https://example.com:8080', 'login')).toBe(
|
|
61
|
+
'https://example.com:8080/login/',
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('absolute URL handling', () => {
|
|
67
|
+
it('should use absolute login URL when provided', () => {
|
|
68
|
+
expect(mergeUrlsWithParams('https://example.com', 'https://login.example.com/')).toBe(
|
|
69
|
+
'https://login.example.com/',
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should preserve query params in absolute login URL', () => {
|
|
74
|
+
expect(
|
|
75
|
+
mergeUrlsWithParams('https://example.com', 'https://login.example.com?token=123'),
|
|
76
|
+
).toBe('https://login.example.com/?token=123');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('relative path handling', () => {
|
|
81
|
+
it('should merge relative login path with base URL', () => {
|
|
82
|
+
expect(mergeUrlsWithParams('https://example.com', 'login')).toBe(
|
|
83
|
+
'https://example.com/login/',
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should handle relative login path with leading slash', () => {
|
|
88
|
+
expect(mergeUrlsWithParams('https://example.com', '/login')).toBe(
|
|
89
|
+
'https://example.com/login/',
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should handle relative login path with trailing slash', () => {
|
|
94
|
+
expect(mergeUrlsWithParams('https://example.com', 'login/')).toBe(
|
|
95
|
+
'https://example.com/login/',
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle multiple slashes in path', () => {
|
|
100
|
+
expect(mergeUrlsWithParams('https://example.com', '//login///path//')).toBe(
|
|
101
|
+
'https://example.com/login/path/',
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('query parameter handling', () => {
|
|
107
|
+
it('should merge query parameters from both URLs', () => {
|
|
108
|
+
expect(mergeUrlsWithParams('https://example.com?a=1', 'login?b=2')).toBe(
|
|
109
|
+
'https://example.com/login/?a=1&b=2',
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should override base query parameters with login query parameters', () => {
|
|
114
|
+
expect(mergeUrlsWithParams('https://example.com?param=1', 'login?param=2')).toBe(
|
|
115
|
+
'https://example.com/login/?param=2',
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle multiple question marks in login URL', () => {
|
|
120
|
+
expect(mergeUrlsWithParams('https://example.com', 'login?param1=1?param2=2')).toBe(
|
|
121
|
+
'https://example.com/login/?param1=1%3Fparam2%3D2',
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should handle complex paths and query parameters', () => {
|
|
126
|
+
expect(
|
|
127
|
+
mergeUrlsWithParams(
|
|
128
|
+
'https://example.com/api?token=123',
|
|
129
|
+
'/auth/login/?user=test&scope=full',
|
|
130
|
+
),
|
|
131
|
+
).toBe('https://example.com/api/auth/login/?token=123&user=test&scope=full');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle alternative SSO host with relative path', () => {
|
|
135
|
+
const result = mergeUrlsWithParams(
|
|
136
|
+
'http://sso.app.client.com/auth?app=staging',
|
|
137
|
+
'/users/login/?tracking=true&role=view',
|
|
138
|
+
);
|
|
139
|
+
const url = new URL(result);
|
|
140
|
+
|
|
141
|
+
expect(url.origin).toBe('http://sso.app.client.com');
|
|
142
|
+
expect(url.pathname).toBe('/auth/users/login/');
|
|
143
|
+
expect(url.searchParams.get('app')).toBe('staging');
|
|
144
|
+
expect(url.searchParams.get('tracking')).toBe('true');
|
|
145
|
+
expect(url.searchParams.get('role')).toBe('view');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
package/src/utils/url.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes a URL string by ensuring it has a trailing slash and optionally includes search parameters.
|
|
3
|
+
* @param url - The URL string to normalize
|
|
4
|
+
* @param includeSearchString - Whether to include search parameters in the result
|
|
5
|
+
* @returns The normalized URL string
|
|
6
|
+
* @throws Error if the URL is invalid, empty, or not a string
|
|
7
|
+
*/
|
|
8
|
+
export function normalizeUrl(url: string, includeSearchString?: boolean): string {
|
|
9
|
+
if (typeof url !== 'string') {
|
|
10
|
+
throw new Error('URL must be a string');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!url.trim()) {
|
|
14
|
+
throw new Error('URL cannot be empty');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const urlObject = new URL(url);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
urlObject.origin +
|
|
22
|
+
(urlObject.pathname.endsWith('/') ? urlObject.pathname : urlObject.pathname + '/') +
|
|
23
|
+
(includeSearchString && urlObject.search ? urlObject.search : '')
|
|
24
|
+
);
|
|
25
|
+
} catch {
|
|
26
|
+
throw new Error(`Connection string ${url} is not a valid URL`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Merges two URLs, combining their paths and query parameters.
|
|
32
|
+
* If loginUrlStr is an absolute URL, it takes precedence over baseUrlStr.
|
|
33
|
+
* If loginUrlStr is relative, it's merged with baseUrlStr.
|
|
34
|
+
*
|
|
35
|
+
* @param baseUrlStr - The base URL to merge with
|
|
36
|
+
* @param loginUrlStr - The login URL (can be absolute or relative)
|
|
37
|
+
* @returns The merged URL string
|
|
38
|
+
* @throws Error if baseUrlStr is not a valid URL
|
|
39
|
+
*/
|
|
40
|
+
export function mergeUrlsWithParams(baseUrlStr: string, loginUrlStr?: string): string {
|
|
41
|
+
if (!baseUrlStr) {
|
|
42
|
+
throw new Error('Base URL is required');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Parse base URL first to validate it
|
|
46
|
+
let baseUrl: URL;
|
|
47
|
+
try {
|
|
48
|
+
baseUrl = new URL(baseUrlStr);
|
|
49
|
+
} catch {
|
|
50
|
+
throw new Error('Base URL is not valid');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// If no login URL provided or it's just a slash, return base URL
|
|
54
|
+
if (!loginUrlStr || loginUrlStr === '/') {
|
|
55
|
+
return baseUrl.toString();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Check if loginUrlStr is an absolute URL
|
|
60
|
+
const loginUrl = new URL(loginUrlStr);
|
|
61
|
+
return loginUrl.toString();
|
|
62
|
+
} catch {
|
|
63
|
+
// loginUrlStr is relative, proceed with merging
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Create a URL by joining base origin with login path
|
|
67
|
+
const resultUrl = new URL(baseUrl.toString());
|
|
68
|
+
|
|
69
|
+
// Split login URL into path and query parts
|
|
70
|
+
const [loginPath = '', loginQuery = ''] = loginUrlStr.split(/\?(.+)/);
|
|
71
|
+
|
|
72
|
+
// Handle the path merging
|
|
73
|
+
const basePath = baseUrl.pathname.replace(/\/$/, ''); // Remove trailing slash
|
|
74
|
+
const cleanLoginPath = loginPath
|
|
75
|
+
.replace(/^\/+/, '') // Remove leading slashes
|
|
76
|
+
.replace(/\/+$/, '') // Remove trailing slashes
|
|
77
|
+
.replace(/\/+/g, '/'); // Replace multiple slashes with single slash
|
|
78
|
+
|
|
79
|
+
resultUrl.pathname = cleanLoginPath ? `${basePath}/${cleanLoginPath}` : basePath;
|
|
80
|
+
|
|
81
|
+
// Parse and merge query parameters from both URLs
|
|
82
|
+
if (loginQuery) {
|
|
83
|
+
const loginParams = new URLSearchParams(loginQuery);
|
|
84
|
+
for (const [key, value] of loginParams.entries()) {
|
|
85
|
+
resultUrl.searchParams.set(key, value);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Normalize the final URL by removing trailing slash
|
|
90
|
+
return normalizeUrl(resultUrl.toString(), true);
|
|
91
|
+
}
|