-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #138 from edgio-docs/COMSUP-338_v7_optimizely
Optimizely SDK in EF + Next
- Loading branch information
Showing
16 changed files
with
12,612 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
OPTIMIZELY_SDK_KEY=your_sdk_key |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.js | ||
.yarn/install-state.gz | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
|
||
# local env files | ||
.env*.local | ||
|
||
# Edgio generated build directory | ||
/.edgio | ||
/node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Edge-side Experiments with Optimizely | ||
|
||
This example demonstrates how you may use edge functions to run Optimizely experiments. | ||
|
||
**Preview**: [https://edgio-community-examples-v7-optimizely-edge-live.glb.edgio.link/](https://edgio-community-examples-v7-optimizely-edge-live.glb.edgio.link/) | ||
|
||
The request workflow is as follows: | ||
|
||
- The client makes a request to `/` which is handled by an edge function. | ||
- The edge function fetches the Optimizely experiment variant and decides which text direction to use based on the variant. | ||
- The edge function makes another fetch request to the Wikipedia homepage and gets the HTML content. | ||
- The edge function modifies the HTML content to include the experiment variant. | ||
- The edge function sets the experiment variant cookie and returns the page to the client. | ||
- Depending on the experiment variant, the page may render normal or mirrored based on the applied transformation. | ||
|
||
## Getting Started | ||
|
||
1. Clone the repository: | ||
|
||
```bash | ||
git clone ... | ||
``` | ||
|
||
2. Install the dependencies: | ||
|
||
```bash | ||
npm install | ||
``` | ||
|
||
3. Create a `.env` file in the root of the project and add the following environment variables: | ||
|
||
```bash | ||
OPTIMIZELY_SDK_KEY=... | ||
``` | ||
|
||
4. Start the development server: | ||
|
||
```bash | ||
npm run edgio:dev | ||
``` | ||
|
||
5. Open [http://localhost:3000/](http://localhost:3000/) with your browser to see the result. Note: The page may render normally or mirrored based on the experiment variant. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import '../lib/polyfills'; // Necessary polyfills for the edge function scope | ||
import { | ||
createInstance, | ||
eventDispatcher, | ||
} from '@optimizely/optimizely-sdk/dist/optimizely.lite.min.js'; | ||
import optimizelyDatafile from '../lib/optimizely/datafile.json'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
|
||
// Constants for Optimizely client configuration | ||
const CLIENT_ENGINE = 'EDGIO_EF'; | ||
const COOKIE_NAME = 'optimizely_visitor_id'; | ||
|
||
/** | ||
* Handles incoming HTTP requests and applies A/B testing using Optimizely. | ||
* | ||
* @param {Request} request - The incoming HTTP request. | ||
* @param {Object} context - The context for this handler | ||
* @returns {Response} The HTTP response after applying A/B testing logic. | ||
*/ | ||
export async function handleHttpRequest(request, context) { | ||
// Retrieve or generate a unique user ID from cookies | ||
const userId = | ||
request.headers | ||
.get('Cookie') | ||
?.split(';') | ||
.find((cookie) => cookie.trim().startsWith(`${COOKIE_NAME}=`)) | ||
?.split('=')[1] || uuidv4(); | ||
|
||
// Create an Optimizely instance with the preloaded datafile and configuration. | ||
// This edge function uses the Optimizely SDK Lite which requires a preloaded datafile. | ||
const instance = createInstance({ | ||
datafile: optimizelyDatafile, | ||
clientEngine: CLIENT_ENGINE, | ||
eventDispatcher, | ||
}); | ||
|
||
// Early exit if the Optimizely instance isn't properly created | ||
if (!instance) { | ||
return new Response('Optimizely instance unavailable.', { status: 500 }); | ||
} | ||
|
||
await instance.onReady(); // Ensures the Optimizely instance is ready before proceeding | ||
|
||
// Create a user context for the retrieved or generated user ID | ||
const userContext = instance.createUserContext(userId.toString()); | ||
|
||
// Make a decision using Optimizely for the 'text_direction' feature | ||
const decision = userContext.decide('text_direction'); | ||
const textDir = decision.enabled ? 1 : -1; // Determine text direction based on decision | ||
|
||
console.log(`[OPTIMIZELY] User ID: ${userId}, Text Direction: ${textDir}`); | ||
|
||
// Fetch the homepage of Wikipedia | ||
const url = new URL('https://en.wikipedia.org'); | ||
const response = await fetch(url.toString(), { | ||
edgio: { origin: 'wikipedia' }, | ||
}); | ||
|
||
// Update the `<body>` tag with the text direction based on the Optimizely decision | ||
const bodyTagRegex = /<body([^>]*)>/i; | ||
const bodyTagReplacement = `<body style="transform: scaleX(${textDir});"$1>`; | ||
const body = await response.text(); | ||
const updatedBody = body.replace(bodyTagRegex, bodyTagReplacement); | ||
|
||
// Create a new response with the updated body content | ||
const updatedResponse = new Response(updatedBody, response); | ||
|
||
// Add the user ID to the response headers as a cookie to ensure the user experience consistency | ||
// const cookie = `${COOKIE_NAME}=${userId}; Path=/; Max-Age=31536000; SameSite=Lax`; | ||
// updatedResponse.headers.append('Set-Cookie', cookie); | ||
|
||
return updatedResponse; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// This file was automatically added by edgio init. | ||
// You should commit this file to source control. | ||
// Learn more about this file at https://docs.edg.io/guides/edgio_config | ||
module.exports = { | ||
// The name of the site in Edgio to which this app should be deployed. | ||
// name: 'my-site-name', | ||
|
||
// The name of the organization in Edgio to which this app should be deployed. | ||
// organization: 'my-organization-name', | ||
|
||
// Overrides the default path to the routes file. The path should be relative to the root of your app. | ||
// routes: 'routes.js', | ||
|
||
// When set to true or omitted entirely, Edgio includes the deployment number in the cache key, | ||
// effectively purging the cache each time you deploy. | ||
// purgeCacheOnDeploy: false, | ||
|
||
// If you need to proxy some URLs to an origin instead of your Next.js app, you can configure the origins here: | ||
origins: [ | ||
{ | ||
name: 'wikipedia', | ||
override_host_header: 'en.wikipedia.org', | ||
hosts: [ | ||
{ | ||
location: 'en.wikipedia.org', | ||
}, | ||
], | ||
tls_verify: { | ||
use_sni: true, | ||
allow_self_signed_certs: true, | ||
sni_hint_and_strict_san_check: 'en.wikipedia.org', | ||
}, | ||
}, | ||
], | ||
|
||
// Options for hosting serverless functions on Edgio | ||
// serverless: { | ||
// // Set to true to include all packages listed in the dependencies property of package.json when deploying to Edgio. | ||
// // This option generally isn't needed as Edgio automatically includes all modules imported by your code in the bundle that | ||
// // is uploaded during deployment | ||
// includeNodeModules: true, | ||
// | ||
// // Include additional paths that are dynamically loaded by your app at runtime here when building the serverless bundle. | ||
// include: ['views/**/*'], | ||
// }, | ||
|
||
// The maximum number of URLs that will be concurrently prerendered during deployment when static prerendering is enabled. | ||
// Defaults to 200, which is the maximum allowed value. | ||
// prerenderConcurrency: 200, | ||
|
||
// A list of glob patterns identifying which prerenderConcurrency source files should be uploaded when running edgio deploy --includeSources. | ||
// This option is primarily used to share source code with Edgio support personnel for the purpose of debugging. If omitted, | ||
// edgio deploy --includeSources will result in all files which are not gitignored being uploaded to Edgio. | ||
// | ||
// sources : [ | ||
// '**/*', // include all files | ||
// '!(**/secrets/**/*)', // except everything in the secrets directory | ||
// ], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"accountId":"29260950578","projectId":"29260950578","revision":"7","attributes":[],"audiences":[{"id":"$opt_dummy_audience","name":"Optimizely-Generated Audience for Backwards Compatibility","conditions":"[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]"}],"version":"4","events":[],"integrations":[],"anonymizeIP":true,"botFiltering":false,"typedAudiences":[],"variables":[],"environmentKey":"development","sdkKey":"2imW2dz6yoT3fzLKWFDmz","featureFlags":[{"id":"179326","key":"text_direction","rolloutId":"rollout-179326-29212200416","experimentIds":["9300000941392"],"variables":[]}],"rollouts":[{"id":"rollout-179326-29212200416","experiments":[{"id":"default-rollout-179326-29212200416","key":"default-rollout-179326-29212200416","status":"Running","layerId":"rollout-179326-29212200416","variations":[{"id":"587821","key":"off","featureEnabled":false,"variables":[]}],"trafficAllocation":[{"entityId":"587821","endOfRange":10000}],"forcedVariations":{},"audienceIds":[],"audienceConditions":[]}]}],"experiments":[{"id":"9300000941392","key":"text_direction","status":"Running","layerId":"9300000714177","variations":[{"id":"587821","key":"off","featureEnabled":false,"variables":[]},{"id":"587822","key":"on","featureEnabled":true,"variables":[]}],"trafficAllocation":[{"entityId":"587821","endOfRange":5000},{"entityId":"587822","endOfRange":10000}],"forcedVariations":{},"audienceIds":[],"audienceConditions":[]}],"groups":[]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import CryptoJS from 'crypto-js'; | ||
import getRandomValues from 'polyfill-crypto.getrandomvalues'; | ||
|
||
global.crypto = { | ||
...CryptoJS, | ||
getRandomValues, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import './timer'; | ||
import './crypto'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
let timers = new Map(); | ||
let nextTimerId = 1; | ||
|
||
(function (global) { | ||
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); | ||
})(global); |
Oops, something went wrong.