@dytsou/shorten-url 2.4.3 → 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/README.md +165 -166
- package/config/config.example.js +22 -3
- package/config/config.js +128 -0
- package/docs/about.md +360 -0
- package/docs/api/index.html +1 -1
- package/docs/api/openapi.yaml +449 -75
- package/docs/flagship.md +58 -0
- package/docs/index.html +2 -2
- package/docs/plans/2026-09-06-001-refactor-worker-hosted-react-assets-plan.md +308 -0
- package/frontend/about-template.html +124 -0
- package/frontend/index.html +17 -0
- package/frontend/package.json +20 -0
- package/frontend/pnpm-lock.yaml +1129 -0
- package/frontend/src/App.jsx +1264 -0
- package/frontend/src/api.js +272 -0
- package/frontend/src/local-flag-mock.js +55 -0
- package/frontend/src/main.jsx +4 -0
- package/frontend/src/styles/app.css +2253 -0
- package/frontend/static/favicon.svg +5 -0
- package/frontend/vite.config.js +34 -0
- package/package.json +7 -6
- package/src/lib/access.js +241 -1
- package/src/lib/assets.js +58 -0
- package/src/lib/endpoints.js +33 -8
- package/src/lib/feature-toggle.js +464 -0
- package/src/lib/flag-management.js +137 -0
- package/src/lib/flag-routes.js +155 -0
- package/src/lib/flagship.js +269 -0
- package/src/lib/observability.js +34 -0
- package/src/lib/responses.js +39 -3
- package/src/lib/runtime-config.js +90 -0
- package/src/lib/safety.js +15 -0
- package/src/lib/shortener.js +2 -2
- package/src/lib/worker-handler.js +122 -0
- package/src/worker.js +6 -0
- package/src/worker.example.js +0 -65
package/README.md
CHANGED
|
@@ -1,231 +1,206 @@
|
|
|
1
1
|
# URL Shortener
|
|
2
2
|
|
|
3
|
-
A
|
|
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
|
+
[](https://cloudflare.com)
|
|
6
|
+
[](https://deploy.workers.cloudflare.com/?url=https://github.com/dytsou/shorten-url)
|
|
4
7
|
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
|
-
- **URL Shortening**:
|
|
8
|
-
- **Custom Slugs**:
|
|
9
|
-
- **
|
|
10
|
-
- **Responsive Design**:
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
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.
|
|
17
23
|
|
|
18
24
|
## Quick Start
|
|
19
25
|
|
|
20
|
-
###
|
|
26
|
+
### Worker-hosted React frontend
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
- GitHub account for hosting the frontend (or any static hosting)
|
|
24
|
-
- Basic knowledge of Git and command line
|
|
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.
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
For a clean local setup, install both declared packages and use the root commands:
|
|
27
31
|
|
|
28
32
|
```bash
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
31
39
|
```
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
# Copy the example configuration
|
|
37
|
-
cp config/config.example.js config/config.js
|
|
38
|
-
|
|
39
|
-
# Edit the configuration file
|
|
40
|
-
vim config/config.js
|
|
41
|
-
```
|
|
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
42
|
|
|
43
|
-
|
|
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
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
url: "https://yourusername.github.io/shorten-url/",
|
|
50
|
-
},
|
|
51
|
-
// ... other settings
|
|
52
|
-
};
|
|
53
|
-
```
|
|
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.
|
|
54
49
|
|
|
55
|
-
###
|
|
50
|
+
### Prerequisites
|
|
56
51
|
|
|
57
|
-
|
|
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
|
|
58
56
|
|
|
59
|
-
1.
|
|
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/`
|
|
57
|
+
### 1. Deploy with Cloudflare (Recommended)
|
|
63
58
|
|
|
64
|
-
|
|
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.
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
2. Update the `frontend.url` in `config/config.js` to match your domain
|
|
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.
|
|
68
64
|
|
|
69
|
-
###
|
|
65
|
+
### 2. Alternative: Manual Worker Deployment
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
Clone the repository and install the pinned Wrangler version:
|
|
72
68
|
|
|
73
69
|
```bash
|
|
74
|
-
|
|
70
|
+
git clone https://github.com/dytsou/shorten-url.git
|
|
71
|
+
cd shorten-url
|
|
72
|
+
corepack enable
|
|
73
|
+
pnpm install
|
|
75
74
|
```
|
|
76
75
|
|
|
77
|
-
|
|
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:
|
|
78
77
|
|
|
79
78
|
```bash
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
|
|
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
|
|
85
86
|
```
|
|
86
87
|
|
|
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.
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
### 3. Deploy Documentation to Cloudflare Pages
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
name = "url-shortener"
|
|
93
|
-
main = "src/worker.js"
|
|
94
|
-
compatibility_date = "2024-01-01"
|
|
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.
|
|
95
93
|
|
|
96
|
-
|
|
97
|
-
binding = "LINKS"
|
|
98
|
-
id = "your-kv-namespace-id-here"
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Replace `your-kv-namespace-id-here` with the ID from the KV namespace creation command.
|
|
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.
|
|
102
95
|
|
|
103
|
-
|
|
96
|
+
For custom error and interstitial pages, set `frontend.pagesBase` to the directory containing those files.
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
wrangler deploy
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### 5. Update Configuration
|
|
110
|
-
|
|
111
|
-
After deploying, update your `config/config.js` with the worker URL:
|
|
112
|
-
|
|
113
|
-
```javascript
|
|
114
|
-
const config = {
|
|
115
|
-
frontend: {
|
|
116
|
-
url: "https://yourusername.github.io/shorten-url/",
|
|
117
|
-
},
|
|
118
|
-
// ... other settings
|
|
119
|
-
};
|
|
120
|
-
```
|
|
98
|
+
The Worker returns short URLs using the request's origin, so configure its public domain in Cloudflare separately from `frontend.url`.
|
|
121
99
|
|
|
122
100
|
## ⚙️ Configuration Options
|
|
123
101
|
|
|
124
102
|
### Frontend Configuration
|
|
125
103
|
|
|
126
|
-
| Option | Description
|
|
127
|
-
| ------------------------ |
|
|
128
|
-
| `frontend.url` |
|
|
129
|
-
| `frontend.
|
|
130
|
-
| `frontend.
|
|
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 | `""` |
|
|
131
111
|
|
|
132
112
|
### Worker Configuration
|
|
133
113
|
|
|
134
|
-
| Option | Description
|
|
135
|
-
| ------------------------------- |
|
|
136
|
-
| `worker.no_ref` |
|
|
137
|
-
| `worker.cors` |
|
|
138
|
-
| `worker.unique_link` |
|
|
139
|
-
| `worker.custom_link` | Allow custom slugs
|
|
140
|
-
| `
|
|
141
|
-
| `worker.min_random_key_length` | Minimum length for generated keys
|
|
142
|
-
| `worker.
|
|
143
|
-
| `worker.
|
|
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 |
|
|
144
125
|
|
|
145
126
|
### Security Configuration
|
|
146
127
|
|
|
147
|
-
| Option
|
|
148
|
-
|
|
|
149
|
-
| `security.rate_limit`
|
|
150
|
-
| `security.validate_urls`
|
|
151
|
-
| `security.blocked_domains`
|
|
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 |
|
|
152
134
|
|
|
153
135
|
### Storage Configuration
|
|
154
136
|
|
|
155
|
-
| Option | Description
|
|
156
|
-
| ---------------------- |
|
|
157
|
-
| `storage.binding_name` | KV
|
|
137
|
+
| Option | Description | Default |
|
|
138
|
+
| ---------------------- | -------------------------------------------------------------------- | --------- |
|
|
139
|
+
| `storage.binding_name` | Worker reads KV from `env.LINKS`; this config value is not consulted | `"LINKS"` |
|
|
158
140
|
|
|
159
141
|
## 🔧 Advanced Setup
|
|
160
142
|
|
|
161
143
|
### Custom Domain
|
|
162
144
|
|
|
163
|
-
1. Add a custom domain
|
|
164
|
-
2.
|
|
165
|
-
3.
|
|
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.
|
|
166
148
|
|
|
167
149
|
### Google Safe Browsing
|
|
168
150
|
|
|
169
151
|
1. Get an API key from [Google Cloud Console](https://developers.google.com/safe-browsing/v4/get-started)
|
|
170
|
-
2.
|
|
152
|
+
2. Store it as a Worker secret:
|
|
171
153
|
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
safe_browsing_api_key: "your-api-key-here",
|
|
175
|
-
}
|
|
154
|
+
```bash
|
|
155
|
+
pnpm exec wrangler secret put SAFE_BROWSING_API_KEY
|
|
176
156
|
```
|
|
177
157
|
|
|
158
|
+
The Worker reads this secret at runtime. The key is not stored in `config/config.js`.
|
|
159
|
+
|
|
178
160
|
### Analytics Integration
|
|
179
161
|
|
|
180
|
-
|
|
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
|
-
```
|
|
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`.
|
|
189
163
|
|
|
190
164
|
### Custom Error Pages
|
|
191
165
|
|
|
192
|
-
The
|
|
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>
|
|
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:
|
|
197
167
|
|
|
198
|
-
|
|
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`
|
|
199
172
|
|
|
200
|
-
|
|
173
|
+
These files are not included in this repository. The Worker fetches their current contents from your static host when needed.
|
|
201
174
|
|
|
202
175
|
## 📖 API Documentation
|
|
203
176
|
|
|
204
|
-
The
|
|
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.
|
|
205
178
|
|
|
206
179
|
### OpenAPI Specification
|
|
207
180
|
|
|
208
181
|
- **Format**: OpenAPI 3.1.0
|
|
209
182
|
- **Location**: [docs/api/openapi.yaml](docs/api/openapi.yaml)
|
|
210
|
-
- **Interactive UI**:
|
|
183
|
+
- **Interactive UI**: [docs/api/index.html](docs/api/index.html), bundled into Worker assets and served at `/api/`
|
|
211
184
|
- **Offline Access**: [docs/api/index.html](docs/api/index.html) for local viewing
|
|
212
185
|
|
|
213
186
|
### Shorten URL
|
|
214
187
|
|
|
215
|
-
**POST**
|
|
188
|
+
**POST** `/`
|
|
216
189
|
|
|
217
190
|
```json
|
|
218
191
|
{
|
|
219
192
|
"url": "https://example.com/very-long-url",
|
|
220
|
-
"custom_slug": "my-link"
|
|
193
|
+
"custom_slug": "my-link"
|
|
221
194
|
}
|
|
222
195
|
```
|
|
223
196
|
|
|
224
|
-
|
|
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):**
|
|
225
200
|
|
|
226
201
|
```json
|
|
227
202
|
{
|
|
228
|
-
"short_url": "https://
|
|
203
|
+
"short_url": "https://your-worker-domain.example/abc123"
|
|
229
204
|
}
|
|
230
205
|
```
|
|
231
206
|
|
|
@@ -242,11 +217,26 @@ The project includes comprehensive API documentation available at `/api` endpoin
|
|
|
242
217
|
|
|
243
218
|
**GET** `/{key}`
|
|
244
219
|
|
|
245
|
-
Redirects to the
|
|
220
|
+
Redirects to the stored destination with HTTP 302. Query parameters from the short URL are appended to the destination.
|
|
246
221
|
|
|
247
222
|
### Additional Endpoints
|
|
248
223
|
|
|
249
|
-
- **GET**
|
|
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.
|
|
250
240
|
|
|
251
241
|
## 🛠️ Development
|
|
252
242
|
|
|
@@ -254,45 +244,50 @@ Redirects to the original URL or shows appropriate error page.
|
|
|
254
244
|
|
|
255
245
|
```bash
|
|
256
246
|
# Start local development server (see wrangler.toml for port / host)
|
|
257
|
-
|
|
247
|
+
pnpm dev
|
|
258
248
|
|
|
259
|
-
#
|
|
260
|
-
|
|
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/ \
|
|
261
252
|
-H "Content-Type: application/json" \
|
|
253
|
+
-H "Cf-Access-Jwt-Assertion: local-test" \
|
|
254
|
+
-H "Cf-Access-Authenticated-User-Email: you@example.com" \
|
|
262
255
|
-d '{"url": "https://example.com"}'
|
|
263
256
|
```
|
|
264
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
|
+
|
|
265
260
|
### File Structure
|
|
266
261
|
|
|
267
262
|
```
|
|
268
263
|
shorten-url/
|
|
269
264
|
├── src/
|
|
270
|
-
│ ├──
|
|
271
|
-
│ ├── worker.
|
|
272
|
-
│ └── worker.example.js
|
|
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
|
|
273
269
|
├── docs/
|
|
274
|
-
│ ├── index.html #
|
|
270
|
+
│ ├── index.html # Documentation site homepage
|
|
271
|
+
│ ├── about.md # Content rendered as the /about website page
|
|
275
272
|
│ └── api/
|
|
276
273
|
│ ├── index.html # Swagger UI for API docs
|
|
277
274
|
│ └── openapi.yaml # OpenAPI 3.1.0 specification
|
|
278
275
|
├── config/
|
|
279
|
-
│ ├── config.js
|
|
280
|
-
│ └── config.example.js
|
|
281
|
-
├── .
|
|
276
|
+
│ ├── config.js # Existing deployment template
|
|
277
|
+
│ └── config.example.js # Configuration example
|
|
278
|
+
├── wrangler.toml.example # Wrangler deployment template
|
|
282
279
|
├── .github/ # GitHub workflows and templates
|
|
283
|
-
├── .gitignore
|
|
284
|
-
|
|
285
|
-
└── wrangler.toml # Wrangler configuration
|
|
280
|
+
├── .gitignore # Ignores local-only files
|
|
281
|
+
└── README.md # This file
|
|
286
282
|
```
|
|
287
283
|
|
|
288
284
|
## Security Considerations
|
|
289
285
|
|
|
290
|
-
- Keep
|
|
291
|
-
-
|
|
292
|
-
-
|
|
293
|
-
- Consider enabling Google Safe Browsing for
|
|
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
|
|
294
290
|
- 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
291
|
|
|
297
292
|
## Troubleshooting
|
|
298
293
|
|
|
@@ -305,7 +300,9 @@ shorten-url/
|
|
|
305
300
|
|
|
306
301
|
**Worker deployment fails:**
|
|
307
302
|
|
|
308
|
-
-
|
|
303
|
+
- Confirm `src/worker.js`, `wrangler.toml.example`, and `frontend/` are present
|
|
304
|
+
- Build the frontend so `frontend/dist/index.html` exists
|
|
305
|
+
- Ensure the `LINKS` KV namespace ID in `wrangler.toml` is valid
|
|
309
306
|
- Ensure KV namespace ID is correct
|
|
310
307
|
- Verify you're logged into the correct Cloudflare account
|
|
311
308
|
|
|
@@ -317,13 +314,13 @@ shorten-url/
|
|
|
317
314
|
|
|
318
315
|
**Frontend not loading:**
|
|
319
316
|
|
|
320
|
-
-
|
|
321
|
-
-
|
|
317
|
+
- Check that the Worker has an `ASSETS` binding and `frontend/dist/index.html` was built
|
|
318
|
+
- If shortening returns an invalid-path response, check that the frontend POST path is `/`
|
|
319
|
+
- Enable CORS if the frontend and Worker use different origins
|
|
322
320
|
|
|
323
321
|
**Rate limiting issues:**
|
|
324
322
|
|
|
325
|
-
-
|
|
326
|
-
- Consider implementing IP allowlisting for trusted sources
|
|
323
|
+
- The Worker does not currently rate-limit requests. Protect shortening with Cloudflare Access or add a rate limiter.
|
|
327
324
|
|
|
328
325
|
## License
|
|
329
326
|
|
|
@@ -347,12 +344,14 @@ If you encounter any issues or have questions:
|
|
|
347
344
|
|
|
348
345
|
## Deployment Checklist
|
|
349
346
|
|
|
350
|
-
- [ ]
|
|
351
|
-
- [ ]
|
|
347
|
+
- [ ] Set `LINKS_KV_NAMESPACE_ID` and `ACCESS_ALLOWED_HOSTS` for Worker deployment
|
|
348
|
+
- [ ] Build the React frontend into `frontend/dist`
|
|
349
|
+
- [ ] Deployed `docs/` to Cloudflare Pages
|
|
352
350
|
- [ ] Created Cloudflare KV namespace
|
|
353
351
|
- [ ] Configured `wrangler.toml` with correct KV namespace ID
|
|
354
|
-
- [ ]
|
|
355
|
-
- [ ]
|
|
352
|
+
- [ ] Protected the Worker with a Cloudflare Access policy
|
|
353
|
+
- [ ] Deployed the Worker using `pnpm deploy`
|
|
354
|
+
- [ ] Tested URL shortening at `POST /`
|
|
356
355
|
- [ ] Verified copy-to-clipboard feature works
|
|
357
356
|
- [ ] Tested custom error pages (404, security warnings)
|
|
358
357
|
- [ ] (Optional) Configured custom domain
|
package/config/config.example.js
CHANGED
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
const config = {
|
|
10
10
|
// Frontend Configuration
|
|
11
11
|
frontend: {
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
url: "
|
|
12
|
+
// Leave empty when the Worker serves the React UI. Set this only for
|
|
13
|
+
// separately hosted legacy error/interstitial pages.
|
|
14
|
+
url: "",
|
|
15
|
+
|
|
16
|
+
// Public Worker origin used by frontend/index.html for shortening and
|
|
17
|
+
// protected settings APIs when the frontend is hosted separately. Keep it in
|
|
18
|
+
// sync with the HTML meta tag or VITE_WORKER_ORIGIN; leave empty when the
|
|
19
|
+
// Worker serves the page.
|
|
20
|
+
workerOrigin: "",
|
|
15
21
|
|
|
16
22
|
// Domain displayed in the custom slug input prefix
|
|
17
23
|
// If null, will use the current domain automatically
|
|
@@ -64,6 +70,7 @@ const config = {
|
|
|
64
70
|
"support",
|
|
65
71
|
"contact",
|
|
66
72
|
"about",
|
|
73
|
+
"settings",
|
|
67
74
|
],
|
|
68
75
|
},
|
|
69
76
|
|
|
@@ -73,6 +80,18 @@ const config = {
|
|
|
73
80
|
binding_name: "LINKS",
|
|
74
81
|
},
|
|
75
82
|
|
|
83
|
+
// Flagship Configuration (optional until the binding and secrets are provisioned)
|
|
84
|
+
flagship: {
|
|
85
|
+
// Wrangler binding name. The Worker expects the official `env.FLAGS` binding.
|
|
86
|
+
binding_name: "FLAGS",
|
|
87
|
+
// Set in the Flagship binding block in wrangler.toml; do not put credentials here.
|
|
88
|
+
app_id: "",
|
|
89
|
+
// Worker secrets/variables used only by protected settings management:
|
|
90
|
+
// CLOUDFLARE_ACCOUNT_ID, FLAGSHIP_APP_ID, FLAGSHIP_API_TOKEN,
|
|
91
|
+
// FLAGSHIP_CSRF_SECRET, CF_ACCESS_ISSUER, CF_ACCESS_AUDIENCE,
|
|
92
|
+
// and optionally CF_ACCESS_JWKS_URL.
|
|
93
|
+
},
|
|
94
|
+
|
|
76
95
|
// Security Configuration
|
|
77
96
|
security: {
|
|
78
97
|
// Rate limiting (requests per minute per IP)
|