Skip to content

Commit 889f278

Browse files
committed
Optimizely SDK in EF + Next
1 parent e9abcef commit 889f278

23 files changed

+15695
-0
lines changed

Diff for: examples/v7-optimizely-edge/.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OPTIMIZELY_SDK_KEY=your_sdk_key

Diff for: examples/v7-optimizely-edge/.eslintrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
**NOTICE TO CONTRIBUTORS**
2+
3+
This repository is not actively monitored and any pull requests made to this repository will be closed/ignored.
4+
5+
Please submit the pull request to [edgio-docs/edgio-examples](https://github.com/edgio-docs/edgio-examples) instead.
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Deploy to Edgio
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
7+
jobs:
8+
deploy-to-edgio:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v3
12+
- uses: actions/setup-node@v3
13+
with:
14+
node-version: 18
15+
- run: if [ -f yarn.lock ]; then yarn install; else npm ci; fi
16+
- run: if [ -f yarn.lock ]; then yarn edgio:deploy -- --token=$EDGIO_DEPLOY_TOKEN; else npm run edgio:deploy -- --token=$EDGIO_DEPLOY_TOKEN; fi
17+
env:
18+
EDGIO_DEPLOY_TOKEN: ${{secrets.EDGIO_DEPLOY_TOKEN}}

Diff for: examples/v7-optimizely-edge/.gitignore

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
.yarn/install-state.gz
8+
9+
# testing
10+
/coverage
11+
12+
# next.js
13+
/.next/
14+
/out/
15+
16+
# production
17+
/build
18+
19+
# misc
20+
.DS_Store
21+
*.pem
22+
23+
# debug
24+
npm-debug.log*
25+
yarn-debug.log*
26+
yarn-error.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
37+
38+
# Edgio generated build directory
39+
/.edgio
40+
/node_modules
41+
lib/optimizely/*

Diff for: examples/v7-optimizely-edge/README.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20+
21+
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22+
23+
## Learn More
24+
25+
To learn more about Next.js, take a look at the following resources:
26+
27+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29+
30+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31+
32+
## Deploy on Vercel
33+
34+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35+
36+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

Diff for: examples/v7-optimizely-edge/edge-functions/main.js

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import {
2+
createInstance,
3+
eventDispatcher,
4+
} from '@optimizely/optimizely-sdk/dist/optimizely.lite.min.js';
5+
import optimizelyDatafile from '../lib/optimizely/datafile.json';
6+
7+
const CLIENT_ENGINE = 'EDGIO_EF';
8+
const COOKIE_NAME = 'optimizely_visitor_id';
9+
10+
/**
11+
* An example edge function which forwards the request to the origin.
12+
* See routes.js for how this function is configured to run for requests to "/".
13+
*/
14+
15+
export async function handleHttpRequest(request, context) {
16+
// Fetch user Id from the cookie if available so a returning user from same browser session always sees the same variation.
17+
const userId =
18+
request.headers
19+
.get('Cookie')
20+
?.split(';')
21+
.find((cookie) => cookie.trim().startsWith(`${COOKIE_NAME}=`))
22+
?.split('=')[1] || `user-${new Date().getTime()}`;
23+
24+
console.log(JSON.stringify(context, null, 2));
25+
const url = new URL('/', request.url);
26+
const resp = await fetch(url, {
27+
edgio: {
28+
origin: 'edgio_self',
29+
},
30+
});
31+
32+
// handle the response as needed
33+
// For example, to inject some html into the body:
34+
const html = await resp.text();
35+
36+
// Create Optimizely instance using datafile downloaded at build time.
37+
const instance = createInstance({
38+
datafile: optimizelyDatafile,
39+
clientEngine: CLIENT_ENGINE,
40+
eventDispatcher,
41+
});
42+
43+
// Return the original HTML if the instance is not created.
44+
if (!instance) {
45+
return resp;
46+
}
47+
48+
await instance.onReady();
49+
50+
// Create Optimizely User Context
51+
const userContext = instance.createUserContext(userId.toString());
52+
53+
// Decide variation for the flag.
54+
const decision = userContext.decide('foo_flag');
55+
56+
console.log(`[OPTIMIZELY] userId: ${userId}`);
57+
console.log(
58+
`[OPTIMIZELY] flag 'foo_flag' is ${
59+
decision.enabled ? 'enabled' : 'disabled'
60+
} for the user ${userId}`
61+
);
62+
63+
// To send the response to the client with the new HTML but the same headers as the origin response:
64+
return new Response(html, {
65+
...resp,
66+
headers: {
67+
...resp.headers,
68+
'x-edge-function': 'main.js',
69+
},
70+
});
71+
}
72+
73+
// Check if setTimeout is already available (in case of running in an environment that has it)
74+
75+
let timers = new Map();
76+
let nextTimerId = 1;
77+
78+
(function () {
79+
var timerQueue = [];
80+
var nextTimerId = 0;
81+
82+
function runTimers() {
83+
var now = Date.now();
84+
var nextCheck = null;
85+
86+
// Run due timers
87+
for (var i = 0; i < timerQueue.length; i++) {
88+
var timer = timerQueue[i];
89+
if (timer.time <= now) {
90+
timer.callback.apply(null, timer.args);
91+
if (timer.repeating) {
92+
timer.time = now + timer.delay; // schedule next run
93+
nextCheck =
94+
nextCheck !== null ? Math.min(nextCheck, timer.time) : timer.time;
95+
} else {
96+
timerQueue.splice(i--, 1); // remove non-repeating timer
97+
}
98+
} else {
99+
nextCheck =
100+
nextCheck !== null ? Math.min(nextCheck, timer.time) : timer.time;
101+
}
102+
}
103+
104+
// Schedule next check
105+
if (nextCheck !== null) {
106+
var delay = Math.max(nextCheck - Date.now(), 0);
107+
setTimeout(runTimers, delay);
108+
}
109+
}
110+
111+
global.setTimeout = function (callback, delay, ...args) {
112+
var timerId = ++nextTimerId;
113+
var timer = {
114+
id: timerId,
115+
callback: callback,
116+
time: Date.now() + delay,
117+
args: args,
118+
repeating: false,
119+
delay: delay,
120+
};
121+
timerQueue.push(timer);
122+
return timerId;
123+
};
124+
125+
global.clearTimeout = function (timerId) {
126+
for (var i = 0; i < timerQueue.length; i++) {
127+
if (timerQueue[i].id === timerId) {
128+
timerQueue.splice(i, 1);
129+
break;
130+
}
131+
}
132+
};
133+
134+
global.queueMicrotask = function (callback) {
135+
Promise.resolve()
136+
.then(callback)
137+
.catch((err) =>
138+
setTimeout(() => {
139+
throw err;
140+
})
141+
);
142+
};
143+
144+
setTimeout(runTimers, 0);
145+
})();
146+
147+
//@ts-ignore
148+
149+
// export async function middleware(req: NextRequest, ev: NextFetchEvent) {
150+
// // Fetch user Id from the cookie if available so a returning user from same browser session always sees the same variation.
151+
// const userId = req.cookies.get(COOKIE_NAME)?.value || crypto.randomUUID()
152+
153+
// // Create Optimizely instance using datafile downloaded at build time.
154+
// const instance = createInstance({
155+
// datafile: optimizelyDatafile,
156+
// clientEngine: VERCEL_EDGE_CLIENT_ENGINE,
157+
// eventDispatcher: {
158+
// dispatchEvent: ({ url, params }: { url: string; params: any }) => {
159+
// // Tell edge function to wait for this promise to fullfill.
160+
// ev.waitUntil(
161+
// fetch(url, {
162+
// method: 'POST',
163+
// body: JSON.stringify(params),
164+
// })
165+
// )
166+
// },
167+
// },
168+
// })
169+
170+
// // Create Optimizely User Context
171+
// const userContext = instance!.createUserContext(userId.toString())
172+
173+
// // Decide variation for the flag.
174+
// const decision = userContext!.decide('product_sort')
175+
176+
// // Fetch datafile revision for debugging.
177+
// const revision = instance!.getOptimizelyConfig()!.revision
178+
179+
// console.log(`[OPTIMIZELY] Datafile Revision: ${revision}`)
180+
// console.log(`[OPTIMIZELY] userId: ${userId}`)
181+
// console.log(
182+
// `[OPTIMIZELY] flag 'product_sort' is ${
183+
// decision.enabled ? 'enabled' : 'disabled'
184+
// } for the user ${userId}`
185+
// )
186+
// console.log(
187+
// `[OPTIMIZELY] User ${userId} was bucketed in to variation ${decision.variationKey}`
188+
// )
189+
// console.log(`[OPTIMIZELY] sort_method is ${decision.variables.sort_method}`)
190+
191+
// // Rewriting the path based on sort_method. The default is Alphabetical.
192+
// req.nextUrl.pathname =
193+
// decision.variables.sort_method === 'popular_first' ? '/popular' : '/'
194+
// let res = NextResponse.rewrite(req.nextUrl)
195+
196+
// if (!req.cookies.has(COOKIE_NAME)) {
197+
// // Saving userId in the cookie so that the decision sticks for subsequent visits.
198+
// res.cookies.set(COOKIE_NAME, userId)
199+
// }
200+
201+
// return res
202+
// }

0 commit comments

Comments
 (0)