@startup-api/cloudflare 0.0.1
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/LICENSE +13 -0
- package/README.md +114 -0
- package/package.json +53 -0
- package/public/index.html +405 -0
- package/public/users/accounts.html +504 -0
- package/public/users/admin/index.html +765 -0
- package/public/users/power-strip.js +658 -0
- package/public/users/profile.html +443 -0
- package/public/users/style.css +493 -0
- package/src/CookieManager.ts +56 -0
- package/src/PowerStrip.ts +23 -0
- package/src/StartupAPIEnv.ts +12 -0
- package/src/auth/GoogleProvider.ts +67 -0
- package/src/auth/OAuthProvider.ts +52 -0
- package/src/auth/TwitchProvider.ts +64 -0
- package/src/auth/index.ts +231 -0
- package/src/billing/PaymentEngine.ts +20 -0
- package/src/billing/Plan.ts +80 -0
- package/src/billing/plansConfig.ts +48 -0
- package/src/handlers/account.ts +246 -0
- package/src/handlers/admin.ts +144 -0
- package/src/handlers/auth.ts +54 -0
- package/src/handlers/ssr.ts +274 -0
- package/src/handlers/user.ts +168 -0
- package/src/handlers/utils.ts +120 -0
- package/src/index.ts +190 -0
- package/src/schemas/account.ts +37 -0
- package/src/schemas/admin.ts +10 -0
- package/src/schemas/billing.ts +11 -0
- package/src/schemas/credential.ts +38 -0
- package/src/schemas/membership.ts +9 -0
- package/src/schemas/session.ts +10 -0
- package/src/schemas/user.ts +22 -0
- package/src/storage/AccountDO.ts +370 -0
- package/src/storage/CredentialDO.ts +82 -0
- package/src/storage/SystemDO.ts +264 -0
- package/src/storage/UserDO.ts +385 -0
- package/worker-configuration.d.ts +11696 -0
- package/wrangler.template.jsonc +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright (c) 2026 Sergey Chernyshev and contributors.
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Startup API Cloudflare App
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
+
|
|
5
|
+
This application uses the Cloudflare Developer Platform, including Workers and DurableObjects, to implement foundational web application functionality. It acts as a transparent proxy for your application, allowing you to inject custom UI elements and intercept specific paths.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Transparent Proxying:** Forwards requests to your origin application
|
|
10
|
+
- **HTML Injection:** Uses `HTMLRewriter` to inject scripts and custom elements (like `<power-strip>`) into your HTML pages
|
|
11
|
+
- **Path Interception:** Intercepts requests to a configurable path to serve internal assets
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Option 1: Cloudflare Workers GitHub Integration (Recommended)
|
|
16
|
+
|
|
17
|
+
This is the easiest way to deploy and keep your worker up to date.
|
|
18
|
+
|
|
19
|
+
1. **Fork this repository** to your account
|
|
20
|
+
2. Go to your [Cloudflare Dashboard's Workers & pages > Create Application](https://dash.cloudflare.com/?to=/:account/workers-and-pages/create)
|
|
21
|
+
3. Click **Continue with GitHub**
|
|
22
|
+
4. Select your forked `startup-api-cloudflare` repository
|
|
23
|
+
5. Pick the name for your site's worker (e.g. you might have multiple)
|
|
24
|
+
6. Deploy the Worker
|
|
25
|
+
7. In the **Settings** tab of your Worker, go to **Variables** and add the required `ORIGIN_URL` (see [Configuration](#configuration-details) below)
|
|
26
|
+
|
|
27
|
+
### Option 2: Manual Installation (CLI)
|
|
28
|
+
|
|
29
|
+
Use this option if you want to deploy from your local machine.
|
|
30
|
+
|
|
31
|
+
1. **Clone and Install**
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/StartupAPI/startup-api-cloudflare.git
|
|
34
|
+
cd startup-api-cloudflare
|
|
35
|
+
npm install
|
|
36
|
+
```
|
|
37
|
+
2. **Configure Environment Variables**
|
|
38
|
+
|
|
39
|
+
Update `wrangler.jsonc` or use dashboard **Settings** tab of your Worker, go to **Variables** and add the required `ORIGIN_URL` (see [Configuration](#configuration-details) below)
|
|
40
|
+
|
|
41
|
+
3. **Deploy**
|
|
42
|
+
```bash
|
|
43
|
+
npm run deploy
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration Details
|
|
47
|
+
|
|
48
|
+
### How to set environment variables
|
|
49
|
+
|
|
50
|
+
- **Using Cloudflare Dashboard (Recommended):**
|
|
51
|
+
1. Go to **Workers & Pages**
|
|
52
|
+
2. Select your worker
|
|
53
|
+
3. Navigate to **Settings** > **Variables**
|
|
54
|
+
4. Click **Add variable** under **Environment Variables**
|
|
55
|
+
5. Add `ORIGIN_URL` and any optional variables
|
|
56
|
+
6. Click **Save and deploy**
|
|
57
|
+
|
|
58
|
+
- **Using `wrangler.jsonc`:**
|
|
59
|
+
Add the variables to the `"vars"` object in your configuration file. See [Cloudflare documentation](https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables) for more details.
|
|
60
|
+
|
|
61
|
+
| Variable | Required | Default | Description |
|
|
62
|
+
| :--------------------- | :------- | :-------- | :---------------------------------------------------------------------------- |
|
|
63
|
+
| `ORIGIN_URL` | **Yes** | N/A | The base URL of your origin application (e.g., `https://your-app-origin.com`) |
|
|
64
|
+
| `USERS_PATH` | No | `/users/` | The path used to serve internal assets like `power-strip.js` |
|
|
65
|
+
| `AUTH_ORIGIN` | No | N/A | Optional base URL for OAuth redirects (overrides request origin) |
|
|
66
|
+
| `GOOGLE_CLIENT_ID` | No | N/A | Google OAuth2 Client ID |
|
|
67
|
+
| `GOOGLE_CLIENT_SECRET` | No | N/A | Google OAuth2 Client Secret |
|
|
68
|
+
| `TWITCH_CLIENT_ID` | No | N/A | Twitch OAuth2 Client ID |
|
|
69
|
+
| `TWITCH_CLIENT_SECRET` | No | N/A | Twitch OAuth2 Client Secret |
|
|
70
|
+
|
|
71
|
+
### Setting up OAuth
|
|
72
|
+
|
|
73
|
+
#### Google
|
|
74
|
+
|
|
75
|
+
1. Go to the [Google Cloud Console](https://console.cloud.google.com/)
|
|
76
|
+
2. Create a new project or select an existing one
|
|
77
|
+
3. Navigate to **APIs & Services > Credentials**
|
|
78
|
+
4. Click **Create Credentials > OAuth client ID**
|
|
79
|
+
5. Select **Web application** as the application type
|
|
80
|
+
6. Add your authorized redirect URI: `https://<your-worker-url>/users/auth/google/callback`
|
|
81
|
+
7. Copy the **Client ID** and **Client Secret** and add them to your Worker's environment variables
|
|
82
|
+
|
|
83
|
+
#### Twitch
|
|
84
|
+
|
|
85
|
+
1. Go to the [Twitch Developer Console](https://dev.twitch.tv/console)
|
|
86
|
+
2. Register a new application
|
|
87
|
+
3. Add your authorized redirect URI: `https://<your-worker-url>/users/auth/twitch/callback`
|
|
88
|
+
4. Select **Website** as the category
|
|
89
|
+
5. Copy the **Client ID** and generate a **Client Secret** to add them to your Worker's environment variables
|
|
90
|
+
|
|
91
|
+
### Example `wrangler.jsonc` snippet:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"vars": {
|
|
96
|
+
"ORIGIN_URL": "https://your-app-origin.com"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## How It Works
|
|
102
|
+
|
|
103
|
+
1. **Request Interception:** The worker receives all incoming requests
|
|
104
|
+
2. **Path Mapping:** If the request path starts with `USERS_PATH`, the worker serves assets directly from the `public/users/` directory
|
|
105
|
+
3. **Proxying:** All other requests are proxied to the configured `ORIGIN_URL`
|
|
106
|
+
4. **Injection:** For `text/html` responses, the worker injects a `<script>` tag and a `<power-strip>` custom element before serving the content to the user
|
|
107
|
+
|
|
108
|
+
## Contributing
|
|
109
|
+
|
|
110
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details.
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@startup-api/cloudflare",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/StartupAPI/startup-api-cloudflare.git"
|
|
11
|
+
},
|
|
12
|
+
"main": "src/index.ts",
|
|
13
|
+
"types": "src/index.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": "./src/index.ts",
|
|
16
|
+
"./public/*": "./public/*",
|
|
17
|
+
"./wrangler.template.jsonc": "./wrangler.template.jsonc",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"public",
|
|
23
|
+
"worker-configuration.d.ts",
|
|
24
|
+
"wrangler.template.jsonc"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"deploy": "wrangler deploy",
|
|
28
|
+
"dev": "wrangler dev -c wrangler.local.jsonc",
|
|
29
|
+
"preview": "wrangler dev --env preview",
|
|
30
|
+
"test": "eslint . && vitest run",
|
|
31
|
+
"test:coverage": "vitest run --coverage --coverage.provider=istanbul",
|
|
32
|
+
"cf-typegen": "wrangler types",
|
|
33
|
+
"format": "npx prettier . --write",
|
|
34
|
+
"lint": "eslint .",
|
|
35
|
+
"lint:fix": "eslint . --fix"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@cloudflare/vitest-pool-workers": "^0.12.4",
|
|
39
|
+
"@eslint/js": "^10.0.1",
|
|
40
|
+
"@vitest/coverage-istanbul": "^3.2.4",
|
|
41
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
42
|
+
"eslint": "^10.0.1",
|
|
43
|
+
"globals": "^17.3.0",
|
|
44
|
+
"typescript": "^5.5.2",
|
|
45
|
+
"typescript-eslint": "^8.56.0",
|
|
46
|
+
"vitest": "~3.2.0",
|
|
47
|
+
"wrangler": "^4.60.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"prettier": "^3.8.1",
|
|
51
|
+
"zod": "^3.25.76"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Startup API Cloudflare App - Documentation</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--primary-color: #f6821f; /* Cloudflare Orange */
|
|
10
|
+
--bg-color: #f8f9fa;
|
|
11
|
+
--text-color: #333;
|
|
12
|
+
--card-bg: #fff;
|
|
13
|
+
--card-border: #eee;
|
|
14
|
+
--pre-bg: #f1f1f1;
|
|
15
|
+
--nav-bg: #2c2c2c;
|
|
16
|
+
--hero-bg: linear-gradient(135deg, #2c2c2c 0%, #4a4a4a 100%);
|
|
17
|
+
--container-width: 800px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root:not([data-theme='light']) {
|
|
22
|
+
--bg-color: #1a1a1a;
|
|
23
|
+
--text-color: #e0e0e0;
|
|
24
|
+
--card-bg: #2d2d2d;
|
|
25
|
+
--card-border: #444;
|
|
26
|
+
--pre-bg: #333;
|
|
27
|
+
--nav-bg: #111;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[data-theme='dark'] {
|
|
32
|
+
--bg-color: #1a1a1a;
|
|
33
|
+
--text-color: #e0e0e0;
|
|
34
|
+
--card-bg: #2d2d2d;
|
|
35
|
+
--card-border: #444;
|
|
36
|
+
--pre-bg: #333;
|
|
37
|
+
--nav-bg: #111;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[data-theme='light'] {
|
|
41
|
+
--bg-color: #f8f9fa;
|
|
42
|
+
--text-color: #333;
|
|
43
|
+
--card-bg: #fff;
|
|
44
|
+
--card-border: #eee;
|
|
45
|
+
--pre-bg: #f1f1f1;
|
|
46
|
+
--nav-bg: #2c2c2c;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
body {
|
|
50
|
+
font-family:
|
|
51
|
+
system-ui,
|
|
52
|
+
-apple-system,
|
|
53
|
+
BlinkMacSystemFont,
|
|
54
|
+
'Segoe UI',
|
|
55
|
+
Roboto,
|
|
56
|
+
Oxygen,
|
|
57
|
+
Ubuntu,
|
|
58
|
+
Cantarell,
|
|
59
|
+
'Open Sans',
|
|
60
|
+
'Helvetica Neue',
|
|
61
|
+
sans-serif;
|
|
62
|
+
line-height: 1.6;
|
|
63
|
+
color: var(--text-color);
|
|
64
|
+
background-color: var(--bg-color);
|
|
65
|
+
margin: 0;
|
|
66
|
+
transition:
|
|
67
|
+
background-color 0.3s,
|
|
68
|
+
color 0.3s;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
nav {
|
|
72
|
+
background-color: var(--nav-bg);
|
|
73
|
+
color: white;
|
|
74
|
+
padding: 1rem 0;
|
|
75
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.nav-container {
|
|
79
|
+
max-width: var(--container-width);
|
|
80
|
+
margin: 0 auto;
|
|
81
|
+
display: flex;
|
|
82
|
+
justify-content: space-between;
|
|
83
|
+
align-items: center;
|
|
84
|
+
padding: 0 1rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.navbar-brand {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
font-weight: bold;
|
|
91
|
+
text-decoration: none;
|
|
92
|
+
color: white;
|
|
93
|
+
font-size: 1.25rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
header.hero-section {
|
|
97
|
+
background: var(--hero-bg);
|
|
98
|
+
color: white;
|
|
99
|
+
padding: 4rem 1rem;
|
|
100
|
+
text-align: center;
|
|
101
|
+
margin-bottom: 2rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
h1 {
|
|
105
|
+
margin: 0;
|
|
106
|
+
font-size: 2.5rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main {
|
|
110
|
+
max-width: var(--container-width);
|
|
111
|
+
margin: 0 auto;
|
|
112
|
+
padding: 0 1rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.card {
|
|
116
|
+
background-color: var(--card-bg);
|
|
117
|
+
border: 1px solid var(--card-border);
|
|
118
|
+
border-radius: 12px;
|
|
119
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
120
|
+
margin-bottom: 2rem;
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.card-header {
|
|
125
|
+
background-color: rgba(0, 0, 0, 0.03);
|
|
126
|
+
border-bottom: 1px solid var(--card-border);
|
|
127
|
+
font-weight: bold;
|
|
128
|
+
font-size: 1.2rem;
|
|
129
|
+
padding: 1rem 1.5rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.card-body {
|
|
133
|
+
padding: 1.5rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.variable-block {
|
|
137
|
+
margin-bottom: 1.5rem;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.variable-title {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: center;
|
|
143
|
+
gap: 0.5rem;
|
|
144
|
+
margin-bottom: 0.5rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.variable-name {
|
|
148
|
+
font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
|
149
|
+
color: var(--primary-color);
|
|
150
|
+
font-weight: bold;
|
|
151
|
+
background: rgba(246, 130, 31, 0.1);
|
|
152
|
+
padding: 0.2rem 0.4rem;
|
|
153
|
+
border-radius: 4px;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.badge-required {
|
|
157
|
+
background-color: #dc3545;
|
|
158
|
+
color: white;
|
|
159
|
+
font-size: 0.75rem;
|
|
160
|
+
padding: 0.2rem 0.5rem;
|
|
161
|
+
border-radius: 4px;
|
|
162
|
+
font-weight: bold;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pre {
|
|
166
|
+
background-color: var(--pre-bg);
|
|
167
|
+
color: var(--text-color);
|
|
168
|
+
padding: 1rem;
|
|
169
|
+
border-radius: 8px;
|
|
170
|
+
overflow-x: auto;
|
|
171
|
+
font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
|
|
172
|
+
font-size: 0.9rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
ol,
|
|
176
|
+
ul {
|
|
177
|
+
padding-left: 1.5rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
li {
|
|
181
|
+
margin-bottom: 0.5rem;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
footer {
|
|
185
|
+
text-align: center;
|
|
186
|
+
padding: 2rem 0;
|
|
187
|
+
color: #777;
|
|
188
|
+
border-top: 1px solid var(--card-border);
|
|
189
|
+
margin-top: 3rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.theme-toggle {
|
|
193
|
+
cursor: pointer;
|
|
194
|
+
background: transparent;
|
|
195
|
+
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
196
|
+
color: white;
|
|
197
|
+
border-radius: 50%;
|
|
198
|
+
width: 40px;
|
|
199
|
+
height: 40px;
|
|
200
|
+
display: flex;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
transition: all 0.2s;
|
|
204
|
+
padding: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.theme-toggle:hover {
|
|
208
|
+
background: rgba(255, 255, 255, 0.1);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.theme-toggle svg {
|
|
212
|
+
width: 20px;
|
|
213
|
+
height: 20px;
|
|
214
|
+
fill: currentColor;
|
|
215
|
+
}
|
|
216
|
+
</style>
|
|
217
|
+
</head>
|
|
218
|
+
<body>
|
|
219
|
+
<nav>
|
|
220
|
+
<div class="nav-container">
|
|
221
|
+
<a class="navbar-brand" href="">
|
|
222
|
+
<svg
|
|
223
|
+
viewBox="0 0 24 24"
|
|
224
|
+
style="width: 24px; height: 24px; fill: #ffcc00; margin-right: 10px; filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 0.5))"
|
|
225
|
+
>
|
|
226
|
+
<path d="M7 2v11h3v9l7-12h-4l4-8z" />
|
|
227
|
+
</svg>
|
|
228
|
+
Startup API
|
|
229
|
+
</a>
|
|
230
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle Theme">
|
|
231
|
+
<svg id="sunIcon" viewBox="0 0 24 24" style="display: none">
|
|
232
|
+
<path
|
|
233
|
+
d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58a.996.996 0 00-1.41 0 .996.996 0 000 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37a.996.996 0 00-1.41 0 .996.996 0 000 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 000-1.41l-1.06-1.06zm1.06-10.96a.996.996 0 000-1.41.996.996 0 00-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36a.996.996 0 000-1.41.996.996 0 00-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"
|
|
234
|
+
/>
|
|
235
|
+
</svg>
|
|
236
|
+
<svg id="moonIcon" viewBox="0 0 24 24">
|
|
237
|
+
<path
|
|
238
|
+
d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z"
|
|
239
|
+
/>
|
|
240
|
+
</svg>
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
</nav>
|
|
244
|
+
|
|
245
|
+
<script>
|
|
246
|
+
const themeToggle = document.getElementById('themeToggle');
|
|
247
|
+
const sunIcon = document.getElementById('sunIcon');
|
|
248
|
+
const moonIcon = document.getElementById('moonIcon');
|
|
249
|
+
const html = document.documentElement;
|
|
250
|
+
|
|
251
|
+
function updateIcons(theme) {
|
|
252
|
+
if (theme === 'dark') {
|
|
253
|
+
sunIcon.style.display = 'block';
|
|
254
|
+
moonIcon.style.display = 'none';
|
|
255
|
+
} else {
|
|
256
|
+
sunIcon.style.display = 'none';
|
|
257
|
+
moonIcon.style.display = 'block';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function setTheme(theme) {
|
|
262
|
+
if (theme) {
|
|
263
|
+
html.setAttribute('data-theme', theme);
|
|
264
|
+
updateIcons(theme);
|
|
265
|
+
} else {
|
|
266
|
+
html.removeAttribute('data-theme');
|
|
267
|
+
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
268
|
+
updateIcons(isDark ? 'dark' : 'light');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const savedTheme = localStorage.getItem('theme');
|
|
273
|
+
setTheme(savedTheme);
|
|
274
|
+
|
|
275
|
+
themeToggle.addEventListener('click', () => {
|
|
276
|
+
const currentTheme = html.getAttribute('data-theme');
|
|
277
|
+
let newTheme;
|
|
278
|
+
|
|
279
|
+
if (!currentTheme) {
|
|
280
|
+
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
281
|
+
newTheme = isDark ? 'light' : 'dark';
|
|
282
|
+
} else {
|
|
283
|
+
newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
setTheme(newTheme);
|
|
287
|
+
localStorage.setItem('theme', newTheme);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Update icons if system theme changes and no manual override is set
|
|
291
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
292
|
+
if (!html.getAttribute('data-theme')) {
|
|
293
|
+
updateIcons(e.matches ? 'dark' : 'light');
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
</script>
|
|
297
|
+
|
|
298
|
+
<header class="hero-section">
|
|
299
|
+
<div class="container">
|
|
300
|
+
<h1 class="display-4">Welcome to Startup API</h1>
|
|
301
|
+
</div>
|
|
302
|
+
</header>
|
|
303
|
+
|
|
304
|
+
<main class="container">
|
|
305
|
+
<div class="row">
|
|
306
|
+
<div class="col-lg-8 offset-lg-2">
|
|
307
|
+
<!-- Overview -->
|
|
308
|
+
|
|
309
|
+
<section class="card">
|
|
310
|
+
<div class="card-header">Overview</div>
|
|
311
|
+
|
|
312
|
+
<div class="card-body">
|
|
313
|
+
<p>
|
|
314
|
+
This application uses Cloudflare Developer Platform, including Workers and DurableObjects to implement functionality that
|
|
315
|
+
every web application needs on day zero.
|
|
316
|
+
</p>
|
|
317
|
+
|
|
318
|
+
<p>
|
|
319
|
+
The worker acts as a transparent proxy for your application. When configured, it fetches content from your site, injects a
|
|
320
|
+
<code><power-strip></code>, and serves it to the user.
|
|
321
|
+
</p>
|
|
322
|
+
</div>
|
|
323
|
+
</section>
|
|
324
|
+
|
|
325
|
+
<!-- Configuration -->
|
|
326
|
+
<section class="card">
|
|
327
|
+
<div class="card-header">Required Configuration</div>
|
|
328
|
+
<div class="card-body">
|
|
329
|
+
<p>
|
|
330
|
+
These variables <strong>must</strong> be set in your <code>wrangler.jsonc</code> or Cloudflare Dashboard for the worker to
|
|
331
|
+
function. Until these are configured, the worker serves this documentation page.
|
|
332
|
+
</p>
|
|
333
|
+
|
|
334
|
+
<div class="mb-4">
|
|
335
|
+
<h5>
|
|
336
|
+
<span class="variable-name">ORIGIN_URL</span>
|
|
337
|
+
</h5>
|
|
338
|
+
<p>The base URL of the application you want to proxy, e.g. your app URL.</p>
|
|
339
|
+
<pre>ORIGIN_URL = "https://your-app-origin.com"</pre>
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<div class="mb-4">
|
|
343
|
+
<h5>
|
|
344
|
+
<span class="variable-name">SESSION_SECRET</span>
|
|
345
|
+
</h5>
|
|
346
|
+
<p>A long, random string used to encrypt session cookies. <strong>Keep this secret!</strong></p>
|
|
347
|
+
<pre>SESSION_SECRET = "your-long-random-secret-string"</pre>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</section>
|
|
351
|
+
|
|
352
|
+
<section class="card">
|
|
353
|
+
<div class="card-header">Optional Configuration</div>
|
|
354
|
+
<div class="card-body">
|
|
355
|
+
<div class="mb-4">
|
|
356
|
+
<h5>
|
|
357
|
+
<span class="variable-name">USERS_PATH</span>
|
|
358
|
+
</h5>
|
|
359
|
+
<p>
|
|
360
|
+
The URL path where the worker intercepts requests to serve Startup API UI (like
|
|
361
|
+
<code>power-strip.js</code>).
|
|
362
|
+
</p>
|
|
363
|
+
<p><strong>Default:</strong> <code>/users/</code></p>
|
|
364
|
+
<pre>USERS_PATH = "/people/"</pre>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
</section>
|
|
368
|
+
|
|
369
|
+
<!-- How it works -->
|
|
370
|
+
<section class="card">
|
|
371
|
+
<div class="card-header">How Injection Works</div>
|
|
372
|
+
<div class="card-body">
|
|
373
|
+
<ol>
|
|
374
|
+
<li>Worker receives a request.</li>
|
|
375
|
+
<li>
|
|
376
|
+
If the path starts with
|
|
377
|
+
<span class="variable-name">USERS_PATH</span>, it serves files from the <code>public/users/</code> directory.
|
|
378
|
+
</li>
|
|
379
|
+
<li>
|
|
380
|
+
Otherwise, it proxies the request to
|
|
381
|
+
<span class="variable-name">ORIGIN_URL</span>.
|
|
382
|
+
</li>
|
|
383
|
+
<li>
|
|
384
|
+
If the response is <code>text/html</code>, it injects:
|
|
385
|
+
<ul>
|
|
386
|
+
<li>A <code><script></code> tag loading <code>power-strip.js</code>.</li>
|
|
387
|
+
<li>A <code><power-strip></code> element styled to appear in the top-right corner.</li>
|
|
388
|
+
</ul>
|
|
389
|
+
</li>
|
|
390
|
+
</ol>
|
|
391
|
+
</div>
|
|
392
|
+
</section>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</main>
|
|
396
|
+
|
|
397
|
+
<footer>
|
|
398
|
+
<p>© 2026 Startup API. Licensed under Apache 2.0.</p>
|
|
399
|
+
|
|
400
|
+
<p>
|
|
401
|
+
<a href="https://github.com/StartupAPI/cloudflare" style="color: var(--primary-color); text-decoration: none">View on GitHub</a>
|
|
402
|
+
</p>
|
|
403
|
+
</footer>
|
|
404
|
+
</body>
|
|
405
|
+
</html>
|