Skip to content

Commit

Permalink
Optimizely SDK in EF + Next
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlee85 committed May 15, 2024
1 parent e9abcef commit 889f278
Show file tree
Hide file tree
Showing 23 changed files with 15,695 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/v7-optimizely-edge/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OPTIMIZELY_SDK_KEY=your_sdk_key
3 changes: 3 additions & 0 deletions examples/v7-optimizely-edge/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
5 changes: 5 additions & 0 deletions examples/v7-optimizely-edge/.github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**NOTICE TO CONTRIBUTORS**

This repository is not actively monitored and any pull requests made to this repository will be closed/ignored.

Please submit the pull request to [edgio-docs/edgio-examples](https://github.com/edgio-docs/edgio-examples) instead.
18 changes: 18 additions & 0 deletions examples/v7-optimizely-edge/.github/workflows/edgio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Deploy to Edgio

on:
workflow_dispatch:
push:

jobs:
deploy-to-edgio:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: if [ -f yarn.lock ]; then yarn install; else npm ci; fi
- run: if [ -f yarn.lock ]; then yarn edgio:deploy -- --token=$EDGIO_DEPLOY_TOKEN; else npm run edgio:deploy -- --token=$EDGIO_DEPLOY_TOKEN; fi
env:
EDGIO_DEPLOY_TOKEN: ${{secrets.EDGIO_DEPLOY_TOKEN}}
41 changes: 41 additions & 0 deletions examples/v7-optimizely-edge/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# Edgio generated build directory
/.edgio
/node_modules
lib/optimizely/*
36 changes: 36 additions & 0 deletions examples/v7-optimizely-edge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

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.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
202 changes: 202 additions & 0 deletions examples/v7-optimizely-edge/edge-functions/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import {
createInstance,
eventDispatcher,
} from '@optimizely/optimizely-sdk/dist/optimizely.lite.min.js';
import optimizelyDatafile from '../lib/optimizely/datafile.json';

const CLIENT_ENGINE = 'EDGIO_EF';
const COOKIE_NAME = 'optimizely_visitor_id';

/**
* An example edge function which forwards the request to the origin.
* See routes.js for how this function is configured to run for requests to "/".
*/

export async function handleHttpRequest(request, context) {
// Fetch user Id from the cookie if available so a returning user from same browser session always sees the same variation.
const userId =
request.headers
.get('Cookie')
?.split(';')
.find((cookie) => cookie.trim().startsWith(`${COOKIE_NAME}=`))
?.split('=')[1] || `user-${new Date().getTime()}`;

console.log(JSON.stringify(context, null, 2));
const url = new URL('/', request.url);
const resp = await fetch(url, {
edgio: {
origin: 'edgio_self',
},
});

// handle the response as needed
// For example, to inject some html into the body:
const html = await resp.text();

// Create Optimizely instance using datafile downloaded at build time.
const instance = createInstance({
datafile: optimizelyDatafile,
clientEngine: CLIENT_ENGINE,
eventDispatcher,
});

// Return the original HTML if the instance is not created.
if (!instance) {
return resp;
}

await instance.onReady();

// Create Optimizely User Context
const userContext = instance.createUserContext(userId.toString());

// Decide variation for the flag.
const decision = userContext.decide('foo_flag');

console.log(`[OPTIMIZELY] userId: ${userId}`);
console.log(
`[OPTIMIZELY] flag 'foo_flag' is ${
decision.enabled ? 'enabled' : 'disabled'
} for the user ${userId}`
);

// To send the response to the client with the new HTML but the same headers as the origin response:
return new Response(html, {
...resp,
headers: {
...resp.headers,
'x-edge-function': 'main.js',
},
});
}

// Check if setTimeout is already available (in case of running in an environment that has it)

let timers = new Map();
let nextTimerId = 1;

(function () {
var timerQueue = [];
var nextTimerId = 0;

function runTimers() {
var now = Date.now();
var nextCheck = null;

// Run due timers
for (var i = 0; i < timerQueue.length; i++) {
var timer = timerQueue[i];
if (timer.time <= now) {
timer.callback.apply(null, timer.args);
if (timer.repeating) {
timer.time = now + timer.delay; // schedule next run
nextCheck =
nextCheck !== null ? Math.min(nextCheck, timer.time) : timer.time;
} else {
timerQueue.splice(i--, 1); // remove non-repeating timer
}
} else {
nextCheck =
nextCheck !== null ? Math.min(nextCheck, timer.time) : timer.time;
}
}

// Schedule next check
if (nextCheck !== null) {
var delay = Math.max(nextCheck - Date.now(), 0);
setTimeout(runTimers, delay);
}
}

global.setTimeout = function (callback, delay, ...args) {
var timerId = ++nextTimerId;
var timer = {
id: timerId,
callback: callback,
time: Date.now() + delay,
args: args,
repeating: false,
delay: delay,
};
timerQueue.push(timer);
return timerId;
};

global.clearTimeout = function (timerId) {
for (var i = 0; i < timerQueue.length; i++) {
if (timerQueue[i].id === timerId) {
timerQueue.splice(i, 1);
break;
}
}
};

global.queueMicrotask = function (callback) {
Promise.resolve()
.then(callback)
.catch((err) =>
setTimeout(() => {
throw err;
})
);
};

setTimeout(runTimers, 0);
})();

//@ts-ignore

// export async function middleware(req: NextRequest, ev: NextFetchEvent) {
// // Fetch user Id from the cookie if available so a returning user from same browser session always sees the same variation.
// const userId = req.cookies.get(COOKIE_NAME)?.value || crypto.randomUUID()

// // Create Optimizely instance using datafile downloaded at build time.
// const instance = createInstance({
// datafile: optimizelyDatafile,
// clientEngine: VERCEL_EDGE_CLIENT_ENGINE,
// eventDispatcher: {
// dispatchEvent: ({ url, params }: { url: string; params: any }) => {
// // Tell edge function to wait for this promise to fullfill.
// ev.waitUntil(
// fetch(url, {
// method: 'POST',
// body: JSON.stringify(params),
// })
// )
// },
// },
// })

// // Create Optimizely User Context
// const userContext = instance!.createUserContext(userId.toString())

// // Decide variation for the flag.
// const decision = userContext!.decide('product_sort')

// // Fetch datafile revision for debugging.
// const revision = instance!.getOptimizelyConfig()!.revision

// console.log(`[OPTIMIZELY] Datafile Revision: ${revision}`)
// console.log(`[OPTIMIZELY] userId: ${userId}`)
// console.log(
// `[OPTIMIZELY] flag 'product_sort' is ${
// decision.enabled ? 'enabled' : 'disabled'
// } for the user ${userId}`
// )
// console.log(
// `[OPTIMIZELY] User ${userId} was bucketed in to variation ${decision.variationKey}`
// )
// console.log(`[OPTIMIZELY] sort_method is ${decision.variables.sort_method}`)

// // Rewriting the path based on sort_method. The default is Alphabetical.
// req.nextUrl.pathname =
// decision.variables.sort_method === 'popular_first' ? '/popular' : '/'
// let res = NextResponse.rewrite(req.nextUrl)

// if (!req.cookies.has(COOKIE_NAME)) {
// // Saving userId in the cookie so that the decision sticks for subsequent visits.
// res.cookies.set(COOKIE_NAME, userId)
// }

// return res
// }
Loading

0 comments on commit 889f278

Please sign in to comment.