@dytsou/shorten-url 2.4.3 → 2.4.4

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
@@ -1,231 +1,188 @@
1
1
  # URL Shortener
2
2
 
3
- A modern, fast URL shortener built with Cloudflare Workers and a responsive web interface. This project allows anyone to create short URLs with optional custom slugs, featuring a clean UI and robust backend with comprehensive API documentation.
3
+ A URL shortener built with Cloudflare Workers, Cloudflare KV, and a responsive static web interface. The Worker creates short links, stores them in KV, and serves redirects; the `docs/` directory contains the website and OpenAPI reference.
4
+
5
+ [![Built with Cloudflare](https://workers.cloudflare.com/built-with-cloudflare.svg)](https://cloudflare.com)
6
+ [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/dytsou/shorten-url)
4
7
 
5
8
  ## Features
6
9
 
7
- - **URL Shortening**: Convert long URLs into short, shareable links
8
- - **Custom Slugs**: Create personalized short URLs (optional)
9
- - **Protected Shortening**: Short URL creation is protected behind Cloudflare Access / WARP for safer usage
10
- - **Responsive Design**: Works seamlessly on desktop and mobile devices
11
- - **Fast Performance**: Built on Cloudflare Workers for global edge deployment
12
- - **Security Features**: URL validation, rate limiting, and optional Safe Browsing integration
13
- - **Modern Copy Function**: One-click copying with fallbacks for all browsers
14
- - **Custom Error Pages**: Beautiful 404, security warning, and redirect pages (fully customizable via your GitHub Pages)
15
- - **API Documentation**: Interactive Swagger UI with OpenAPI 3.1.0 specification
16
- - **Analytics Ready**: Optional click tracking and analytics integration
10
+ - **URL Shortening**: Create short links stored in Cloudflare KV and redirect with HTTP 302
11
+ - **Custom Slugs**: Optionally choose a slug using letters, numbers, hyphens, or underscores
12
+ - **Cloudflare Access**: The Worker checks for Cloudflare Access JWT and email headers before creating links
13
+ - **Responsive Design**: Static shortening form supports desktop and mobile screens
14
+ - **Unique Links**: Optionally reuse the same short key when the destination URL already exists
15
+ - **URL Safety**: Validate submitted URLs and optionally check redirect destinations with Google Safe Browsing
16
+ - **Copy Support**: Copy shortened URLs with a manual-selection fallback
17
+ - **Hosted Error Pages**: Configure external 404, error, Safe Browsing warning, and no-referrer pages
18
+ - **API Documentation**: Static Swagger UI with an OpenAPI 3.1 specification
19
+ - **Worker Observability**: Structured logs and traces are supported
20
+
21
+ The Worker does not implement click analytics, per-IP rate limiting, or blocked-domain lists. The similarly named settings in `config/config.js` are not used by the current Worker.
17
22
 
18
23
  ## Quick Start
19
24
 
20
25
  ### Prerequisites
21
26
 
22
- - A Cloudflare account with Workers enabled
23
- - GitHub account for hosting the frontend (or any static hosting)
24
- - Basic knowledge of Git and command line
27
+ - A Cloudflare account with Workers and Pages; the Deploy to Cloudflare button provisions the required KV namespace
28
+ - A GitHub or GitLab account to receive the repository created by the Deploy to Cloudflare button
29
+ - A Cloudflare Pages project to publish `docs/`
30
+ - Node.js 24 or newer and pnpm 10.22.0 for manual deployment
25
31
 
26
- ### 1. Clone the Repository
32
+ ### 1. Deploy with Cloudflare (Recommended)
27
33
 
28
- ```bash
29
- git clone https://github.com/dytsou/shorten-url.git
30
- cd shorten-url
31
- ```
34
+ 1. Click the **Deploy to Cloudflare** button near the top of this README.
35
+ 2. Connect your Cloudflare and GitHub or GitLab accounts, then choose the destination repository and Worker name.
36
+ 3. Confirm the `LINKS` KV binding. Cloudflare provisions the namespace and deploys the Worker from the `main` branch.
32
37
 
33
- ### 2. Setup Configuration
38
+ The button deploys the Worker; it does not publish the static homepage in `docs/`. Connect the created repository to Cloudflare Pages, leave the build command blank, and set the output directory to `docs`. Then set `frontend.url` in `config/config.js` to the Pages URL and push the change to redeploy the Worker. The Worker fetches the static homepage and serves it at `/`, so the form submits to `POST /` on the Worker.
34
39
 
35
- ```bash
36
- # Copy the example configuration
37
- cp config/config.example.js config/config.js
40
+ ### 2. Alternative: Manual Worker Deployment
41
+
42
+ Clone the repository and install the pinned Wrangler version:
38
43
 
39
- # Edit the configuration file
40
- vim config/config.js
44
+ ```bash
45
+ git clone https://github.com/dytsou/shorten-url.git
46
+ cd shorten-url
47
+ corepack enable
48
+ pnpm install
41
49
  ```
42
50
 
43
- Update the `config/config.js` file with your settings:
51
+ Set `frontend.url` in `config/config.js` to the public URL where you host `docs/`. The Worker fetches this page for `GET /`:
44
52
 
45
53
  ```javascript
46
54
  const config = {
47
55
  frontend: {
48
- // IMPORTANT: Update this to your GitHub Pages URL or custom domain
49
- url: "https://yourusername.github.io/shorten-url/",
56
+ url: "https://your-project.pages.dev/",
50
57
  },
51
- // ... other settings
58
+ // Configure worker options here
52
59
  };
53
60
  ```
54
61
 
55
- ### 3. Deploy Frontend
56
-
57
- #### Option A: GitHub Pages (Recommended)
62
+ `src/worker.js` imports `config/config.js` directly. Keep API keys out of this file; configure the optional Google Safe Browsing key with a Wrangler secret.
58
63
 
59
- 1. Push your code to GitHub
60
- 2. Go to your repository settings
61
- 3. Enable GitHub Pages for the main branch
62
- 4. Your frontend will be available at `https://yourusername.github.io/shorten-url/`
63
-
64
- #### Option B: Custom Domain
65
-
66
- 1. Upload the `docs/` directory to your web hosting
67
- 2. Update the `frontend.url` in `config/config.js` to match your domain
68
-
69
- ### 4. Setup Cloudflare Workers
70
-
71
- #### Install Wrangler CLI
64
+ Install Wrangler, log in, and deploy:
72
65
 
73
66
  ```bash
74
- npm install -g wrangler
75
- ```
76
-
77
- #### Configure Wrangler
78
-
79
- ```bash
80
- # Login to Cloudflare
81
- wrangler auth login
82
-
83
- # Create a new KV namespace for storing URLs
84
- wrangler kv:namespace create "LINKS"
85
- ```
86
-
87
- #### Create wrangler.toml
88
-
89
- Create a `wrangler.toml` file in your project root:
90
-
91
- ```toml
92
- name = "url-shortener"
93
- main = "src/worker.js"
94
- compatibility_date = "2024-01-01"
95
-
96
- [[kv_namespaces]]
97
- binding = "LINKS"
98
- id = "your-kv-namespace-id-here"
67
+ pnpm exec wrangler login
68
+ pnpm deploy
99
69
  ```
100
70
 
101
- Replace `your-kv-namespace-id-here` with the ID from the KV namespace creation command.
71
+ The `LINKS` binding in `wrangler.toml` omits an account-specific namespace ID so Wrangler can provision it. To use an existing KV namespace, create it with `pnpm exec wrangler kv namespace create LINKS` and set its ID in `wrangler.toml` before deploying.
102
72
 
103
- #### Deploy the Worker
73
+ ### 3. Deploy the Frontend to Cloudflare Pages
104
74
 
105
- ```bash
106
- wrangler deploy
107
- ```
75
+ The `main` branch includes a sample page at `docs/index.html`. To create your own Pages site, connect your repository to Cloudflare Pages, set the production branch to the branch containing your homepage, leave the build command blank, and use `docs` as the output directory.
108
76
 
109
- ### 5. Update Configuration
77
+ This repository's Cloudflare Pages project publishes `docs/` from the `production` branch. The `main` branch remains the one-click Worker template. Set `frontend.url` to your Pages URL so the Worker can serve the hosted homepage at `/`.
110
78
 
111
- After deploying, update your `config/config.js` with the worker URL:
79
+ For custom error and interstitial pages, set `frontend.pagesBase` to the directory containing those files.
112
80
 
113
- ```javascript
114
- const config = {
115
- frontend: {
116
- url: "https://yourusername.github.io/shorten-url/",
117
- },
118
- // ... other settings
119
- };
120
- ```
81
+ The Worker returns short URLs using the request's origin, so configure its public domain in Cloudflare separately from `frontend.url`.
121
82
 
122
83
  ## ⚙️ Configuration Options
123
84
 
124
85
  ### Frontend Configuration
125
86
 
126
- | Option | Description | Default |
127
- | ------------------------ | --------------------------------------- | -------- |
128
- | `frontend.url` | URL where your frontend is hosted | Required |
129
- | `frontend.displayDomain` | Domain shown in UI (null = auto-detect) | `null` |
130
- | `frontend.theme` | UI theme selection | `""` |
87
+ | Option | Description | Default |
88
+ | --------------------- | --------------------------------------------------------------------------- | -------- |
89
+ | `frontend.url` | Static homepage URL fetched by the Worker for `GET /` | Required |
90
+ | `frontend.pagesBase` | Base URL for hosted error and interstitial pages | `url` |
91
+
92
+ The `frontend.displayDomain` and `frontend.theme` values in the configuration are not read by the current static page or Worker.
131
93
 
132
94
  ### Worker Configuration
133
95
 
134
- | Option | Description | Default |
135
- | ------------------------------- | --------------------------------- | ---------- |
136
- | `worker.no_ref` | Hide HTTP referrer header | `"off"` |
137
- | `worker.cors` | Enable CORS for API requests | `"on"` |
138
- | `worker.unique_link` | Same URL = same short link | `true` |
139
- | `worker.custom_link` | Allow custom slugs | `true` |
140
- | `worker.safe_browsing_api_key` | Google Safe Browsing API key | `""` |
141
- | `worker.min_random_key_length` | Minimum length for generated keys | `6` |
142
- | `worker.max_custom_slug_length` | Maximum length for custom slugs | `50` |
143
- | `worker.reserved_slugs` | List of reserved slugs | See config |
96
+ | Option | Description | Default |
97
+ | ------------------------------- | --------------------------------------------------------------- | ---------- |
98
+ | `worker.no_ref` | Use the hosted no-referrer interstitial before redirecting | `"off"` |
99
+ | `worker.cors` | Add wildcard CORS headers for API responses | `"on"` |
100
+ | `worker.unique_link` | Reuse a key when the same URL has already been shortened | `true` |
101
+ | `worker.custom_link` | Allow custom slugs | `true` |
102
+ | `SAFE_BROWSING_API_KEY` | Optional Google Safe Browsing API key stored as a Worker secret | Unset |
103
+ | `worker.min_random_key_length` | Minimum length for generated keys | `6` |
104
+ | `worker.random_chars` | Characters used for generated keys | See config |
105
+ | `worker.max_custom_slug_length` | Maximum length for custom slugs | `50` |
106
+ | `worker.reserved_slugs` | Slugs that cannot be used as custom links | See config |
144
107
 
145
108
  ### Security Configuration
146
109
 
147
- | Option | Description | Default |
148
- | -------------------------- | -------------------------- | ------- |
149
- | `security.rate_limit` | Requests per minute per IP | `10` |
150
- | `security.validate_urls` | Enable URL validation | `true` |
151
- | `security.blocked_domains` | List of blocked domains | `[]` |
110
+ | Option | Description | Default |
111
+ | ------------------------------------- | -------------------------------------------------------------------- | ------- |
112
+ | `security.rate_limit` | Not implemented by the Worker | Unused |
113
+ | `security.validate_urls` | URLs are always validated; this setting does not toggle validation | Unused |
114
+ | `security.blocked_domains` | Domain blocklists are not implemented by the Worker | Unused |
115
+ | `security.block_suspicious_domains` | Domain blocklists are not implemented by the Worker | Unused |
152
116
 
153
117
  ### Storage Configuration
154
118
 
155
- | Option | Description | Default |
156
- | ---------------------- | ------------------------- | --------- |
157
- | `storage.binding_name` | KV namespace binding name | `"LINKS"` |
119
+ | Option | Description | Default |
120
+ | ---------------------- | -------------------------------------------------------------------- | --------- |
121
+ | `storage.binding_name` | Worker reads KV from `env.LINKS`; this config value is not consulted | `"LINKS"` |
158
122
 
159
123
  ## 🔧 Advanced Setup
160
124
 
161
125
  ### Custom Domain
162
126
 
163
- 1. Add a custom domain in Cloudflare Workers dashboard
164
- 2. Update your DNS records to point to Cloudflare
165
- 3. Update the worker URL in your configuration
127
+ 1. Add a custom domain to the Cloudflare Pages project for the static site.
128
+ 2. Add a domain to the Cloudflare Worker separately for the shortening API.
129
+ 3. Set `frontend.url` to the Cloudflare Pages URL; Pages and Worker domains are configured independently.
166
130
 
167
131
  ### Google Safe Browsing
168
132
 
169
133
  1. Get an API key from [Google Cloud Console](https://developers.google.com/safe-browsing/v4/get-started)
170
- 2. Add the key to your `config.js`:
134
+ 2. Store it as a Worker secret:
171
135
 
172
- ```javascript
173
- worker: {
174
- safe_browsing_api_key: "your-api-key-here",
175
- }
136
+ ```bash
137
+ pnpm exec wrangler secret put SAFE_BROWSING_API_KEY
176
138
  ```
177
139
 
140
+ The Worker reads this secret at runtime. The key is not stored in `config/config.js`.
141
+
178
142
  ### Analytics Integration
179
143
 
180
- Configure analytics in your `config.js`:
181
-
182
- ```javascript
183
- analytics: {
184
- enable_tracking: true,
185
- ga_tracking_id: "G-XXXXXXXXXX", // Google Analytics 4
186
- analytics_endpoint: "https://your-analytics-endpoint.com"
187
- }
188
- ```
144
+ Click analytics is not implemented, and the `analytics` object in `config/config.js` is not used by the Worker. Cloudflare Worker logs and traces are enabled in `wrangler.toml`.
189
145
 
190
146
  ### Custom Error Pages
191
147
 
192
- The system fetches error and redirect pages from your GitHub Pages repository, so you can fully customize their content and design:
193
-
194
- - **404 Page**: <code>https://dytsou.github.io/404.html</code>
195
- - **Security Warning**: <code>https://dytsou.github.io/safe-browsing-warning.html</code>
196
- - **No-Referrer Redirect**: <code>https://dytsou.github.io/no-ref-page.html</code>
148
+ The Worker fetches optional error and interstitial pages from `frontend.pagesBase` (or `frontend.url` when `pagesBase` is not set). Add these files to your static host to customize them:
197
149
 
198
- You can edit these HTML files in your GitHub Pages repo to change the look, text, or behavior at any time. The worker will always serve the latest version from your site.
150
+ - **404 Page**: `404.html`
151
+ - **Error Page**: `error.html`
152
+ - **Security Warning**: `safe-browsing-warning.html`
153
+ - **No-Referrer Redirect**: `no-ref-page.html`
199
154
 
200
- All pages feature responsive design with glassmorphism effects matching your main interface.
155
+ These files are not included in this repository. The Worker fetches their current contents from your static host when needed.
201
156
 
202
157
  ## 📖 API Documentation
203
158
 
204
- The project includes comprehensive API documentation available at `/api` endpoint with interactive Swagger UI.
159
+ The API reference is a static Swagger UI published from `docs/api/`; it is not served by the Worker at `/api`.
205
160
 
206
161
  ### OpenAPI Specification
207
162
 
208
163
  - **Format**: OpenAPI 3.1.0
209
164
  - **Location**: [docs/api/openapi.yaml](docs/api/openapi.yaml)
210
- - **Interactive UI**: Available at `/api` endpoint on your deployed worker
165
+ - **Interactive UI**: [docs/api/index.html](docs/api/index.html), published at `/api/` under the Pages base URL
211
166
  - **Offline Access**: [docs/api/index.html](docs/api/index.html) for local viewing
212
167
 
213
168
  ### Shorten URL
214
169
 
215
- **POST** `/shorten`
170
+ **POST** `/`
216
171
 
217
172
  ```json
218
173
  {
219
174
  "url": "https://example.com/very-long-url",
220
- "custom_slug": "my-link" // optional
175
+ "custom_slug": "my-link"
221
176
  }
222
177
  ```
223
178
 
224
- **Response:**
179
+ `custom_slug` is optional. The default Worker template requires Cloudflare Access headers before creating a link.
180
+
181
+ **Response (201 Created):**
225
182
 
226
183
  ```json
227
184
  {
228
- "short_url": "https://url.tsou.me/abc123"
185
+ "short_url": "https://your-worker-domain.example/abc123"
229
186
  }
230
187
  ```
231
188
 
@@ -242,11 +199,12 @@ The project includes comprehensive API documentation available at `/api` endpoin
242
199
 
243
200
  **GET** `/{key}`
244
201
 
245
- Redirects to the original URL or shows appropriate error page.
202
+ Redirects to the stored destination with HTTP 302. Query parameters from the short URL are appended to the destination.
246
203
 
247
204
  ### Additional Endpoints
248
205
 
249
- - **GET** `/api` - Interactive API documentation (Swagger UI)
206
+ - **GET** `/` - Fetches the configured static homepage
207
+ - **OPTIONS** - Returns a CORS preflight response
250
208
 
251
209
  ## 🛠️ Development
252
210
 
@@ -254,45 +212,46 @@ Redirects to the original URL or shows appropriate error page.
254
212
 
255
213
  ```bash
256
214
  # Start local development server (see wrangler.toml for port / host)
257
- wrangler dev
215
+ pnpm dev
258
216
 
259
- # Test with curl (example; in production, requests must pass through Cloudflare Access/WARP)
260
- curl -X POST http://localhost:8787/shorten \
217
+ # Local-only example. The handler checks for both headers; Cloudflare Access must
218
+ # authenticate production requests before they reach the Worker.
219
+ curl -X POST http://localhost:8787/ \
261
220
  -H "Content-Type: application/json" \
221
+ -H "Cf-Access-Jwt-Assertion: local-test" \
222
+ -H "Cf-Access-Authenticated-User-Email: you@example.com" \
262
223
  -d '{"url": "https://example.com"}'
263
224
  ```
264
225
 
226
+ The Worker imports `config/config.js` directly. Run `pnpm test` to execute the Worker tests.
227
+
265
228
  ### File Structure
266
229
 
267
230
  ```
268
231
  shorten-url/
269
232
  ├── src/
270
- │ ├── worker.js # Production Cloudflare Worker
271
- │ ├── worker.dev.js # Development Cloudflare Worker
272
- │ └── worker.example.js # Worker template
233
+ │ ├── lib/ # Validation, redirects, KV, responses, and observability
234
+ │ └── worker.js # Worker entrypoint
273
235
  ├── docs/
274
- │ ├── index.html # Homepage interface
236
+ │ ├── index.html # Sample homepage (POST /)
275
237
  │ └── api/
276
238
  │ ├── index.html # Swagger UI for API docs
277
239
  │ └── openapi.yaml # OpenAPI 3.1.0 specification
278
240
  ├── config/
279
- │ ├── config.js # Configuration (not in git)
280
- │ └── config.example.js # Configuration template
281
- ├── .cursor/ # Cursor IDE rules and workflow
241
+ │ └── config.js # Worker configuration
242
+ ├── wrangler.toml # Wrangler configuration
282
243
  ├── .github/ # GitHub workflows and templates
283
- ├── .gitignore # Git ignore rules
284
- ├── README.md # This file
285
- └── wrangler.toml # Wrangler configuration
244
+ ├── .gitignore # Ignores local-only files
245
+ └── README.md # This file
286
246
  ```
287
247
 
288
248
  ## Security Considerations
289
249
 
290
- - Keep your `config.js` file private (it's in `.gitignore`)
291
- - Use environment variables for sensitive data in production
292
- - Enable rate limiting to prevent abuse
293
- - Consider enabling Google Safe Browsing for malicious URL detection
250
+ - Keep API keys out of version control; store them with Wrangler secrets
251
+ - Configure a Cloudflare Access policy in front of the Worker; the handler checks for Access headers but does not verify JWT signatures itself
252
+ - The Worker has no built-in per-IP rate limit or blocked-domain list
253
+ - Consider enabling Google Safe Browsing for redirect destinations
294
254
  - Regularly monitor your KV storage usage
295
- - Configure Cloudflare Access / WARP correctly, as URL shortening is intended to be available only to authenticated/requesters you control
296
255
 
297
256
  ## Troubleshooting
298
257
 
@@ -305,7 +264,8 @@ shorten-url/
305
264
 
306
265
  **Worker deployment fails:**
307
266
 
308
- - Check your `wrangler.toml` configuration
267
+ - Confirm `src/worker.js` and `config/config.js` are present
268
+ - Ensure the `LINKS` KV namespace ID in `wrangler.toml` is valid
309
269
  - Ensure KV namespace ID is correct
310
270
  - Verify you're logged into the correct Cloudflare account
311
271
 
@@ -317,13 +277,13 @@ shorten-url/
317
277
 
318
278
  **Frontend not loading:**
319
279
 
320
- - Update `frontend.url` in your config to match your hosting URL
321
- - Ensure CORS is enabled in worker configuration
280
+ - Set `frontend.url` to the hosted static homepage URL
281
+ - If the form returns an invalid-path response, check that its POST path matches the Worker route (`/` for the template)
282
+ - Enable CORS if the frontend and Worker use different origins
322
283
 
323
284
  **Rate limiting issues:**
324
285
 
325
- - Adjust `security.rate_limit` in your configuration
326
- - Consider implementing IP allowlisting for trusted sources
286
+ - The Worker does not currently rate-limit requests. Protect shortening with Cloudflare Access or add a rate limiter.
327
287
 
328
288
  ## License
329
289
 
@@ -347,12 +307,13 @@ If you encounter any issues or have questions:
347
307
 
348
308
  ## Deployment Checklist
349
309
 
350
- - [ ] Updated `config.js` with your settings
351
- - [ ] Deployed frontend to GitHub Pages or custom hosting
310
+ - [ ] Set `frontend.url` and the `LINKS` KV namespace in the deployment configuration
311
+ - [ ] Deployed `docs/` to Cloudflare Pages
352
312
  - [ ] Created Cloudflare KV namespace
353
313
  - [ ] Configured `wrangler.toml` with correct KV namespace ID
354
- - [ ] Deployed worker using `wrangler deploy`
355
- - [ ] Tested URL shortening functionality
314
+ - [ ] Protected the Worker with a Cloudflare Access policy
315
+ - [ ] Deployed the Worker using `pnpm deploy`
316
+ - [ ] Tested URL shortening at `POST /`
356
317
  - [ ] Verified copy-to-clipboard feature works
357
318
  - [ ] Tested custom error pages (404, security warnings)
358
319
  - [ ] (Optional) Configured custom domain
@@ -1,17 +1,16 @@
1
1
  /**
2
- * URL Shortener Configuration Example
2
+ * URL Shortener deployment configuration
3
3
  *
4
- * Copy this file to config.js and modify the values according to your setup.
5
- * This file should be committed to your repository as a template.
6
- * The actual config.js file should be added to .gitignore to keep your secrets safe.
4
+ * Set frontend.url to the public static homepage that the Worker should serve.
5
+ * Keep private credentials out of this tracked file.
7
6
  */
8
7
 
9
8
  const config = {
10
9
  // Frontend Configuration
11
10
  frontend: {
12
11
  // URL where your frontend is hosted (used by worker to serve the main page)
13
- // REQUIRED: Change this to your GitHub Pages URL or custom domain
14
- url: "https://yourusername.github.io/your-repo-name/",
12
+ // REQUIRED: Change this to your Cloudflare Pages URL or custom domain
13
+ url: "https://your-project.pages.dev/",
15
14
 
16
15
  // Domain displayed in the custom slug input prefix
17
16
  // If null, will use the current domain automatically
@@ -107,12 +106,4 @@ const config = {
107
106
  },
108
107
  };
109
108
 
110
- // Export for use in other files
111
- if (typeof module !== "undefined" && module.exports) {
112
- module.exports = config;
113
- }
114
-
115
- // Make available globally for browser usage
116
- if (typeof window !== "undefined") {
117
- window.APP_CONFIG = config;
118
- }
109
+ export default config;
@@ -23,7 +23,7 @@
23
23
  <script>
24
24
  window.onload = () => {
25
25
  SwaggerUIBundle({
26
- url: "openapi.yaml",
26
+ url: "/api/openapi.yaml",
27
27
  dom_id: "#swagger-ui",
28
28
  presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
29
29
  layout: "BaseLayout",
@@ -5,16 +5,16 @@ info:
5
5
  description: |
6
6
  A modern, fast URL shortener built with Cloudflare Workers and a responsive web interface.
7
7
  - Custom slugs are supported
8
- - The homepage is served at /home
8
+ - The homepage is served at /
9
9
  - API is available at / (POST) for shortening URLs
10
10
  - Short URL access is via /{key} (GET)
11
11
  - Custom error pages for 404, security warnings, and redirects
12
12
 
13
13
  servers:
14
- - url: https://url.tsou.me
14
+ - url: /
15
15
 
16
16
  paths:
17
- /shorten:
17
+ /:
18
18
  post:
19
19
  summary: Shorten a URL
20
20
  description: Create a new shortened URL, optionally with a custom slug.
@@ -49,7 +49,7 @@ paths:
49
49
  short_url:
50
50
  type: string
51
51
  format: uri
52
- example: https://url.tsou.me/abc123
52
+ example: https://your-worker-domain.example/abc123
53
53
  "400":
54
54
  description: Bad request
55
55
  content:
@@ -63,6 +63,18 @@ paths:
63
63
  message:
64
64
  type: string
65
65
  example: Invalid URL format
66
+ "403":
67
+ description: Cloudflare Access headers are missing
68
+ content:
69
+ application/json:
70
+ schema:
71
+ $ref: "#/components/schemas/ErrorResponse"
72
+ "404":
73
+ description: Invalid API path
74
+ content:
75
+ application/json:
76
+ schema:
77
+ $ref: "#/components/schemas/ErrorResponse"
66
78
  "500":
67
79
  description: Server error
68
80
  content:
@@ -76,6 +88,21 @@ paths:
76
88
  message:
77
89
  type: string
78
90
  example: Server error
91
+ get:
92
+ summary: Get homepage
93
+ description: Fetches the configured URL shortener frontend.
94
+ responses:
95
+ "200":
96
+ description: Homepage HTML
97
+ content:
98
+ text/html:
99
+ schema:
100
+ type: string
101
+ options:
102
+ summary: Handle CORS preflight
103
+ responses:
104
+ "204":
105
+ description: Preflight response
79
106
 
80
107
  /{key}:
81
108
  get:
@@ -104,19 +131,6 @@ paths:
104
131
  schema:
105
132
  type: string
106
133
 
107
- /home:
108
- get:
109
- summary: Get homepage
110
- description: Serves the URL shortener frontend
111
- responses:
112
- "200":
113
- description: Homepage HTML
114
- content:
115
- text/html:
116
- schema:
117
- type: string
118
- example: URL shortener frontend with form interface
119
-
120
134
  components:
121
135
  schemas:
122
136
  ShortenRequest:
package/docs/index.html CHANGED
@@ -529,7 +529,7 @@
529
529
  }
530
530
 
531
531
  // Make API call to your Cloudflare Worker
532
- const response = await fetch(window.location.origin + "/shorten", {
532
+ const response = await fetch(window.location.origin + "/", {
533
533
  method: "POST",
534
534
  headers: {
535
535
  "Content-Type": "application/json",
@@ -660,7 +660,7 @@
660
660
  }
661
661
 
662
662
  // Make API call to shorten URL
663
- const response = await fetch(window.location.origin + "/shorten", {
663
+ const response = await fetch(window.location.origin + "/", {
664
664
  method: "POST",
665
665
  headers: {
666
666
  "Content-Type": "application/json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dytsou/shorten-url",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "description": "A modern, fast URL shortener built with Cloudflare Workers and a responsive web interface",
5
5
  "type": "module",
6
6
  "main": "src/worker.js",
@@ -27,12 +27,11 @@
27
27
  "devDependencies": {
28
28
  "prettier": "^3.7.4",
29
29
  "vite": "7.3.6",
30
- "vitest": "^4.1.10",
30
+ "vitest": "^5.0.0",
31
31
  "wrangler": "^4.120.0"
32
32
  },
33
33
  "files": [
34
34
  "src/worker.js",
35
- "src/worker.example.js",
36
35
  "src/lib/",
37
36
  "config/",
38
37
  "docs/",
@@ -1,7 +1,7 @@
1
1
  /**
2
- * URL Shortener — Cloudflare Worker (template)
2
+ * URL Shortener - Cloudflare Worker
3
3
  *
4
- * Copy this file to `src/worker.js` and wire in your config (see README).
4
+ * Configuration is imported from `config/config.js`.
5
5
  * Requires a KV namespace bound as `LINKS`.
6
6
  *
7
7
  * Routes:
@@ -14,33 +14,20 @@
14
14
  import { createShortener } from "./lib/shortener.js";
15
15
  import { endpointsFromFrontend } from "./lib/endpoints.js";
16
16
  import { withFetchObservability } from "./lib/observability.js";
17
-
18
- // ============================================================================
19
- // Configuration
20
- // ============================================================================
21
-
22
- let config;
23
-
24
- try {
25
- if (typeof importConfig !== "undefined") {
26
- config = { ...defaultConfig, ...importConfig };
27
- } else {
28
- throw new TypeError(
29
- "Worker config is missing. Copy config/config.example.js to config/config.js and import it when building worker.js."
30
- );
31
- }
32
- } catch (error) {
33
- console.error("Failed to load worker config:", error);
34
- throw error;
35
- }
17
+ import config from "../config/config.js";
36
18
 
37
19
  function getEndpoints() {
38
20
  return endpointsFromFrontend(config.frontend);
39
21
  }
40
22
 
41
- async function exampleFetchHandler(request, env) {
23
+ async function fetchHandler(request, env) {
24
+ const workerConfig = {
25
+ ...config.worker,
26
+ safe_browsing_api_key:
27
+ env.SAFE_BROWSING_API_KEY ?? config.worker.safe_browsing_api_key,
28
+ };
42
29
  const shortener = createShortener({
43
- worker: config.worker,
30
+ worker: workerConfig,
44
31
  endpoints: getEndpoints(),
45
32
  kv: env.LINKS,
46
33
  });
@@ -52,6 +39,23 @@ async function exampleFetchHandler(request, env) {
52
39
  if (request.method === "OPTIONS") {
53
40
  return new Response(null, { status: 204, headers: shortener.htmlHeaders() });
54
41
  }
42
+ if (
43
+ (request.method === "GET" || request.method === "HEAD") &&
44
+ (requestURL.pathname === "/api" || requestURL.pathname.startsWith("/api/"))
45
+ ) {
46
+ if (
47
+ requestURL.pathname !== "/api/" &&
48
+ /^\/api(?:\/api)*\/?$/.test(requestURL.pathname)
49
+ ) {
50
+ return Response.redirect(new URL("/api/", requestURL), 308);
51
+ }
52
+
53
+ const assetURL = new URL(
54
+ `${requestURL.pathname.slice(1)}${requestURL.search}`,
55
+ config.frontend.url,
56
+ );
57
+ return fetch(new Request(assetURL, { method: request.method }));
58
+ }
55
59
  if (request.method === "POST") {
56
60
  return shortener.handleShorten(request, requestURL, {
57
61
  apiPath: requestURL.pathname,
@@ -62,4 +66,4 @@ async function exampleFetchHandler(request, env) {
62
66
  return shortener.handleShortUrlRedirect(path, params);
63
67
  }
64
68
 
65
- export default withFetchObservability(exampleFetchHandler);
69
+ export default withFetchObservability(fetchHandler);