Skip to content

Commit 9f981db

Browse files
authored
Merge branch 'main-convert' into api-serving-update-6390
2 parents 892ff4c + 2462887 commit 9f981db

36 files changed

Lines changed: 3480 additions & 1653 deletions

.env.example

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# BrowserStack credentails
2-
BROWSER_STACK_USERNAME=
3-
BROWSER_STACK_ACCESS=
1+
# Convert Staging SDK Key
2+
CONVERT_STAGING_SDK_KEY=
3+
CONVERT_STAGING_SDK_KEY2=
4+
CONVERT_STAGING_SDK_KEY2_SECRET=
45

56
# Logger
67
LOG_LEVEL=2

.github/workflows/publish-package.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ jobs:
8888
(cd packages/experience && npm publish --access public)
8989
env:
9090
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
91+
- if: ${{ startsWith(env.RELEASE_TAG, 'js-sdk-cloudflare-v') }}
92+
run: |
93+
yarn cloudflare:build
94+
(cd packages/cloudflare && npm publish --access public)
95+
env:
96+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
9197
- if: ${{ startsWith(env.RELEASE_TAG, 'js-sdk-v') }}
9298
run: |
9399
yarn sdk:build

.github/workflows/qa.yml

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,37 @@ jobs:
1717
runs-on: ubuntu-latest
1818
strategy:
1919
matrix:
20-
node: [18]
20+
node: [22]
2121
# Steps represent a sequence of tasks that will be executed as part of the job
2222
steps:
23-
- uses: actions/setup-node@v1
23+
- uses: actions/checkout@v4
2424
with:
25-
# The Node.js version to configure
26-
node-version: ${{ matrix.node }}
25+
fetch-depth: 2
2726

28-
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
29-
- uses: actions/checkout@v2
27+
- name: Setup Node
28+
uses: actions/setup-node@v4
3029
with:
31-
fetch-depth: 2
32-
- name: Install needed libraries and packages
30+
node-version: ${{ matrix.node }}
31+
32+
- name: Setup Yarn
33+
run: |
34+
corepack enable
35+
corepack prepare yarn@stable --activate
36+
37+
- name: Install Playwright browsers
3338
run: |
34-
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
35-
sudo sh -c 'echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
36-
sudo apt-get update
37-
sudo apt-get install -y google-chrome-stable
39+
cd packages/js-sdk
40+
npx playwright install --with-deps chromium
3841
3942
- name: Runs the SDK QA checks
43+
env:
44+
CONVERT_STAGING_SDK_KEY: ${{ secrets.CONVERT_STAGING_SDK_KEY }}
45+
CONVERT_STAGING_SDK_KEY2: ${{ secrets.CONVERT_STAGING_SDK_KEY2 }}
46+
CONVERT_STAGING_SDK_KEY2_SECRET: ${{ secrets.CONVERT_STAGING_SDK_KEY2_SECRET }}
4047
run: |
41-
yarn set version berry
4248
yarn
4349
cd packages/js-sdk
4450
yarn lint
45-
yarn test
51+
yarn build
52+
yarn test:mocha
53+
yarn test:browser

.github/workflows/release-please.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ jobs:
5656
packages/data) yarn data:build && (cd packages/data && npm publish --access public) ;;
5757
packages/experience) yarn experience:build && (cd packages/experience && npm publish --access public) ;;
5858
packages/js-sdk) yarn sdk:build && (cd packages/js-sdk && npm publish --access public) ;;
59+
packages/cloudflare) yarn cloudflare:build && (cd packages/cloudflare && npm publish --access public) ;;
5960
*) echo "Unknown path: $REL_PATH" && exit 1 ;;
6061
esac

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ lib/
1212
dist/
1313
docs/
1414
coverage/
15+
test-results/
1516
packages/demo-*
1617
.next
1718
demo/remixjs-server-side/build

.release-please-manifest.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
"packages/segments": "2.1.2",
1111
"packages/api": "2.1.4",
1212
"packages/data": "3.3.3",
13-
"packages/experience": "2.3.2"
13+
"packages/experience": "2.3.2",
14+
"packages/cloudflare": "1.0.0"
1415
}

demo/cloudflare-workers/README.md

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# Convert SDK — Cloudflare Workers Demo
2+
3+
Run Convert A/B tests at the Cloudflare edge with zero client-side flicker. This demo works out of the box with the Convert staging project (`10035569/10034190`) — the same credentials used by all other SDK demos.
4+
5+
## Routes
6+
7+
| Route | Location | Experiments | Features |
8+
|-------|----------|-------------|----------|
9+
| `/` || None (home page) ||
10+
| `/events` | `events` | `test-experience-ab-fullstack-1` ||
11+
| `/statistics` | `statistics` | All matching | `feature-4` |
12+
| `/pricing` | `pricing` | All matching | `feature-5` |
13+
14+
## How It Works
15+
16+
```
17+
Visitor → Cloudflare Worker (localhost:8787)
18+
├── Fetch config from Convert CDN (edge-cached, ~1-5ms)
19+
├── Map URL path to location property (e.g. /events → "events")
20+
├── SDK: createContext → runExperience/runExperiences
21+
├── Fetch origin page (localhost:8888)
22+
├── HTMLRewriter: inject experiment results into HTML
23+
├── Set visitor cookie + cache headers
24+
└── Respond (total overhead: ~5-8ms)
25+
26+
└── Background (waitUntil):
27+
└── Flush tracking events to Convert
28+
```
29+
30+
No KV namespace is required. Config is cached using Cloudflare's native `cf.cacheTtl` fetch option (free, all plans). Visitor bucketing is deterministic — the same visitor ID always gets the same variation via a cookie.
31+
32+
## Setup
33+
34+
### 1. Install Dependencies
35+
36+
From the monorepo root:
37+
38+
```bash
39+
yarn install
40+
```
41+
42+
If the cloudflare package hasn't been built yet:
43+
44+
```bash
45+
yarn cloudflare:build
46+
```
47+
48+
You don't need to publish `@convertcom/js-sdk-cloudflare` to npm — Yarn workspaces symlinks it to the local `packages/cloudflare/` directory, and Wrangler's bundler follows symlinks.
49+
50+
### 2. Start the Origin Server
51+
52+
The demo includes a simple origin server (`origin/`) that serves HTML pages for each route. The Worker proxies to this origin and modifies the response with experiment results.
53+
54+
```bash
55+
# Terminal 1
56+
cd demo/cloudflare-workers
57+
yarn origin
58+
```
59+
60+
This starts on http://localhost:8888 with pages for `/`, `/events`, `/statistics`, and `/pricing`.
61+
62+
### 3. Start the Worker
63+
64+
```bash
65+
# Terminal 2
66+
cd demo/cloudflare-workers
67+
yarn dev
68+
```
69+
70+
This starts on http://localhost:8787.
71+
72+
### 4. Open in Browser
73+
74+
Visit http://localhost:8787/ and navigate to the different routes:
75+
76+
- **Events** — Shows the bucketed variation for `test-experience-ab-fullstack-1`
77+
- **Statistics** — Shows all matching variations + `feature-4` status
78+
- **Pricing** — Shows all matching variations (both experiences) + `feature-5` status
79+
80+
## Verification
81+
82+
### Config Fetch
83+
84+
Confirm the staging project config is accessible:
85+
86+
```bash
87+
curl -s "https://cdn-4.convertexperiments.com/api/v1/config/10035569/10034190" | head -c 200
88+
```
89+
90+
Should return JSON starting with `{"account_id":"10035569"...`.
91+
92+
### Visitor Cookie
93+
94+
```bash
95+
curl -v http://localhost:8787/events 2>&1 | grep -i set-cookie
96+
```
97+
98+
Expected: `Set-Cookie: convert_visitor_id=<uuid>; Path=/; ...`
99+
100+
### Deterministic Bucketing
101+
102+
```bash
103+
# Get a visitor ID
104+
VISITOR_ID=$(curl -s -D- http://localhost:8787/events 2>&1 | grep -io 'convert_visitor_id=[^;]*' | cut -d= -f2)
105+
echo "Visitor: $VISITOR_ID"
106+
107+
# Same cookie → same variation every time
108+
curl -s -b "convert_visitor_id=$VISITOR_ID" http://localhost:8787/events
109+
curl -s -b "convert_visitor_id=$VISITOR_ID" http://localhost:8787/events
110+
```
111+
112+
Both responses should show identical experiment results.
113+
114+
### HTMLRewriter
115+
116+
Compare the origin page vs the Worker-modified page:
117+
118+
```bash
119+
# Origin (unmodified)
120+
curl -s http://localhost:8888/events | grep experiment-results
121+
122+
# Worker (modified with bucketing results)
123+
curl -s http://localhost:8787/events | grep experiment-results
124+
```
125+
126+
The Worker response should contain variation names/keys instead of the placeholder text.
127+
128+
### Cache Headers
129+
130+
```bash
131+
curl -s -D- http://localhost:8787/events 2>&1 | grep -iE 'cache-control|vary'
132+
```
133+
134+
Expected:
135+
136+
```
137+
Cache-Control: public, max-age=300
138+
Vary: Cookie
139+
```
140+
141+
### Tracking Events
142+
143+
Watch the Worker logs while making requests:
144+
145+
```bash
146+
# Terminal 3
147+
cd demo/cloudflare-workers
148+
yarn tail
149+
```
150+
151+
After visiting a page, you should see the SDK POST tracking data to Convert. In the Convert dashboard, the visitor count for the experience should increment.
152+
153+
### Error Fallback
154+
155+
Temporarily set an invalid SDK key in `wrangler.toml`:
156+
157+
```toml
158+
CONVERT_SDK_KEY = "invalid/key"
159+
```
160+
161+
Restart `yarn dev` and visit http://localhost:8787/events — should return the unmodified origin page (graceful degradation via try/catch).
162+
163+
### Bundle Validation
164+
165+
```bash
166+
cd demo/cloudflare-workers
167+
npx wrangler deploy --dry-run --outdir /tmp/cf-bundle
168+
```
169+
170+
Should succeed with bundle size ~296 KiB / ~58 KiB gzipped.
171+
172+
## Checklist
173+
174+
| Test | What to Verify |
175+
|------|---------------|
176+
| Config fetch | `curl` to CDN config endpoint returns JSON |
177+
| Wrangler bundle | `--dry-run` succeeds |
178+
| Worker starts | `yarn dev` runs without errors |
179+
| Origin server | `yarn origin` serves pages on port 8888 |
180+
| New visitor cookie | `Set-Cookie: convert_visitor_id=<uuid>` in response |
181+
| Deterministic bucketing | Same cookie → same variation on repeated requests |
182+
| HTMLRewriter | Experiment results injected into HTML |
183+
| Cache headers | `Cache-Control: public, max-age=300` + `Vary: Cookie` |
184+
| Tracking events | Visitor count increments in Convert dashboard |
185+
| Error fallback | Invalid SDK key → unmodified origin page returned |
186+
| No KV required | All above works without any KV namespace configured |
187+
188+
## Configuration
189+
190+
The demo is pre-configured in `wrangler.toml`:
191+
192+
```toml
193+
CONVERT_SDK_KEY = "10035569/10034190" # Staging project
194+
ORIGIN_URL = "http://localhost:8888" # Local origin server
195+
```
196+
197+
To use your own project, update these values and adjust the experience/feature keys in `src/index.ts`.
198+
199+
## Iterating on Package Changes
200+
201+
If you modify `packages/cloudflare/src/` files:
202+
203+
```bash
204+
# Rebuild the package
205+
yarn cloudflare:build
206+
207+
# Restart the Worker (Ctrl+C, then yarn dev again)
208+
```
209+
210+
## Production Deployment
211+
212+
**Important:** The default `ORIGIN_URL` is `http://localhost:8888` which only works for local development. Deploying with this value will fail because `localhost` is not reachable from Cloudflare's network (you'll see a Cloudflare "Error 1003: Direct IP access not allowed" page).
213+
214+
For production, update `ORIGIN_URL` in `wrangler.toml` to a publicly accessible origin:
215+
216+
```toml
217+
ORIGIN_URL = "https://your-site.com"
218+
```
219+
220+
Then deploy:
221+
222+
```bash
223+
yarn deploy
224+
```
225+
226+
## Optional: KV-Backed Persistence
227+
228+
If you need to preserve bucketing across experience config changes or store custom visitor attributes, you can optionally add KV support. See the commented section at the bottom of `src/index.ts` for setup instructions.
229+
230+
## Troubleshooting
231+
232+
**"Cannot find module @convertcom/js-sdk-cloudflare"**
233+
→ Run `yarn install` at the monorepo root to ensure workspace symlinks exist, then `yarn cloudflare:build`.
234+
235+
**"Convert config fetch failed: 404"**
236+
→ Verify your SDK key. Test directly: `curl https://cdn-4.convertexperiments.com/api/v1/config/ACCOUNT_ID/PROJECT_ID`
237+
238+
**Experience returns null / string (RuleError)**
239+
→ The experience key doesn't match, or location/audience rules don't match. Check that the experience is **Active** (not Draft/Paused) in Convert.
240+
241+
**HTMLRewriter changes not visible**
242+
→ Add `console.log('variation:', variation)` in the Worker and check Wrangler logs to confirm bucketing.
243+
244+
**Tracking events not appearing in dashboard**
245+
→ Confirm `ctx.waitUntil(context.releaseQueues(...))` is called. Check Wrangler logs for outbound POST requests.
246+
247+
**Page hangs / infinite loop**
248+
→ All `fetch(request)` calls must go through `fetchOrigin()` which rewrites the URL to `ORIGIN_URL`. Direct `fetch(request)` loops back to the Worker.
249+
250+
## Documentation
251+
252+
Full guide: [Cloudflare Workers Edge Experimentation](https://github.com/convertcom/javascript-sdk/wiki/CloudflareWorkers)

0 commit comments

Comments
 (0)