shiny-url-input-box 1.0.1 → 1.1.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/README.md +48 -0
- package/dist/ShinyUrlInput.d.ts.map +1 -1
- package/dist/ShinyUrlInput.js +6 -53
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/shortener.d.ts +17 -0
- package/dist/shortener.d.ts.map +1 -0
- package/dist/shortener.js +66 -0
- package/package.json +1 -1
- package/src/ShinyUrlInput.tsx +6 -69
- package/src/index.ts +1 -0
- package/src/shortener.ts +93 -0
package/README.md
CHANGED
|
@@ -62,3 +62,51 @@ function App() {
|
|
|
62
62
|
- `buttonText` (string, optional) - Submit button text (default: "Shorten URL")
|
|
63
63
|
- `className` (string, optional) - Additional CSS class for the wrapper
|
|
64
64
|
|
|
65
|
+
|
|
66
|
+
## Programmatic Usage
|
|
67
|
+
|
|
68
|
+
You can also use the exported `shortenUrl` function to shorten URLs programmatically without using the UI component.
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
import { shortenUrl } from 'shiny-url-input-box';
|
|
72
|
+
|
|
73
|
+
// Inside an async function
|
|
74
|
+
try {
|
|
75
|
+
const result = await shortenUrl(
|
|
76
|
+
'https://example.com/long/url',
|
|
77
|
+
'YOUR_API_KEY',
|
|
78
|
+
{ apiBaseUrl: 'https://shinyurl-backend.onrender.com' } // Optional
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (result.success) {
|
|
82
|
+
console.log('Short URL:', result.data.shortUrl);
|
|
83
|
+
} else {
|
|
84
|
+
console.error('Error:', result.message);
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('Failed to shorten URL:', error);
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `shortenUrl` Arguments
|
|
92
|
+
|
|
93
|
+
1. `url` (string): The URL to shorten.
|
|
94
|
+
2. `apiKey` (string): Your API key.
|
|
95
|
+
3. `options` (object, optional):
|
|
96
|
+
- `apiBaseUrl` (string): Base URL of the backend API. Defaults to window origin or shinyurl-backend.onrender.com.
|
|
97
|
+
|
|
98
|
+
### Return Value
|
|
99
|
+
|
|
100
|
+
Returns a Promise that resolves to:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface ShortenResponse {
|
|
104
|
+
success: boolean;
|
|
105
|
+
data?: {
|
|
106
|
+
originalUrl: string;
|
|
107
|
+
shortUrl: string;
|
|
108
|
+
shortCode: string;
|
|
109
|
+
};
|
|
110
|
+
message?: string;
|
|
111
|
+
}
|
|
112
|
+
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShinyUrlInput.d.ts","sourceRoot":"","sources":["../src/ShinyUrlInput.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"ShinyUrlInput.d.ts","sourceRoot":"","sources":["../src/ShinyUrlInput.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAC5D,OAAO,oBAAoB,CAAC;AAG5B,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkN/C,CAAC;AAEF,eAAe,aAAa,CAAC"}
|
package/dist/ShinyUrlInput.js
CHANGED
|
@@ -2,29 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useMemo, useEffect } from 'react';
|
|
4
4
|
import './TinyUrlInput.css';
|
|
5
|
-
|
|
6
|
-
const isValidUrl = (string) => {
|
|
7
|
-
try {
|
|
8
|
-
const url = new URL(string);
|
|
9
|
-
// Only allow http and https protocols
|
|
10
|
-
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
11
|
-
}
|
|
12
|
-
catch (_) {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
// Security: Sanitize input to prevent XSS
|
|
17
|
-
const sanitizeInput = (input) => {
|
|
18
|
-
return input.trim().replace(/[<>]/g, '');
|
|
19
|
-
};
|
|
20
|
-
// Get default API base URL (same origin or localhost:5000)
|
|
21
|
-
const getDefaultApiBaseUrl = () => {
|
|
22
|
-
if (typeof window !== 'undefined') {
|
|
23
|
-
// Use same origin if available
|
|
24
|
-
return window.location.origin;
|
|
25
|
-
}
|
|
26
|
-
return 'https://shinyurl-backend.onrender.com';
|
|
27
|
-
};
|
|
5
|
+
import { shortenUrl, getDefaultApiBaseUrl, sanitizeInput, isValidUrl } from './shortener';
|
|
28
6
|
const ShinyUrlInput = ({ apiKey, apiBaseUrl, onSuccess, onError, label = 'Enter URL to shorten', buttonText = 'Shorten URL', className = '', installationPageUrl }) => {
|
|
29
7
|
const [url, setUrl] = useState('');
|
|
30
8
|
const [loading, setLoading] = useState(false);
|
|
@@ -93,34 +71,13 @@ const ShinyUrlInput = ({ apiKey, apiBaseUrl, onSuccess, onError, label = 'Enter
|
|
|
93
71
|
setShortUrl('');
|
|
94
72
|
setCopied(false);
|
|
95
73
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
headers: {
|
|
100
|
-
'Content-Type': 'application/json',
|
|
101
|
-
'X-API-Key': apiKey,
|
|
102
|
-
},
|
|
103
|
-
body: JSON.stringify({ originalUrl: sanitizedUrl }),
|
|
104
|
-
});
|
|
105
|
-
if (!response.ok) {
|
|
106
|
-
// Try to get error message from response
|
|
107
|
-
let errorMessage = 'Failed to shorten URL';
|
|
108
|
-
try {
|
|
109
|
-
const errorData = await response.json();
|
|
110
|
-
errorMessage = errorData.message || errorMessage;
|
|
111
|
-
}
|
|
112
|
-
catch (e) {
|
|
113
|
-
errorMessage = `Server error: ${response.status} ${response.statusText}`;
|
|
114
|
-
}
|
|
115
|
-
throw new Error(errorMessage);
|
|
116
|
-
}
|
|
117
|
-
const data = await response.json();
|
|
118
|
-
if (!data.success || !data.data) {
|
|
119
|
-
throw new Error(data.message || 'Failed to shorten URL');
|
|
74
|
+
const result = await shortenUrl(sanitizedUrl, apiKey, { apiBaseUrl: baseUrl });
|
|
75
|
+
if (!result.success || !result.data) {
|
|
76
|
+
throw new Error(result.message || 'Failed to shorten URL');
|
|
120
77
|
}
|
|
121
|
-
setShortUrl(
|
|
78
|
+
setShortUrl(result.data.shortUrl);
|
|
122
79
|
if (onSuccess) {
|
|
123
|
-
onSuccess(
|
|
80
|
+
onSuccess(result);
|
|
124
81
|
}
|
|
125
82
|
}
|
|
126
83
|
catch (err) {
|
|
@@ -128,10 +85,6 @@ const ShinyUrlInput = ({ apiKey, apiBaseUrl, onSuccess, onError, label = 'Enter
|
|
|
128
85
|
if (err instanceof Error) {
|
|
129
86
|
errorMessage = err.message;
|
|
130
87
|
}
|
|
131
|
-
// Handle network errors
|
|
132
|
-
if (err instanceof TypeError && err.message.includes('fetch')) {
|
|
133
|
-
errorMessage = 'Cannot connect to server. Make sure the backend is running on ' + baseUrl;
|
|
134
|
-
}
|
|
135
88
|
setError(errorMessage);
|
|
136
89
|
if (onError) {
|
|
137
90
|
onError(errorMessage);
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface ShortenResponse {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: {
|
|
4
|
+
originalUrl: string;
|
|
5
|
+
shortUrl: string;
|
|
6
|
+
shortCode: string;
|
|
7
|
+
};
|
|
8
|
+
message?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ShortenOptions {
|
|
11
|
+
apiBaseUrl?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const isValidUrl: (string: string) => boolean;
|
|
14
|
+
export declare const sanitizeInput: (input: string) => string;
|
|
15
|
+
export declare const getDefaultApiBaseUrl: () => string;
|
|
16
|
+
export declare const shortenUrl: (originalUrl: string, apiKey: string, options?: ShortenOptions) => Promise<ShortenResponse>;
|
|
17
|
+
//# sourceMappingURL=shortener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortener.d.ts","sourceRoot":"","sources":["../src/shortener.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE;QACL,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAGD,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,KAAG,OAQ3C,CAAC;AAGF,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,KAAG,MAE7C,CAAC;AAGF,eAAO,MAAM,oBAAoB,QAAO,MAMvC,CAAC;AAEF,eAAO,MAAM,UAAU,GACrB,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,UAAS,cAAmB,KAC3B,OAAO,CAAC,eAAe,CAiDzB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Security: Validate and sanitize URL
|
|
2
|
+
export const isValidUrl = (string) => {
|
|
3
|
+
try {
|
|
4
|
+
const url = new URL(string);
|
|
5
|
+
// Only allow http and https protocols
|
|
6
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
7
|
+
}
|
|
8
|
+
catch (_) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
// Security: Sanitize input to prevent XSS
|
|
13
|
+
export const sanitizeInput = (input) => {
|
|
14
|
+
return input.trim().replace(/[<>]/g, '');
|
|
15
|
+
};
|
|
16
|
+
// Get default API base URL (same origin or localhost:5000)
|
|
17
|
+
export const getDefaultApiBaseUrl = () => {
|
|
18
|
+
if (typeof window !== 'undefined') {
|
|
19
|
+
// Use same origin if available
|
|
20
|
+
return window.location.origin;
|
|
21
|
+
}
|
|
22
|
+
return 'https://shinyurl-backend.onrender.com';
|
|
23
|
+
};
|
|
24
|
+
export const shortenUrl = async (originalUrl, apiKey, options = {}) => {
|
|
25
|
+
const sanitizedUrl = sanitizeInput(originalUrl);
|
|
26
|
+
if (!sanitizedUrl) {
|
|
27
|
+
return { success: false, message: 'Please enter a URL' };
|
|
28
|
+
}
|
|
29
|
+
if (!isValidUrl(sanitizedUrl)) {
|
|
30
|
+
return { success: false, message: 'Please enter a valid URL (must start with http:// or https://)' };
|
|
31
|
+
}
|
|
32
|
+
const baseUrl = options.apiBaseUrl || getDefaultApiBaseUrl();
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch(`${baseUrl}/api/shorten`, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'X-API-Key': apiKey,
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({ originalUrl: sanitizedUrl }),
|
|
41
|
+
});
|
|
42
|
+
if (!response.ok) {
|
|
43
|
+
let errorMessage = 'Failed to shorten URL';
|
|
44
|
+
try {
|
|
45
|
+
const errorData = await response.json();
|
|
46
|
+
errorMessage = errorData.message || errorMessage;
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
errorMessage = `Server error: ${response.status} ${response.statusText}`;
|
|
50
|
+
}
|
|
51
|
+
return { success: false, message: errorMessage };
|
|
52
|
+
}
|
|
53
|
+
const data = await response.json();
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
let errorMessage = 'An error occurred';
|
|
58
|
+
if (err instanceof Error) {
|
|
59
|
+
errorMessage = err.message;
|
|
60
|
+
}
|
|
61
|
+
if (err instanceof TypeError && typeof err.message === 'string' && err.message.includes('fetch')) {
|
|
62
|
+
errorMessage = 'Cannot connect to server. Make sure the backend is running on ' + baseUrl;
|
|
63
|
+
}
|
|
64
|
+
return { success: false, message: errorMessage };
|
|
65
|
+
}
|
|
66
|
+
};
|
package/package.json
CHANGED
package/src/ShinyUrlInput.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useMemo, useEffect } from 'react';
|
|
4
4
|
import './TinyUrlInput.css';
|
|
5
|
+
import { shortenUrl, ShortenResponse, getDefaultApiBaseUrl, sanitizeInput, isValidUrl } from './shortener';
|
|
5
6
|
|
|
6
7
|
export interface ShinyUrlInputProps {
|
|
7
8
|
apiKey: string;
|
|
@@ -14,41 +15,6 @@ export interface ShinyUrlInputProps {
|
|
|
14
15
|
installationPageUrl?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
interface ShortenResponse {
|
|
18
|
-
success: boolean;
|
|
19
|
-
data?: {
|
|
20
|
-
originalUrl: string;
|
|
21
|
-
shortUrl: string;
|
|
22
|
-
shortCode: string;
|
|
23
|
-
};
|
|
24
|
-
message?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Security: Validate and sanitize URL
|
|
28
|
-
const isValidUrl = (string: string): boolean => {
|
|
29
|
-
try {
|
|
30
|
-
const url = new URL(string);
|
|
31
|
-
// Only allow http and https protocols
|
|
32
|
-
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
33
|
-
} catch (_) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Security: Sanitize input to prevent XSS
|
|
39
|
-
const sanitizeInput = (input: string): string => {
|
|
40
|
-
return input.trim().replace(/[<>]/g, '');
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// Get default API base URL (same origin or localhost:5000)
|
|
44
|
-
const getDefaultApiBaseUrl = (): string => {
|
|
45
|
-
if (typeof window !== 'undefined') {
|
|
46
|
-
// Use same origin if available
|
|
47
|
-
return window.location.origin;
|
|
48
|
-
}
|
|
49
|
-
return 'https://shinyurl-backend.onrender.com';
|
|
50
|
-
};
|
|
51
|
-
|
|
52
18
|
const ShinyUrlInput: React.FC<ShinyUrlInputProps> = ({
|
|
53
19
|
apiKey,
|
|
54
20
|
apiBaseUrl,
|
|
@@ -135,51 +101,22 @@ const ShinyUrlInput: React.FC<ShinyUrlInputProps> = ({
|
|
|
135
101
|
setCopied(false);
|
|
136
102
|
|
|
137
103
|
try {
|
|
138
|
-
|
|
139
|
-
const response = await fetch(`${baseUrl}/api/shorten`, {
|
|
140
|
-
method: 'POST',
|
|
141
|
-
headers: {
|
|
142
|
-
'Content-Type': 'application/json',
|
|
143
|
-
'X-API-Key': apiKey,
|
|
144
|
-
},
|
|
145
|
-
body: JSON.stringify({ originalUrl: sanitizedUrl }),
|
|
146
|
-
});
|
|
104
|
+
const result = await shortenUrl(sanitizedUrl, apiKey, { apiBaseUrl: baseUrl });
|
|
147
105
|
|
|
148
|
-
if (!
|
|
149
|
-
|
|
150
|
-
let errorMessage = 'Failed to shorten URL';
|
|
151
|
-
try {
|
|
152
|
-
const errorData: ShortenResponse = await response.json();
|
|
153
|
-
errorMessage = errorData.message || errorMessage;
|
|
154
|
-
} catch (e) {
|
|
155
|
-
errorMessage = `Server error: ${response.status} ${response.statusText}`;
|
|
156
|
-
}
|
|
157
|
-
throw new Error(errorMessage);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const data: ShortenResponse = await response.json();
|
|
161
|
-
|
|
162
|
-
if (!data.success || !data.data) {
|
|
163
|
-
throw new Error(data.message || 'Failed to shorten URL');
|
|
106
|
+
if (!result.success || !result.data) {
|
|
107
|
+
throw new Error(result.message || 'Failed to shorten URL');
|
|
164
108
|
}
|
|
165
109
|
|
|
166
|
-
setShortUrl(
|
|
110
|
+
setShortUrl(result.data.shortUrl);
|
|
167
111
|
|
|
168
112
|
if (onSuccess) {
|
|
169
|
-
onSuccess(
|
|
113
|
+
onSuccess(result);
|
|
170
114
|
}
|
|
171
115
|
} catch (err) {
|
|
172
116
|
let errorMessage = 'An error occurred';
|
|
173
|
-
|
|
174
117
|
if (err instanceof Error) {
|
|
175
118
|
errorMessage = err.message;
|
|
176
119
|
}
|
|
177
|
-
|
|
178
|
-
// Handle network errors
|
|
179
|
-
if (err instanceof TypeError && err.message.includes('fetch')) {
|
|
180
|
-
errorMessage = 'Cannot connect to server. Make sure the backend is running on ' + baseUrl;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
120
|
setError(errorMessage);
|
|
184
121
|
|
|
185
122
|
if (onError) {
|
package/src/index.ts
CHANGED
package/src/shortener.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export interface ShortenResponse {
|
|
2
|
+
success: boolean;
|
|
3
|
+
data?: {
|
|
4
|
+
originalUrl: string;
|
|
5
|
+
shortUrl: string;
|
|
6
|
+
shortCode: string;
|
|
7
|
+
};
|
|
8
|
+
message?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ShortenOptions {
|
|
12
|
+
apiBaseUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Security: Validate and sanitize URL
|
|
16
|
+
export const isValidUrl = (string: string): boolean => {
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(string);
|
|
19
|
+
// Only allow http and https protocols
|
|
20
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Security: Sanitize input to prevent XSS
|
|
27
|
+
export const sanitizeInput = (input: string): string => {
|
|
28
|
+
return input.trim().replace(/[<>]/g, '');
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Get default API base URL (same origin or localhost:5000)
|
|
32
|
+
export const getDefaultApiBaseUrl = (): string => {
|
|
33
|
+
if (typeof window !== 'undefined') {
|
|
34
|
+
// Use same origin if available
|
|
35
|
+
return window.location.origin;
|
|
36
|
+
}
|
|
37
|
+
return 'https://shinyurl-backend.onrender.com';
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const shortenUrl = async (
|
|
41
|
+
originalUrl: string,
|
|
42
|
+
apiKey: string,
|
|
43
|
+
options: ShortenOptions = {}
|
|
44
|
+
): Promise<ShortenResponse> => {
|
|
45
|
+
const sanitizedUrl = sanitizeInput(originalUrl);
|
|
46
|
+
|
|
47
|
+
if (!sanitizedUrl) {
|
|
48
|
+
return { success: false, message: 'Please enter a URL' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!isValidUrl(sanitizedUrl)) {
|
|
52
|
+
return { success: false, message: 'Please enter a valid URL (must start with http:// or https://)' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const baseUrl = options.apiBaseUrl || getDefaultApiBaseUrl();
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const response = await fetch(`${baseUrl}/api/shorten`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
'X-API-Key': apiKey,
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({ originalUrl: sanitizedUrl }),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
let errorMessage = 'Failed to shorten URL';
|
|
69
|
+
try {
|
|
70
|
+
const errorData = await response.json();
|
|
71
|
+
errorMessage = errorData.message || errorMessage;
|
|
72
|
+
} catch (e) {
|
|
73
|
+
errorMessage = `Server error: ${response.status} ${response.statusText}`;
|
|
74
|
+
}
|
|
75
|
+
return { success: false, message: errorMessage };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const data: ShortenResponse = await response.json();
|
|
79
|
+
return data;
|
|
80
|
+
} catch (err: unknown) {
|
|
81
|
+
let errorMessage = 'An error occurred';
|
|
82
|
+
|
|
83
|
+
if (err instanceof Error) {
|
|
84
|
+
errorMessage = err.message;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (err instanceof TypeError && typeof err.message === 'string' && err.message.includes('fetch')) {
|
|
88
|
+
errorMessage = 'Cannot connect to server. Make sure the backend is running on ' + baseUrl;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { success: false, message: errorMessage };
|
|
92
|
+
}
|
|
93
|
+
};
|