@marvalt/wadapter 2.3.19 → 2.3.21
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 +746 -62
- package/dist/client/wordpress-client.d.ts +47 -2
- package/dist/client/wordpress-client.d.ts.map +1 -1
- package/dist/generators/wordpress/wordpress-generator.d.ts +3 -0
- package/dist/generators/wordpress/wordpress-generator.d.ts.map +1 -1
- package/dist/generators.cjs +51 -4
- package/dist/generators.cjs.map +1 -1
- package/dist/generators.esm.js +51 -4
- package/dist/generators.esm.js.map +1 -1
- package/dist/index.d.ts +232 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +298 -51
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +309 -50
- package/dist/index.js.map +1 -1
- package/dist/react/hooks/useFrontpageSlug.d.ts +33 -0
- package/dist/react/hooks/useFrontpageSlug.d.ts.map +1 -0
- package/dist/static/wordpress-static.d.ts +66 -0
- package/dist/static/wordpress-static.d.ts.map +1 -1
- package/dist/utils/wordpress-menu.d.ts +84 -0
- package/dist/utils/wordpress-menu.d.ts.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,118 +1,802 @@
|
|
|
1
|
-
# marvalt
|
|
1
|
+
# @marvalt/wadapter
|
|
2
2
|
|
|
3
|
-
Static-first WordPress and Gravity Forms integration for React
|
|
3
|
+
Static-first WordPress and Gravity Forms integration package for React applications. Provides build-time static data generation, runtime static data access, and secure form submission capabilities with Cloudflare Turnstile bot protection.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Table of Contents
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [WordPress Plugin Requirements](#wordpress-plugin-requirements)
|
|
10
|
+
- [Environment Configuration](#environment-configuration)
|
|
11
|
+
- [Build-Time Data Generation](#build-time-data-generation)
|
|
12
|
+
- [Runtime Usage](#runtime-usage)
|
|
13
|
+
- [Cloudflare Setup](#cloudflare-setup)
|
|
14
|
+
- [Security](#security)
|
|
15
|
+
- [API Reference](#api-reference)
|
|
16
|
+
- [Examples](#examples)
|
|
17
|
+
- [Troubleshooting](#troubleshooting)
|
|
10
18
|
|
|
11
|
-
##
|
|
19
|
+
## Overview
|
|
12
20
|
|
|
13
|
-
-
|
|
14
|
-
- Frontend calls a Cloudflare Worker with `?endpoint=/wp-json/...`.
|
|
15
|
-
- Worker injects Cloudflare Access credentials from its environment and forwards requests.
|
|
16
|
-
- Gravity Forms submissions go to enhanced `gf-api/v1` endpoints via the worker.
|
|
21
|
+
`@marvalt/wadapter` provides a complete solution for integrating WordPress and Gravity Forms into React applications with a static-first architecture:
|
|
17
22
|
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
- **Build-time generators**: Fetch WordPress content and Gravity Forms schemas at build time, outputting static JSON files
|
|
24
|
+
- **Runtime static access**: Read-only access to pre-generated static data (no runtime API calls)
|
|
25
|
+
- **Secure form submissions**: Server-side proxy with multiple security layers including Turnstile bot protection
|
|
26
|
+
- **React components**: Ready-to-use components for rendering WordPress content and Gravity Forms
|
|
27
|
+
- **TypeScript support**: Full type definitions for all data structures
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
### Package Structure
|
|
30
|
+
|
|
31
|
+
The package has three distinct entry points to separate browser-safe code from Node.js-only code:
|
|
32
|
+
|
|
33
|
+
1. **`@marvalt/wadapter`** (Main browser bundle)
|
|
34
|
+
- React components (`GravityForm`, `WordPressContent`, `TurnstileWidget`)
|
|
35
|
+
- React hooks (`useWordPress`, `useGravityForms`)
|
|
36
|
+
- API clients (`WordPressClient`, `GravityFormsClient`)
|
|
37
|
+
- Static data loaders and selectors
|
|
38
|
+
- Types and utilities
|
|
39
|
+
- **No Node.js dependencies** (browser-safe)
|
|
40
|
+
|
|
41
|
+
2. **`@marvalt/wadapter/generators`** (Node.js-only bundle)
|
|
42
|
+
- `generateWordPressData()` - Build-time WordPress data generation
|
|
43
|
+
- `generateGravityFormsData()` - Build-time Gravity Forms schema generation
|
|
44
|
+
- `generateFormProtectionData()` - Build-time form protection data
|
|
45
|
+
- Uses `fs`, `path`, and other Node.js modules
|
|
46
|
+
- **Only for build scripts and SSG**
|
|
47
|
+
|
|
48
|
+
3. **`@marvalt/wadapter/server`** (Cloudflare Pages Functions)
|
|
49
|
+
- `handleGravityFormsProxy()` - Server-side proxy handler
|
|
50
|
+
- `verifyTurnstile()` - Server-side Turnstile verification
|
|
51
|
+
- **Only for serverless functions**
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
24
54
|
|
|
25
55
|
```bash
|
|
26
56
|
npm install @marvalt/wadapter
|
|
27
57
|
```
|
|
28
58
|
|
|
29
|
-
|
|
59
|
+
### Peer Dependencies
|
|
60
|
+
|
|
61
|
+
The package requires React (16.8.0+) and React DOM (16.8.0+). These should already be installed in your React project.
|
|
62
|
+
|
|
63
|
+
## WordPress Plugin Requirements
|
|
64
|
+
|
|
65
|
+
### Required Plugins
|
|
66
|
+
|
|
67
|
+
1. **Gravity Forms** (Premium or Basic)
|
|
68
|
+
- Must be installed and activated
|
|
69
|
+
- Required for form functionality
|
|
70
|
+
|
|
71
|
+
2. **Gravity Forms API Endpoint** (Custom Plugin)
|
|
72
|
+
- **Required** for proper form submissions with notification triggering
|
|
73
|
+
- Provides enhanced REST API endpoints at `/wp-json/gf-api/v1/`
|
|
74
|
+
- Solves the critical issue where standard Gravity Forms REST API submissions don't trigger email notifications
|
|
75
|
+
- Installation:
|
|
76
|
+
```bash
|
|
77
|
+
# Clone or download the plugin
|
|
78
|
+
git clone https://github.com/ViBuNe-Pty-Ltd/gravity-forms-api-endpoint.git wp-content/plugins/gravity-forms-api-endpoint
|
|
79
|
+
```
|
|
80
|
+
- Activate the plugin in WordPress admin: `Plugins → Installed Plugins`
|
|
81
|
+
- Verify installation: Visit `https://your-wordpress-site.com/wp-json/gf-api/v1/health`
|
|
30
82
|
|
|
31
|
-
|
|
83
|
+
### Why the Custom Plugin is Required
|
|
84
|
+
|
|
85
|
+
The standard Gravity Forms REST API (`/wp-json/gf/v2/forms/{id}/submissions`) creates entries but **does not trigger notifications**. The custom plugin provides `/wp-json/gf-api/v1/forms/{id}/submit` which:
|
|
86
|
+
- ✅ Creates entries AND triggers notifications
|
|
87
|
+
- ✅ Fires all Gravity Forms hooks
|
|
88
|
+
- ✅ Returns proper confirmation messages
|
|
89
|
+
- ✅ Integrates with webhook automation for static data regeneration
|
|
90
|
+
|
|
91
|
+
## Environment Configuration
|
|
92
|
+
|
|
93
|
+
### Build-Time Variables (`.env` or `.env.local`)
|
|
94
|
+
|
|
95
|
+
These variables are used during build-time data generation and may be exposed to the browser (use `.env` for non-secrets, `.env.local` for secrets):
|
|
32
96
|
|
|
33
97
|
```env
|
|
34
|
-
# Mode
|
|
35
|
-
VITE_AUTH_MODE=cloudflare_proxy
|
|
98
|
+
# Authentication Mode
|
|
99
|
+
VITE_AUTH_MODE=direct # or 'cloudflare_proxy'
|
|
36
100
|
|
|
37
|
-
# WordPress
|
|
101
|
+
# WordPress Configuration
|
|
38
102
|
VITE_WORDPRESS_API_URL=https://your-wordpress-site.com
|
|
39
|
-
VITE_CLOUDFLARE_WORKER_URL=https://your-worker.your-subdomain.workers.dev # when cloudflare_proxy
|
|
40
103
|
|
|
41
|
-
#
|
|
104
|
+
# WordPress Credentials (for build-time generation only - never exposed to browser)
|
|
105
|
+
VITE_WP_API_USERNAME=your-username
|
|
106
|
+
VITE_WP_APP_PASSWORD=your-app-password
|
|
107
|
+
|
|
108
|
+
# Gravity Forms Configuration
|
|
109
|
+
VITE_GRAVITY_FORMS_API_URL=https://your-wordpress-site.com/wp-json/gf-api/v1
|
|
110
|
+
# Or leave empty to auto-construct from VITE_WORDPRESS_API_URL
|
|
111
|
+
|
|
112
|
+
# Cloudflare Access (if WordPress is behind Cloudflare Access)
|
|
113
|
+
VITE_CF_ACCESS_CLIENT_ID=your-client-id
|
|
114
|
+
VITE_CF_ACCESS_CLIENT_SECRET=your-client-secret
|
|
115
|
+
|
|
116
|
+
# Turnstile Bot Protection (optional)
|
|
117
|
+
VITE_TURNSTILE_SITE_KEY=your-site-key
|
|
118
|
+
|
|
119
|
+
# WordPress Data Generation Options
|
|
120
|
+
VITE_ENABLED_POST_TYPES=posts,pages,chapter_member # Comma-separated list
|
|
121
|
+
VITE_DEFAULT_MAX_ITEMS=100 # Max items per post type
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Cloudflare Pages Environment Variables
|
|
125
|
+
|
|
126
|
+
These are set in the Cloudflare Pages dashboard (Settings → Environment Variables) and are **server-side only**:
|
|
127
|
+
|
|
128
|
+
```env
|
|
129
|
+
# WordPress Credentials (for Pages Function proxy)
|
|
130
|
+
VITE_WORDPRESS_API_URL=https://your-wordpress-site.com
|
|
42
131
|
VITE_WP_API_USERNAME=your-username
|
|
43
|
-
|
|
132
|
+
VITE_WP_APP_PASSWORD=your-app-password
|
|
133
|
+
|
|
134
|
+
# Origin Validation (REQUIRED for production)
|
|
135
|
+
ALLOWED_ORIGINS=https://yourdomain.com,https://preview.yourdomain.com
|
|
44
136
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
VITE_GRAVITY_FORMS_USERNAME=your-username # when direct
|
|
48
|
-
VITE_GRAVITY_FORMS_PASSWORD=your-app-password # when direct
|
|
137
|
+
# Turnstile Verification (optional but recommended)
|
|
138
|
+
TURNSTILE_SECRET_KEY=your-secret-key
|
|
49
139
|
|
|
50
|
-
#
|
|
140
|
+
# Cloudflare Access (if WordPress is behind Cloudflare Access)
|
|
141
|
+
VITE_CF_ACCESS_CLIENT_ID=your-client-id
|
|
142
|
+
VITE_CF_ACCESS_CLIENT_SECRET=your-client-secret
|
|
51
143
|
```
|
|
52
144
|
|
|
53
|
-
|
|
145
|
+
**Important Notes:**
|
|
146
|
+
- `ALLOWED_ORIGINS` is **required** for the proxy to work in production. If not set, only `localhost:8080` and `localhost:5173` are allowed.
|
|
147
|
+
- `TURNSTILE_SECRET_KEY` is optional. If set, Turnstile verification will be enforced for form submissions.
|
|
148
|
+
- `VITE_TURNSTILE_SITE_KEY` must be set in both build-time and runtime environments for Turnstile to work.
|
|
54
149
|
|
|
55
|
-
|
|
150
|
+
## Build-Time Data Generation
|
|
56
151
|
|
|
57
|
-
|
|
58
|
-
- Gravity Forms JSON: `public/gravityForms.json`
|
|
152
|
+
### Setup Generation Scripts
|
|
59
153
|
|
|
60
|
-
|
|
154
|
+
Create scripts to generate static data at build time. These scripts use the `/generators` export which requires Node.js.
|
|
61
155
|
|
|
62
|
-
|
|
156
|
+
**Example: `scripts/generateWordPressData.ts`**
|
|
63
157
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
158
|
+
```typescript
|
|
159
|
+
import dotenv from 'dotenv';
|
|
160
|
+
import { generateWordPressData } from '@marvalt/wadapter/generators';
|
|
67
161
|
|
|
68
|
-
|
|
162
|
+
dotenv.config({ path: '.env' });
|
|
163
|
+
dotenv.config({ path: '.env.local' });
|
|
69
164
|
|
|
70
|
-
|
|
165
|
+
async function run() {
|
|
166
|
+
const config = {
|
|
167
|
+
authMode: 'direct' as const,
|
|
168
|
+
apiUrl: process.env.VITE_WORDPRESS_API_URL,
|
|
169
|
+
username: process.env.VITE_WP_API_USERNAME,
|
|
170
|
+
password: process.env.VITE_WP_APP_PASSWORD,
|
|
171
|
+
cfAccessClientId: process.env.VITE_CF_ACCESS_CLIENT_ID,
|
|
172
|
+
cfAccessClientSecret: process.env.VITE_CF_ACCESS_CLIENT_SECRET,
|
|
173
|
+
};
|
|
71
174
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
175
|
+
if (!config.apiUrl || !config.username || !config.password) {
|
|
176
|
+
console.error('❌ Missing required environment variables');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
75
179
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
180
|
+
const postTypes = process.env.VITE_ENABLED_POST_TYPES
|
|
181
|
+
? process.env.VITE_ENABLED_POST_TYPES.split(',').map(t => t.trim())
|
|
182
|
+
: ['posts', 'pages'];
|
|
79
183
|
|
|
80
|
-
|
|
184
|
+
const maxItems = parseInt(process.env.VITE_DEFAULT_MAX_ITEMS || '100', 10);
|
|
81
185
|
|
|
82
|
-
|
|
186
|
+
await generateWordPressData({
|
|
187
|
+
...config,
|
|
188
|
+
outputPath: './public/wordpress-data.json',
|
|
189
|
+
postTypes,
|
|
190
|
+
includeEmbedded: true, // Always true for full functionality
|
|
191
|
+
maxItems,
|
|
192
|
+
});
|
|
83
193
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
- `GravityFormsGenerator`, `generateGravityFormsData`
|
|
87
|
-
- Generator output type for GF: `GravityFormsStaticBundle`
|
|
194
|
+
console.log('✅ WordPress data generation completed');
|
|
195
|
+
}
|
|
88
196
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- Gravity Forms: `loadGravityFormsData`, `hasGravityFormsStatic`, `getActiveForms`, `getPublishedForms`, `getFormById`
|
|
197
|
+
run();
|
|
198
|
+
```
|
|
92
199
|
|
|
93
|
-
|
|
200
|
+
**Example: `scripts/generateGravityFormsData.ts`**
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import dotenv from 'dotenv';
|
|
204
|
+
import { generateGravityFormsData } from '@marvalt/wadapter/generators';
|
|
205
|
+
|
|
206
|
+
dotenv.config({ path: '.env' });
|
|
207
|
+
dotenv.config({ path: '.env.local' });
|
|
208
|
+
|
|
209
|
+
async function run() {
|
|
210
|
+
const config = {
|
|
211
|
+
authMode: 'direct' as const,
|
|
212
|
+
apiUrl: process.env.VITE_GRAVITY_FORMS_API_URL ||
|
|
213
|
+
`${process.env.VITE_WORDPRESS_API_URL}/wp-json/gf-api/v1`,
|
|
214
|
+
username: process.env.VITE_WP_API_USERNAME || '',
|
|
215
|
+
password: process.env.VITE_WP_APP_PASSWORD || '',
|
|
216
|
+
useCustomEndpoint: true, // Force use of gf-api/v1
|
|
217
|
+
cfAccessClientId: process.env.VITE_CF_ACCESS_CLIENT_ID,
|
|
218
|
+
cfAccessClientSecret: process.env.VITE_CF_ACCESS_CLIENT_SECRET,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (!config.apiUrl || !config.username || !config.password) {
|
|
222
|
+
console.error('❌ Missing required environment variables');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await generateGravityFormsData({
|
|
227
|
+
...config,
|
|
228
|
+
outputPath: './public/gravityForms.json',
|
|
229
|
+
includeInactive: false,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
console.log('✅ Gravity Forms data generation completed');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
run();
|
|
236
|
+
```
|
|
94
237
|
|
|
95
|
-
|
|
238
|
+
### Integration with Build Process
|
|
96
239
|
|
|
97
|
-
|
|
240
|
+
Add to your `package.json`:
|
|
98
241
|
|
|
99
|
-
|
|
100
|
-
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"scripts": {
|
|
245
|
+
"generate:wp": "tsx scripts/generateWordPressData.ts",
|
|
246
|
+
"generate:gf": "tsx scripts/generateGravityFormsData.ts",
|
|
247
|
+
"generate:all": "npm run generate:wp && npm run generate:gf",
|
|
248
|
+
"build": "npm run generate:all && vite build"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
101
252
|
|
|
102
|
-
|
|
253
|
+
### Generated Files
|
|
254
|
+
|
|
255
|
+
The generators create static JSON files in your `public` directory:
|
|
256
|
+
|
|
257
|
+
- `public/wordpress-data.json` - WordPress posts, pages, media, categories, tags, and custom post types
|
|
258
|
+
- `public/gravityForms.json` - Gravity Forms schemas with fields, notifications, and confirmations
|
|
259
|
+
|
|
260
|
+
**WordPress Data Structure:**
|
|
261
|
+
- Pages include parsed Gutenberg blocks (`content.raw` is parsed and stored as `blocks`)
|
|
262
|
+
- Posts use rendered HTML (`content.rendered`)
|
|
263
|
+
- Embedded data (featured media, terms, etc.) is included when `includeEmbedded: true`
|
|
264
|
+
|
|
265
|
+
## Runtime Usage
|
|
266
|
+
|
|
267
|
+
### Static Data Access
|
|
268
|
+
|
|
269
|
+
Load and query static data at runtime (no API calls):
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import {
|
|
273
|
+
loadWordPressData,
|
|
274
|
+
getWordPressPosts,
|
|
275
|
+
getWordPressPages,
|
|
276
|
+
getWordPressMedia
|
|
277
|
+
} from '@marvalt/wadapter';
|
|
278
|
+
|
|
279
|
+
import {
|
|
280
|
+
loadGravityFormsData,
|
|
281
|
+
getActiveForms,
|
|
282
|
+
getFormById
|
|
283
|
+
} from '@marvalt/wadapter';
|
|
284
|
+
|
|
285
|
+
// Load data (typically done once at app startup)
|
|
286
|
+
await loadWordPressData();
|
|
287
|
+
await loadGravityFormsData();
|
|
288
|
+
|
|
289
|
+
// Query data
|
|
290
|
+
const posts = getWordPressPosts();
|
|
291
|
+
const pages = getWordPressPages();
|
|
292
|
+
const media = getWordPressMedia();
|
|
293
|
+
const forms = getActiveForms();
|
|
294
|
+
const form = getFormById(1);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### React Hooks
|
|
298
|
+
|
|
299
|
+
Use React hooks for convenient data access:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { useWordPress, useGravityForms } from '@marvalt/wadapter';
|
|
303
|
+
|
|
304
|
+
function MyComponent() {
|
|
305
|
+
const { posts, pages, media, loading } = useWordPress();
|
|
306
|
+
const { form, loading: formLoading, submitForm } = useGravityForms(1, {
|
|
307
|
+
apiUrl: import.meta.env.VITE_WORDPRESS_API_URL,
|
|
308
|
+
authMode: 'direct',
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Use the data...
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### React Components
|
|
316
|
+
|
|
317
|
+
#### WordPressContent Component
|
|
318
|
+
|
|
319
|
+
Render WordPress content with automatic HTML sanitization and responsive images:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
import { WordPressContent } from '@marvalt/wadapter';
|
|
323
|
+
|
|
324
|
+
function PostPage({ post }) {
|
|
325
|
+
return (
|
|
326
|
+
<WordPressContent
|
|
327
|
+
content={post.content.rendered}
|
|
328
|
+
className="prose prose-lg"
|
|
329
|
+
/>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### GravityForm Component
|
|
335
|
+
|
|
336
|
+
Render and handle Gravity Forms with automatic Turnstile integration:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { GravityForm } from '@marvalt/wadapter';
|
|
340
|
+
|
|
341
|
+
function ContactForm() {
|
|
342
|
+
return (
|
|
343
|
+
<GravityForm
|
|
344
|
+
formId={1}
|
|
345
|
+
config={{
|
|
346
|
+
apiUrl: import.meta.env.VITE_WORDPRESS_API_URL,
|
|
347
|
+
authMode: 'direct', // Uses Cloudflare Pages Function proxy
|
|
348
|
+
}}
|
|
349
|
+
onSubmit={(result) => {
|
|
350
|
+
console.log('Success:', result);
|
|
351
|
+
// result.confirmation contains confirmation message
|
|
352
|
+
}}
|
|
353
|
+
onError={(error) => {
|
|
354
|
+
console.error('Error:', error);
|
|
355
|
+
}}
|
|
356
|
+
/>
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Features:**
|
|
362
|
+
- Automatic field rendering based on form schema
|
|
363
|
+
- Client-side validation
|
|
364
|
+
- Conditional logic support
|
|
365
|
+
- Turnstile bot protection (automatic if `VITE_TURNSTILE_SITE_KEY` is set)
|
|
366
|
+
- Submit button disabled until Turnstile verification completes (if enabled)
|
|
367
|
+
|
|
368
|
+
#### TurnstileWidget Component
|
|
369
|
+
|
|
370
|
+
Standalone Turnstile widget for custom implementations:
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { TurnstileWidget } from '@marvalt/wadapter';
|
|
374
|
+
|
|
375
|
+
function CustomForm() {
|
|
376
|
+
const [token, setToken] = useState<string | null>(null);
|
|
377
|
+
|
|
378
|
+
return (
|
|
379
|
+
<>
|
|
380
|
+
<TurnstileWidget
|
|
381
|
+
onVerify={(token) => setToken(token)}
|
|
382
|
+
onError={() => setToken(null)}
|
|
383
|
+
/>
|
|
384
|
+
<button disabled={!token}>Submit</button>
|
|
385
|
+
</>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### API Clients
|
|
391
|
+
|
|
392
|
+
For programmatic access without React:
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { GravityFormsClient } from '@marvalt/wadapter';
|
|
396
|
+
|
|
397
|
+
const client = new GravityFormsClient({
|
|
398
|
+
apiUrl: import.meta.env.VITE_WORDPRESS_API_URL,
|
|
399
|
+
authMode: 'direct',
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const result = await client.submitForm({
|
|
403
|
+
form_id: 1,
|
|
404
|
+
field_values: {
|
|
405
|
+
'1': 'John Doe',
|
|
406
|
+
'2': 'john@example.com',
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Cloudflare Setup
|
|
412
|
+
|
|
413
|
+
### Cloudflare Pages Function
|
|
414
|
+
|
|
415
|
+
The package automatically installs a Cloudflare Pages Function template during `npm install` via a postinstall script. The function is located at:
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
functions/api/gravity-forms-submit.ts
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**If the file doesn't exist**, create it manually:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import { handleGravityFormsProxy } from '@marvalt/wadapter/server';
|
|
425
|
+
|
|
426
|
+
export const onRequest = handleGravityFormsProxy;
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### How the Proxy Works
|
|
430
|
+
|
|
431
|
+
```
|
|
432
|
+
┌─────────┐
|
|
433
|
+
│ Browser │ (Untrusted)
|
|
434
|
+
└────┬────┘
|
|
435
|
+
│ HTTPS + Turnstile Token (optional)
|
|
436
|
+
▼
|
|
437
|
+
┌─────────────────────┐
|
|
438
|
+
│ Pages Function │ ← Security Checkpoint
|
|
439
|
+
│ /api/gf-submit │ • Origin validation
|
|
440
|
+
│ │ • Turnstile verification (optional)
|
|
441
|
+
│ │ • Endpoint whitelist
|
|
442
|
+
│ │ • Basic Auth injection
|
|
443
|
+
│ │ • CF Access injection (optional)
|
|
444
|
+
└────┬────────────────┘
|
|
445
|
+
│ Authenticated Request
|
|
446
|
+
▼
|
|
447
|
+
┌─────────────────────┐
|
|
448
|
+
│ WordPress │ (Trusted)
|
|
449
|
+
│ /wp-json/gf-api/v1 │ • No auth needed from client
|
|
450
|
+
│ (Custom Plugin) │ • Processes submission
|
|
451
|
+
│ │ • Triggers notifications
|
|
452
|
+
│ │ • Returns confirmation
|
|
453
|
+
└─────────────────────┘
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Security Layers
|
|
457
|
+
|
|
458
|
+
The proxy implements five security layers:
|
|
459
|
+
|
|
460
|
+
1. **Origin Validation** - Only authorized domains can submit (via `ALLOWED_ORIGINS`)
|
|
461
|
+
2. **Endpoint Whitelisting** - Only `/forms/{id}/submit` endpoints allowed
|
|
462
|
+
3. **Turnstile Verification** - Bot protection (optional, requires `TURNSTILE_SECRET_KEY`)
|
|
463
|
+
4. **Server-Side Auth** - WordPress credentials never exposed to browser
|
|
464
|
+
5. **CF Access Support** - Optional additional security layer for WordPress behind Cloudflare Access
|
|
465
|
+
|
|
466
|
+
### Cloudflare Access (Optional)
|
|
467
|
+
|
|
468
|
+
If your WordPress site is behind Cloudflare Access (Zero Trust), you need to:
|
|
469
|
+
|
|
470
|
+
1. Create a Cloudflare Access Service Token
|
|
471
|
+
2. Set `VITE_CF_ACCESS_CLIENT_ID` and `VITE_CF_ACCESS_CLIENT_SECRET` in both:
|
|
472
|
+
- Build-time environment (for data generation)
|
|
473
|
+
- Cloudflare Pages environment (for proxy)
|
|
474
|
+
|
|
475
|
+
The proxy will automatically inject CF Access headers when these credentials are present.
|
|
476
|
+
|
|
477
|
+
## Security
|
|
478
|
+
|
|
479
|
+
### Authentication Modes
|
|
480
|
+
|
|
481
|
+
#### Direct Mode (`VITE_AUTH_MODE=direct`)
|
|
482
|
+
|
|
483
|
+
- **Build-time**: Uses WordPress Basic Auth directly (credentials in `.env.local`)
|
|
484
|
+
- **Runtime**: Uses Cloudflare Pages Function proxy (credentials in Cloudflare Pages environment)
|
|
485
|
+
- **WordPress**: Uses standard REST API endpoints
|
|
486
|
+
- **Gravity Forms**: Uses custom `gf-api/v1` endpoints (requires custom plugin)
|
|
487
|
+
|
|
488
|
+
#### Cloudflare Proxy Mode (`VITE_AUTH_MODE=cloudflare_proxy`)
|
|
489
|
+
|
|
490
|
+
- **Build-time**: Uses WordPress Basic Auth directly
|
|
491
|
+
- **Runtime**: Uses Cloudflare Worker with `?endpoint=` query parameter
|
|
492
|
+
- **WordPress**: Worker injects CF Access credentials
|
|
493
|
+
- **Gravity Forms**: Worker routes to `gf-api/v1` endpoints
|
|
494
|
+
|
|
495
|
+
**Note**: Most implementations use `direct` mode with Cloudflare Pages Functions, which provides the same security benefits without requiring a separate Worker.
|
|
496
|
+
|
|
497
|
+
### Best Practices
|
|
498
|
+
|
|
499
|
+
1. **Never expose credentials to the browser**: All WordPress credentials should be in `.env.local` (not committed) or Cloudflare Pages environment variables
|
|
500
|
+
2. **Always use the proxy for form submissions**: Never call WordPress API directly from the browser
|
|
501
|
+
3. **Enable Turnstile**: Set `VITE_TURNSTILE_SITE_KEY` and `TURNSTILE_SECRET_KEY` for bot protection
|
|
502
|
+
4. **Set ALLOWED_ORIGINS**: Required for production deployments
|
|
503
|
+
5. **Use HTTPS**: Always use HTTPS in production
|
|
504
|
+
|
|
505
|
+
## API Reference
|
|
103
506
|
|
|
104
|
-
|
|
507
|
+
### Generators (`@marvalt/wadapter/generators`)
|
|
105
508
|
|
|
509
|
+
#### `generateWordPressData(config)`
|
|
106
510
|
|
|
511
|
+
Generates static WordPress data.
|
|
107
512
|
|
|
513
|
+
**Parameters:**
|
|
514
|
+
- `config.apiUrl` (string, required) - WordPress API URL
|
|
515
|
+
- `config.username` (string, required) - WordPress username
|
|
516
|
+
- `config.password` (string, required) - WordPress app password
|
|
517
|
+
- `config.outputPath` (string, required) - Output file path
|
|
518
|
+
- `config.postTypes` (string[], optional) - Post types to fetch (default: `['posts', 'pages']`)
|
|
519
|
+
- `config.includeEmbedded` (boolean, optional) - Include embedded data (default: `true`)
|
|
520
|
+
- `config.maxItems` (number, optional) - Max items per post type (default: `100`)
|
|
521
|
+
- `config.cfAccessClientId` (string, optional) - CF Access client ID
|
|
522
|
+
- `config.cfAccessClientSecret` (string, optional) - CF Access client secret
|
|
108
523
|
|
|
524
|
+
**Returns:** `Promise<WordPressStaticData>`
|
|
109
525
|
|
|
526
|
+
#### `generateGravityFormsData(config)`
|
|
110
527
|
|
|
528
|
+
Generates static Gravity Forms data.
|
|
111
529
|
|
|
530
|
+
**Parameters:**
|
|
531
|
+
- `config.apiUrl` (string, required) - Gravity Forms API URL (should use `gf-api/v1`)
|
|
532
|
+
- `config.username` (string, required) - WordPress username
|
|
533
|
+
- `config.password` (string, required) - WordPress app password
|
|
534
|
+
- `config.outputPath` (string, required) - Output file path
|
|
535
|
+
- `config.useCustomEndpoint` (boolean, optional) - Use custom `gf-api/v1` endpoint (default: `true`)
|
|
536
|
+
- `config.includeInactive` (boolean, optional) - Include inactive forms (default: `false`)
|
|
537
|
+
- `config.cfAccessClientId` (string, optional) - CF Access client ID
|
|
538
|
+
- `config.cfAccessClientSecret` (string, optional) - CF Access client secret
|
|
112
539
|
|
|
540
|
+
**Returns:** `Promise<GravityFormsStaticBundle>`
|
|
113
541
|
|
|
542
|
+
### Server Functions (`@marvalt/wadapter/server`)
|
|
543
|
+
|
|
544
|
+
#### `handleGravityFormsProxy(context)`
|
|
545
|
+
|
|
546
|
+
Cloudflare Pages Function handler for Gravity Forms proxy.
|
|
547
|
+
|
|
548
|
+
**Usage:**
|
|
549
|
+
```typescript
|
|
550
|
+
import { handleGravityFormsProxy } from '@marvalt/wadapter/server';
|
|
551
|
+
|
|
552
|
+
export const onRequest = handleGravityFormsProxy;
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Required Environment Variables:**
|
|
556
|
+
- `VITE_WORDPRESS_API_URL` or `WORDPRESS_API_URL`
|
|
557
|
+
- `VITE_WP_API_USERNAME` or `WP_API_USERNAME`
|
|
558
|
+
- `VITE_WP_APP_PASSWORD` or `WP_APP_PASSWORD`
|
|
559
|
+
- `ALLOWED_ORIGINS` (required for production)
|
|
560
|
+
|
|
561
|
+
**Optional Environment Variables:**
|
|
562
|
+
- `TURNSTILE_SECRET_KEY` or `VITE_TURNSTILE_SECRET_KEY`
|
|
563
|
+
- `VITE_CF_ACCESS_CLIENT_ID` or `CF_ACCESS_CLIENT_ID`
|
|
564
|
+
- `VITE_CF_ACCESS_CLIENT_SECRET` or `CF_ACCESS_CLIENT_SECRET`
|
|
565
|
+
|
|
566
|
+
### React Components
|
|
567
|
+
|
|
568
|
+
#### `<GravityForm />`
|
|
569
|
+
|
|
570
|
+
**Props:**
|
|
571
|
+
- `formId` (number, required) - Gravity Forms form ID
|
|
572
|
+
- `config` (GravityFormsConfig, required) - Configuration object
|
|
573
|
+
- `className` (string, optional) - Additional CSS classes
|
|
574
|
+
- `onSubmit` (function, optional) - Success callback
|
|
575
|
+
- `onError` (function, optional) - Error callback
|
|
576
|
+
|
|
577
|
+
#### `<WordPressContent />`
|
|
578
|
+
|
|
579
|
+
**Props:**
|
|
580
|
+
- `content` (string, required) - WordPress HTML content
|
|
581
|
+
- `className` (string, optional) - Additional CSS classes
|
|
582
|
+
|
|
583
|
+
#### `<TurnstileWidget />`
|
|
584
|
+
|
|
585
|
+
**Props:**
|
|
586
|
+
- `onVerify` (function, required) - Called with token when verified
|
|
587
|
+
- `onError` (function, optional) - Called on verification error
|
|
588
|
+
|
|
589
|
+
### React Hooks
|
|
590
|
+
|
|
591
|
+
#### `useWordPress()`
|
|
592
|
+
|
|
593
|
+
Returns WordPress static data.
|
|
594
|
+
|
|
595
|
+
**Returns:**
|
|
596
|
+
```typescript
|
|
597
|
+
{
|
|
598
|
+
posts: WordPressPost[];
|
|
599
|
+
pages: WordPressPage[];
|
|
600
|
+
media: WordPressMedia[];
|
|
601
|
+
categories: WordPressCategory[];
|
|
602
|
+
tags: WordPressTag[];
|
|
603
|
+
loading: boolean;
|
|
604
|
+
error: Error | null;
|
|
605
|
+
}
|
|
606
|
+
```
|
|
114
607
|
|
|
608
|
+
#### `useGravityForms(formId, config)`
|
|
115
609
|
|
|
610
|
+
Returns Gravity Forms data and submission function.
|
|
611
|
+
|
|
612
|
+
**Parameters:**
|
|
613
|
+
- `formId` (number, required) - Form ID
|
|
614
|
+
- `config` (GravityFormsConfig, required) - Configuration
|
|
615
|
+
|
|
616
|
+
**Returns:**
|
|
617
|
+
```typescript
|
|
618
|
+
{
|
|
619
|
+
form: GravityForm | null;
|
|
620
|
+
loading: boolean;
|
|
621
|
+
error: Error | null;
|
|
622
|
+
submitting: boolean;
|
|
623
|
+
result: any | null;
|
|
624
|
+
submitForm: (data: GravityFormSubmission) => Promise<any>;
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
## Examples
|
|
629
|
+
|
|
630
|
+
### Complete Setup Example
|
|
631
|
+
|
|
632
|
+
See the `bnibrilliance-website` implementation for a complete, production-ready example:
|
|
633
|
+
|
|
634
|
+
- Build scripts: `scripts/generateWordPressData.ts`, `scripts/generateGravityFormsData.ts`
|
|
635
|
+
- Cloudflare Pages Function: `functions/api/gravity-forms-submit.ts`
|
|
636
|
+
- React components using the package
|
|
637
|
+
- Environment variable configuration
|
|
638
|
+
|
|
639
|
+
### Basic Form Implementation
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
import { GravityForm } from '@marvalt/wadapter';
|
|
643
|
+
|
|
644
|
+
function ContactPage() {
|
|
645
|
+
return (
|
|
646
|
+
<div>
|
|
647
|
+
<h1>Contact Us</h1>
|
|
648
|
+
<GravityForm
|
|
649
|
+
formId={1}
|
|
650
|
+
config={{
|
|
651
|
+
apiUrl: import.meta.env.VITE_WORDPRESS_API_URL,
|
|
652
|
+
authMode: 'direct',
|
|
653
|
+
}}
|
|
654
|
+
onSubmit={(result) => {
|
|
655
|
+
alert(result.confirmation?.message || 'Thank you!');
|
|
656
|
+
}}
|
|
657
|
+
onError={(error) => {
|
|
658
|
+
alert('Error: ' + error.message);
|
|
659
|
+
}}
|
|
660
|
+
/>
|
|
661
|
+
</div>
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Custom Form with Manual Submission
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import { useGravityForms } from '@marvalt/wadapter';
|
|
670
|
+
|
|
671
|
+
function CustomForm() {
|
|
672
|
+
const { form, loading, submitForm } = useGravityForms(1, {
|
|
673
|
+
apiUrl: import.meta.env.VITE_WORDPRESS_API_URL,
|
|
674
|
+
authMode: 'direct',
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
678
|
+
e.preventDefault();
|
|
679
|
+
const formData = new FormData(e.target as HTMLFormElement);
|
|
680
|
+
|
|
681
|
+
const fieldValues: Record<string, string> = {};
|
|
682
|
+
formData.forEach((value, key) => {
|
|
683
|
+
fieldValues[key] = value.toString();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
const result = await submitForm({
|
|
688
|
+
form_id: 1,
|
|
689
|
+
field_values: fieldValues,
|
|
690
|
+
});
|
|
691
|
+
console.log('Success:', result);
|
|
692
|
+
} catch (error) {
|
|
693
|
+
console.error('Error:', error);
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
if (loading) return <div>Loading form...</div>;
|
|
698
|
+
if (!form) return <div>Form not found</div>;
|
|
699
|
+
|
|
700
|
+
return (
|
|
701
|
+
<form onSubmit={handleSubmit}>
|
|
702
|
+
{/* Render form fields based on form.fields */}
|
|
703
|
+
<button type="submit">Submit</button>
|
|
704
|
+
</form>
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
## Troubleshooting
|
|
710
|
+
|
|
711
|
+
### Build Errors
|
|
712
|
+
|
|
713
|
+
**Error: Module "fs" has been externalized for browser compatibility**
|
|
714
|
+
|
|
715
|
+
**Solution**: You're importing generators from the main package. Use the `/generators` export instead:
|
|
716
|
+
|
|
717
|
+
```typescript
|
|
718
|
+
// ❌ Wrong
|
|
719
|
+
import { generateWordPressData } from '@marvalt/wadapter';
|
|
720
|
+
|
|
721
|
+
// ✅ Correct
|
|
722
|
+
import { generateWordPressData } from '@marvalt/wadapter/generators';
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Form Submission Errors
|
|
726
|
+
|
|
727
|
+
**Error: 403 Forbidden**
|
|
728
|
+
|
|
729
|
+
**Possible causes:**
|
|
730
|
+
1. `ALLOWED_ORIGINS` not set in Cloudflare Pages environment
|
|
731
|
+
2. Turnstile token missing or invalid (if Turnstile is enabled)
|
|
732
|
+
3. Endpoint pattern mismatch
|
|
733
|
+
|
|
734
|
+
**Solutions:**
|
|
735
|
+
1. Set `ALLOWED_ORIGINS` in Cloudflare Pages dashboard: `Settings → Environment Variables`
|
|
736
|
+
2. Verify `VITE_TURNSTILE_SITE_KEY` is set and Turnstile widget is rendering
|
|
737
|
+
3. Check that form ID matches the endpoint pattern
|
|
738
|
+
|
|
739
|
+
**Error: 404 Not Found**
|
|
740
|
+
|
|
741
|
+
**Possible causes:**
|
|
742
|
+
1. Gravity Forms API Endpoint plugin not installed/activated
|
|
743
|
+
2. Incorrect API URL
|
|
744
|
+
|
|
745
|
+
**Solutions:**
|
|
746
|
+
1. Verify plugin is installed: Visit `https://your-site.com/wp-json/gf-api/v1/health`
|
|
747
|
+
2. Check `VITE_WORDPRESS_API_URL` is correct
|
|
748
|
+
3. Verify WordPress REST API is enabled (Settings → Permalinks)
|
|
749
|
+
|
|
750
|
+
### Data Generation Errors
|
|
751
|
+
|
|
752
|
+
**Error: 401 Unauthorized**
|
|
753
|
+
|
|
754
|
+
**Possible causes:**
|
|
755
|
+
1. Incorrect WordPress credentials
|
|
756
|
+
2. Cloudflare Access credentials missing (if WordPress is behind CF Access)
|
|
757
|
+
|
|
758
|
+
**Solutions:**
|
|
759
|
+
1. Verify `VITE_WP_API_USERNAME` and `VITE_WP_APP_PASSWORD` are correct
|
|
760
|
+
2. If using Cloudflare Access, set `VITE_CF_ACCESS_CLIENT_ID` and `VITE_CF_ACCESS_CLIENT_SECRET`
|
|
761
|
+
|
|
762
|
+
**Error: Empty or incomplete data**
|
|
763
|
+
|
|
764
|
+
**Possible causes:**
|
|
765
|
+
1. `includeEmbedded: false` (should always be `true` for full functionality)
|
|
766
|
+
2. `maxItems` too low
|
|
767
|
+
3. Post types not enabled
|
|
768
|
+
|
|
769
|
+
**Solutions:**
|
|
770
|
+
1. Always set `includeEmbedded: true` in generator config
|
|
771
|
+
2. Increase `VITE_DEFAULT_MAX_ITEMS` if needed
|
|
772
|
+
3. Verify `VITE_ENABLED_POST_TYPES` includes all needed post types
|
|
773
|
+
|
|
774
|
+
### Turnstile Issues
|
|
775
|
+
|
|
776
|
+
**Widget not rendering**
|
|
777
|
+
|
|
778
|
+
**Possible causes:**
|
|
779
|
+
1. `VITE_TURNSTILE_SITE_KEY` not set
|
|
780
|
+
2. Turnstile script not loaded
|
|
781
|
+
|
|
782
|
+
**Solutions:**
|
|
783
|
+
1. Set `VITE_TURNSTILE_SITE_KEY` in environment variables
|
|
784
|
+
2. The widget automatically loads the Turnstile script - verify no CSP blocks it
|
|
785
|
+
|
|
786
|
+
**Verification always fails**
|
|
787
|
+
|
|
788
|
+
**Possible causes:**
|
|
789
|
+
1. `TURNSTILE_SECRET_KEY` incorrect
|
|
790
|
+
2. Token expired (tokens expire after 5 minutes)
|
|
791
|
+
|
|
792
|
+
**Solutions:**
|
|
793
|
+
1. Verify `TURNSTILE_SECRET_KEY` matches your Cloudflare Turnstile configuration
|
|
794
|
+
2. Ensure form is submitted within 5 minutes of Turnstile verification
|
|
795
|
+
|
|
796
|
+
## License
|
|
116
797
|
|
|
798
|
+
GPL-3.0-or-later
|
|
117
799
|
|
|
800
|
+
---
|
|
118
801
|
|
|
802
|
+
**Need help?** Check the implementation example in `bnibrilliance-website` or review the source code in the `src` directory.
|