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 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;AAE5B,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;AAqCD,QAAA,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA+O/C,CAAC;AAEF,eAAe,aAAa,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"}
@@ -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
- // Security: Validate and sanitize URL
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
- // Security: Use baseUrl which is validated
97
- const response = await fetch(`${baseUrl}/api/shorten`, {
98
- method: 'POST',
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(data.data.shortUrl);
78
+ setShortUrl(result.data.shortUrl);
122
79
  if (onSuccess) {
123
- onSuccess(data);
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
@@ -1,3 +1,4 @@
1
1
  export { default as ShinyUrlInput } from './ShinyUrlInput';
2
2
  export type { ShinyUrlInputProps } from './ShinyUrlInput';
3
+ export * from './shortener';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -1 +1,2 @@
1
1
  export { default as ShinyUrlInput } from './ShinyUrlInput';
2
+ export * from './shortener';
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiny-url-input-box",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A reusable React component for generating ShinyURLs",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -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
- // Security: Use baseUrl which is validated
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 (!response.ok) {
149
- // Try to get error message from response
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(data.data.shortUrl);
110
+ setShortUrl(result.data.shortUrl);
167
111
 
168
112
  if (onSuccess) {
169
- onSuccess(data);
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
@@ -1,3 +1,4 @@
1
1
  export { default as ShinyUrlInput } from './ShinyUrlInput';
2
2
  export type { ShinyUrlInputProps } from './ShinyUrlInput';
3
+ export * from './shortener';
3
4
 
@@ -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
+ };