@dytsou/shorten-url 2.4.4 → 2.4.5

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/docs/about.md ADDED
@@ -0,0 +1,360 @@
1
+ # URL Shortener
2
+
3
+ A URL shortener built with Cloudflare Workers, Cloudflare KV, and a responsive React interface. The Worker serves the product UI and redirects, while `docs/` contains the documentation 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)
7
+
8
+ ## Features
9
+
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**: Worker-hosted React interface supports desktop and mobile screens
14
+ - **Flagship Controls**: Shorten and protected Flagship views share one interface
15
+ - **Unique Links**: Optionally reuse the same short key when the destination URL already exists
16
+ - **URL Safety**: Validate submitted URLs and optionally check redirect destinations with Google Safe Browsing
17
+ - **Copy Support**: Copy shortened URLs with a manual-selection fallback
18
+ - **Hosted Error Pages**: Configure external 404, error, Safe Browsing warning, and no-referrer pages
19
+ - **API Documentation**: Static Swagger UI with an OpenAPI 3.1 specification
20
+ - **Worker Observability**: Structured logs and traces are supported
21
+
22
+ 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.
23
+
24
+ ## Quick Start
25
+
26
+ ### Worker-hosted React frontend
27
+
28
+ The product UI is React source in `frontend/`, compiled to `frontend/dist`, and served by the Worker through its `ASSETS` binding. Worker routing and APIs remain in `src/`; the API reference from `docs/api/` is bundled into Worker assets at `/api/` and `/api/openapi.yaml`. The separate Cloudflare Pages site can publish the documentation landing page from `docs/`; `docs/index.html` is not a product asset.
29
+
30
+ For a clean local setup, install both declared packages and use the root commands:
31
+
32
+ ```bash
33
+ pnpm install --frozen-lockfile --ignore-scripts
34
+ pnpm --dir frontend install --frozen-lockfile --ignore-scripts
35
+ cp wrangler.toml.example wrangler.toml
36
+ # Set a real LINKS namespace and production-only Access values in wrangler.toml or secrets.
37
+ pnpm build
38
+ pnpm dev
39
+ ```
40
+
41
+ `pnpm dev`, `pnpm preview`, and `pnpm deploy` build `frontend/dist` before running Wrangler. The Worker serves the UI at `GET /`. Only the `production` branch mounts its product page at `/shorten`; other branch previews stay at `/`. Regardless of the page path, the UI sends API requests to `POST /` and `/settings/api/*`. Standalone settings pages are not exposed.
42
+
43
+ Production must use an Access-protected custom hostname, set `workers_dev = false`, and set `ACCESS_ALLOWED_HOSTS` to that hostname. Keep `CLOUDFLARE_API_TOKEN`, `FLAGSHIP_CSRF_SECRET`, and provider credentials in Cloudflare/GitHub secrets, never Vite metadata or tracked configuration. Optional `FRONTEND_URL` and `FRONTEND_PAGES_BASE` retain legacy error/interstitial pages only; they are not used to serve the normal UI.
44
+
45
+ The Worker deployment workflow reads the non-secret GitHub variables
46
+ `LINKS_KV_NAMESPACE_ID` and `ACCESS_ALLOWED_HOSTS`, plus the
47
+ `CLOUDFLARE_API_TOKEN` secret, to create its ignored `wrangler.toml` at build
48
+ time.
49
+
50
+ ### Prerequisites
51
+
52
+ - A Cloudflare account with Workers and Pages; the Deploy to Cloudflare button provisions the required KV namespace
53
+ - A GitHub or GitLab account to receive the repository created by the Deploy to Cloudflare button
54
+ - A Cloudflare Pages project to publish `docs/`
55
+ - Node.js 24 or newer and pnpm 10.22.0 for manual deployment
56
+
57
+ ### 1. Deploy with Cloudflare (Recommended)
58
+
59
+ 1. Click the **Deploy to Cloudflare** button near the top of this README.
60
+ 2. Connect your Cloudflare and GitHub or GitLab accounts, then choose the destination repository and Worker name.
61
+ 3. Confirm the `LINKS` KV binding. Cloudflare provisions the namespace and deploys the Worker from the `main` branch.
62
+
63
+ The button deploys the Worker; it does not publish `docs/`. Connect the created repository to Cloudflare Pages, leave the build command blank, and set the output directory to `docs`. The React product UI is built into `frontend/dist` and served by the Worker through `ASSETS`; use the Worker deployment workflow or `pnpm deploy` to build and deploy those assets.
64
+
65
+ ### 2. Alternative: Manual Worker Deployment
66
+
67
+ Clone the repository and install the pinned Wrangler version:
68
+
69
+ ```bash
70
+ git clone https://github.com/dytsou/shorten-url.git
71
+ cd shorten-url
72
+ corepack enable
73
+ pnpm install
74
+ ```
75
+
76
+ The Worker serves the product UI from `ASSETS`. To deploy manually, install both packages, create the local Wrangler file from the tracked example, set the `LINKS` namespace ID and Access hostname, then build and deploy:
77
+
78
+ ```bash
79
+ pnpm install --frozen-lockfile --ignore-scripts
80
+ pnpm --dir frontend install --frozen-lockfile --ignore-scripts
81
+ cp wrangler.toml.example wrangler.toml
82
+ # Set the LINKS namespace ID and ACCESS_ALLOWED_HOSTS.
83
+ pnpm build
84
+ pnpm exec wrangler login
85
+ pnpm deploy
86
+ ```
87
+
88
+ 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.
89
+
90
+ ### 3. Deploy Documentation to Cloudflare Pages
91
+
92
+ The `docs/` directory contains the API reference and documentation. To create a Pages site, connect your repository, set the production branch to the branch containing your documentation, leave the build command blank, and use `docs` as the output directory.
93
+
94
+ This repository's Cloudflare Pages project publishes `docs/` from the `production` branch. The product UI is served by the Worker and does not depend on the Pages site.
95
+
96
+ For custom error and interstitial pages, set `frontend.pagesBase` to the directory containing those files.
97
+
98
+ The Worker returns short URLs using the request's origin, so configure its public domain in Cloudflare separately from `frontend.url`.
99
+
100
+ ## ⚙️ Configuration Options
101
+
102
+ ### Frontend Configuration
103
+
104
+ | Option | Description | Default |
105
+ | ------------------------ | ------------------------------------------------------- | ------- |
106
+ | `frontend.url` | Optional origin for legacy error and interstitial pages | `""` |
107
+ | `frontend.pagesBase` | Base URL for hosted error and interstitial pages | `url` |
108
+ | `frontend.workerOrigin` | Public Worker origin for a separately hosted frontend | `""` |
109
+ | `frontend.displayDomain` | Domain shown in UI (null = auto-detect) | `null` |
110
+ | `frontend.theme` | UI theme selection | `""` |
111
+
112
+ ### Worker Configuration
113
+
114
+ | Option | Description | Default |
115
+ | ------------------------------- | --------------------------------------------------------------- | ---------- |
116
+ | `worker.no_ref` | Use the hosted no-referrer interstitial before redirecting | `"off"` |
117
+ | `worker.cors` | Add wildcard CORS headers for API responses | `"on"` |
118
+ | `worker.unique_link` | Reuse a key when the same URL has already been shortened | `true` |
119
+ | `worker.custom_link` | Allow custom slugs | `true` |
120
+ | `SAFE_BROWSING_API_KEY` | Optional Google Safe Browsing API key stored as a Worker secret | Unset |
121
+ | `worker.min_random_key_length` | Minimum length for generated keys | `6` |
122
+ | `worker.random_chars` | Characters used for generated keys | See config |
123
+ | `worker.max_custom_slug_length` | Maximum length for custom slugs | `50` |
124
+ | `worker.reserved_slugs` | Slugs that cannot be used as custom links | See config |
125
+
126
+ ### Security Configuration
127
+
128
+ | Option | Description | Default |
129
+ | ----------------------------------- | ------------------------------------------------------------------ | ------- |
130
+ | `security.rate_limit` | Not implemented by the Worker | Unused |
131
+ | `security.validate_urls` | URLs are always validated; this setting does not toggle validation | Unused |
132
+ | `security.blocked_domains` | Domain blocklists are not implemented by the Worker | Unused |
133
+ | `security.block_suspicious_domains` | Domain blocklists are not implemented by the Worker | Unused |
134
+
135
+ ### Storage Configuration
136
+
137
+ | Option | Description | Default |
138
+ | ---------------------- | -------------------------------------------------------------------- | --------- |
139
+ | `storage.binding_name` | Worker reads KV from `env.LINKS`; this config value is not consulted | `"LINKS"` |
140
+
141
+ ## 🔧 Advanced Setup
142
+
143
+ ### Custom Domain
144
+
145
+ 1. Add a custom domain to the Cloudflare Worker and protect it with Cloudflare Access.
146
+ 2. Add a custom domain to the Cloudflare Pages project for documentation.
147
+ 3. Set `frontend.url` only when using separately hosted legacy error or interstitial pages.
148
+
149
+ ### Google Safe Browsing
150
+
151
+ 1. Get an API key from [Google Cloud Console](https://developers.google.com/safe-browsing/v4/get-started)
152
+ 2. Store it as a Worker secret:
153
+
154
+ ```bash
155
+ pnpm exec wrangler secret put SAFE_BROWSING_API_KEY
156
+ ```
157
+
158
+ The Worker reads this secret at runtime. The key is not stored in `config/config.js`.
159
+
160
+ ### Analytics Integration
161
+
162
+ 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`.
163
+
164
+ ### Custom Error Pages
165
+
166
+ 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:
167
+
168
+ - **404 Page**: `404.html`
169
+ - **Error Page**: `error.html`
170
+ - **Security Warning**: `safe-browsing-warning.html`
171
+ - **No-Referrer Redirect**: `no-ref-page.html`
172
+
173
+ These files are not included in this repository. The Worker fetches their current contents from your static host when needed.
174
+
175
+ ## 📖 API Documentation
176
+
177
+ The API reference is a static Swagger UI built from `docs/api/` and bundled into Worker assets. `/api` redirects to `/api/`, where the interactive reference is served.
178
+
179
+ ### OpenAPI Specification
180
+
181
+ - **Format**: OpenAPI 3.1.0
182
+ - **Location**: [docs/api/openapi.yaml](/api/openapi.yaml)
183
+ - **Interactive UI**: [docs/api/index.html](/api/), bundled into Worker assets and served at `/api/`
184
+ - **Offline Access**: [docs/api/index.html](/api/) for local viewing
185
+
186
+ ### Shorten URL
187
+
188
+ **POST** `/`
189
+
190
+ ```json
191
+ {
192
+ "url": "https://example.com/very-long-url",
193
+ "custom_slug": "my-link"
194
+ }
195
+ ```
196
+
197
+ The endpoint stays at the origin root even when the production page is mounted at `/shorten`. `custom_slug` is optional. The default Worker template requires Cloudflare Access headers before creating a link.
198
+
199
+ **Response (201 Created):**
200
+
201
+ ```json
202
+ {
203
+ "short_url": "https://your-worker-domain.example/abc123"
204
+ }
205
+ ```
206
+
207
+ **Error Response:**
208
+
209
+ ```json
210
+ {
211
+ "status": 400,
212
+ "message": "Invalid URL format"
213
+ }
214
+ ```
215
+
216
+ ### Access Short URL
217
+
218
+ **GET** `/{key}`
219
+
220
+ Redirects to the stored destination with HTTP 302. Query parameters from the short URL are appended to the destination.
221
+
222
+ ### Additional Endpoints
223
+
224
+ - **GET** `/` - Serves the Worker-hosted React interface
225
+ - **OPTIONS** - Returns a CORS preflight response
226
+
227
+ ### Flagship Management API
228
+
229
+ The authenticated control plane manages the `shorten-routing` flag under `/settings/api`. Requests require Cloudflare Access authentication. Fetch a CSRF token before sending a write; POST and PUT requests must include that token in `X-CSRF-Token`, use `application/json`, and come from the same origin as the UI.
230
+
231
+ | Method | Path | Purpose |
232
+ | ------ | -------------------------------------------- | --------------------------------------------------------- |
233
+ | `GET` | `/settings/api/csrf` | Get a CSRF token for the signed-in Access user and origin |
234
+ | `GET` | `/settings/api/flags` | Read the saved shortening flag |
235
+ | `POST` | `/settings/api/flag` | Create the `shorten-routing` flag |
236
+ | `PUT` | `/settings/api/flag/shorten-routing` | Save a draft with `expectedUpdatedAt` concurrency control |
237
+ | `POST` | `/settings/api/flag/shorten-routing/publish` | Publish the saved version |
238
+
239
+ Targeting rules are evaluated by ascending priority. Conditions in a rule are combined with AND and can target country, region, continent, timezone, browser language, or UTM source, medium, and campaign. Supported operators are `equals`, `not_equals`, `contains`, `starts_with`, `ends_with`, `in`, and `not_in`. A rule can also set `rolloutPercentage` from 0 to 100; the Worker uses a stable visitor key for the canary assignment. See the OpenAPI schema for request and response shapes.
240
+
241
+ ## 🛠️ Development
242
+
243
+ ### Local Development
244
+
245
+ ```bash
246
+ # Start local development server (see wrangler.toml for port / host)
247
+ pnpm dev
248
+
249
+ # Local-only example. The handler checks for both headers; Cloudflare Access must
250
+ # authenticate production requests before they reach the Worker.
251
+ curl -X POST http://localhost:8787/ \
252
+ -H "Content-Type: application/json" \
253
+ -H "Cf-Access-Jwt-Assertion: local-test" \
254
+ -H "Cf-Access-Authenticated-User-Email: you@example.com" \
255
+ -d '{"url": "https://example.com"}'
256
+ ```
257
+
258
+ The Worker combines built-in defaults with non-secret Wrangler variables; keep credentials in Wrangler secrets. Run `pnpm test` to execute the Worker tests.
259
+
260
+ ### File Structure
261
+
262
+ ```
263
+ shorten-url/
264
+ ├── src/
265
+ │ ├── lib/ # Validation, routes, KV, assets, and observability
266
+ │ ├── worker.js # Production Worker entrypoint
267
+ │ └── worker.example.js # Example Worker entrypoint
268
+ ├── frontend/ # React product UI, about-page template, and build configuration
269
+ ├── docs/
270
+ │ ├── index.html # Documentation site homepage
271
+ │ ├── about.md # Content rendered as the /about website page
272
+ │ └── api/
273
+ │ ├── index.html # Swagger UI for API docs
274
+ │ └── openapi.yaml # OpenAPI 3.1.0 specification
275
+ ├── config/
276
+ │ ├── config.js # Existing deployment template
277
+ │ └── config.example.js # Configuration example
278
+ ├── wrangler.toml.example # Wrangler deployment template
279
+ ├── .github/ # GitHub workflows and templates
280
+ ├── .gitignore # Ignores local-only files
281
+ └── README.md # This file
282
+ ```
283
+
284
+ ## Security Considerations
285
+
286
+ - Keep API keys out of version control; store them with Wrangler secrets
287
+ - Configure a Cloudflare Access policy in front of the Worker; the handler checks for Access headers but does not verify JWT signatures itself
288
+ - The Worker has no built-in per-IP rate limit or blocked-domain list
289
+ - Consider enabling Google Safe Browsing for redirect destinations
290
+ - Regularly monitor your KV storage usage
291
+
292
+ <a id="troubleshooting"></a>
293
+
294
+ ## Troubleshooting
295
+
296
+ ### Common Issues
297
+
298
+ **"Failed to copy" error:**
299
+
300
+ - The app uses modern clipboard API with fallbacks
301
+ - Ensure you're using HTTPS (required for clipboard access)
302
+
303
+ **Worker deployment fails:**
304
+
305
+ - Confirm `src/worker.js`, `wrangler.toml.example`, and `frontend/` are present
306
+ - Build the frontend so `frontend/dist/index.html` exists
307
+ - Ensure the `LINKS` KV namespace ID in `wrangler.toml` is valid
308
+ - Ensure KV namespace ID is correct
309
+ - Verify you're logged into the correct Cloudflare account
310
+
311
+ **Custom slugs not working:**
312
+
313
+ - Check that `custom_link` is enabled in config
314
+ - Verify slug meets validation requirements (alphanumeric, hyphens, underscores only)
315
+ - Ensure slug is not in the reserved slugs list
316
+
317
+ **Frontend not loading:**
318
+
319
+ - Check that the Worker has an `ASSETS` binding and `frontend/dist/index.html` was built
320
+ - If shortening returns an invalid-path response, check that the frontend POST path is `/`
321
+ - Enable CORS if the frontend and Worker use different origins
322
+
323
+ **Rate limiting issues:**
324
+
325
+ - The Worker does not currently rate-limit requests. Protect shortening with Cloudflare Access or add a rate limiter.
326
+
327
+ ## License
328
+
329
+ This project is licensed under the MIT License - see the [LICENSE](https://github.com/dytsou/shorten-url/blob/main/LICENSE) file for details.
330
+
331
+ ## Contributing
332
+
333
+ 1. Fork the repository
334
+ 2. Create a feature branch (`git checkout -b feature/amazing-feature`)
335
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
336
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
337
+ 5. Open a Pull Request
338
+
339
+ ## Support
340
+
341
+ If you encounter any issues or have questions:
342
+
343
+ 1. Check the [troubleshooting section](#troubleshooting)
344
+ 2. Search existing issues on GitHub
345
+ 3. Create a new issue with detailed information
346
+
347
+ ## Deployment Checklist
348
+
349
+ - [ ] Set `LINKS_KV_NAMESPACE_ID` and `ACCESS_ALLOWED_HOSTS` for Worker deployment
350
+ - [ ] Build the React frontend into `frontend/dist`
351
+ - [ ] Deployed `docs/` to Cloudflare Pages
352
+ - [ ] Created Cloudflare KV namespace
353
+ - [ ] Configured `wrangler.toml` with correct KV namespace ID
354
+ - [ ] Protected the Worker with a Cloudflare Access policy
355
+ - [ ] Deployed the Worker using `pnpm deploy`
356
+ - [ ] Tested URL shortening at `POST /`
357
+ - [ ] Verified copy-to-clipboard feature works
358
+ - [ ] Tested custom error pages (404, security warnings)
359
+ - [ ] (Optional) Configured custom domain
360
+ - [ ] (Optional) Added Google Safe Browsing API key