diff --git a/edgio.config.js b/edgio.config.js index 4b9911e..047fcac 100644 --- a/edgio.config.js +++ b/edgio.config.js @@ -1,6 +1,8 @@ // 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 +require('dotenv').config(); + module.exports = { connector: '@edgio/next', diff --git a/functions/database/upstash/index.js b/functions/database/upstash/index.js new file mode 100644 index 0000000..f9e7102 --- /dev/null +++ b/functions/database/upstash/index.js @@ -0,0 +1,144 @@ +import createFetchForOrigin from '../../../utils/createFetchForOrigin'; +import { + getCookiesFromRequest, + setCookieToResponse, +} from '../../../utils/cookies'; +import { setEnvFromContext } from '../../../utils/polyfills/process.env'; +import '../../../utils/polyfills/URL'; +import waitingPage from './waiting-room-capacity.html'; +import landingPage from './waiting-room-landing.html'; + +// Constants +const COOKIE_NAME_ID = '__booking_id'; +const COOKIE_NAME_TIME = '__booking_last_update_time'; +const TOTAL_ACTIVE_USERS = 2; +const SESSION_DURATION_SECONDS = 15; + +// Setup fetch function for Upstash +const fetch = createFetchForOrigin('upstash'); + +/** + * Main handler for the edge request. + */ +export async function handleHttpRequest(request, context) { + let resp; + + // Set context environment variables to process.env + setEnvFromContext(context); + + const cookies = getCookiesFromRequest(request); + + // Get user ID from cookie or generate a new one + const userId = cookies[COOKIE_NAME_ID] ?? generateId(); + + // Get the current number of active sessions and the active user + const size = await getRecordCount(); + const isActiveUser = (await getRecord(userId)) === '1'; + + console.log('Current number of active sessions: ', size); + + // Check capacity + if (size < TOTAL_ACTIVE_USERS || isActiveUser) { + resp = await getDefaultResponse(request, userId); + } else { + resp = await getWaitingRoomResponse(); + } + + return resp; +} + +/** + * Generate a random ID + */ +function generateId(len = 10) { + return Array.from({ length: len }, () => + ((Math.random() * 36) | 0).toString(36) + ).join(''); +} + +/** + * Handle the default response. + */ +async function getDefaultResponse(request, userId) { + const response = new Response( + landingPage({ + domain: getDomainFromRequest(request), + maxUsers: TOTAL_ACTIVE_USERS, + sessionDuration: SESSION_DURATION_SECONDS, + }) + ); + response.headers.set('content-type', 'text/html;charset=UTF-8'); + + const cookies = getCookiesFromRequest(request); + const now = Date.now(); + const lastUpdate = cookies[COOKIE_NAME_TIME]; + let lastUpdateTime = 0; + + if (lastUpdate) { + lastUpdateTime = parseInt(lastUpdate); + } + + const diff = now - lastUpdateTime; + const updateInterval = (SESSION_DURATION_SECONDS * 1000) / 2; + if (diff > updateInterval) { + await setExpiryRecord(userId, '1', SESSION_DURATION_SECONDS); + setCookieToResponse(response, [[COOKIE_NAME_TIME, now.toString()]]); + } + + setCookieToResponse(response, [[COOKIE_NAME_ID, userId]]); + return response; +} + +/** + * Send a REST request to Upstash. + */ +async function sendUpstashRequest(cmd) { + cmd = Array.isArray(cmd) ? cmd.join('/') : cmd; + + return ( + await fetch(`${process.env.UPSTASH_REDIS_REST_URL}`, { + method: 'POST', + body: JSON.stringify(cmd.split('/')), + headers: { + Authorization: `Bearer ${process.env.UPSTASH_REDIS_REST_TOKEN}`, + }, + }) + ).json(); +} + +/** + * Get the current number of records. + */ +async function getRecordCount() { + const data = await sendUpstashRequest('DBSIZE'); + return data.result; +} + +/** + * Fetch a record from Upstash by key. + */ +async function getRecord(key) { + const data = await sendUpstashRequest(['GET', key]); + return data.result; +} + +/** + * Set a record with an expiry time in Upstash. + */ +async function setExpiryRecord(key, value, seconds) { + return sendUpstashRequest(['SET', key, value, 'EX', seconds]); +} + +/** + * Response for the waiting room. + */ +async function getWaitingRoomResponse() { + const response = new Response(waitingPage()); + response.headers.set('content-type', 'text/html;charset=UTF-8'); + return response; +} + +function getDomainFromRequest(request) { + const url = new URL(request.url); + return url.origin; +} diff --git a/functions/waiting-room/waiting-room-capacity.html.js b/functions/database/upstash/waiting-room-capacity.html.js similarity index 94% rename from functions/waiting-room/waiting-room-capacity.html.js rename to functions/database/upstash/waiting-room-capacity.html.js index 1df8633..f7c2d2a 100644 --- a/functions/waiting-room/waiting-room-capacity.html.js +++ b/functions/database/upstash/waiting-room-capacity.html.js @@ -1,4 +1,4 @@ -const template = ({ queuePosition }) => ` +const template = () => ` @@ -70,10 +70,10 @@ const template = ({ queuePosition }) => `

Almost There!

Our site is currently at full capacity. Thanks for your patience.

-

You are number ${queuePosition} in line.

You'll be redirected shortly. Please do not close your browser.

+ `; diff --git a/functions/waiting-room/waiting-room-landing.html.js b/functions/database/upstash/waiting-room-landing.html.js similarity index 65% rename from functions/waiting-room/waiting-room-landing.html.js rename to functions/database/upstash/waiting-room-landing.html.js index b7dc05d..1b9ac65 100644 --- a/functions/waiting-room/waiting-room-landing.html.js +++ b/functions/database/upstash/waiting-room-landing.html.js @@ -1,4 +1,4 @@ -const template = ({ requestUrl }) => ` +const template = ({ domain, maxUsers, sessionDuration }) => ` @@ -91,7 +91,7 @@ const template = ({ requestUrl }) => ` } function keepSessionAlive() { - fetch('${requestUrl}/ping'); + fetch(window.location.origin + '/example/upstash-database'); } // Set interval to keep session alive every 15 seconds @@ -101,26 +101,24 @@ const template = ({ requestUrl }) => `
-

Welcome to the Waiting Room Demo

- This demo showcases an effective way to manage website traffic during high-volume periods. When the site is at full capacity, visitors are temporarily placed in a waiting room, ensuring a smooth user experience. Here's how it works:

- -

- If you see this page, you have established a session as an active user since the session limit has not been reached. -

-

- To simulate creating new sessions, issue the following command: -

for i in {1..5}; do curl ${requestUrl}; done
- -

-

- To start a new session which is queued, follow these instructions: -

Open the link in a new private/incognito window: Start New Session. This will attempt to establish a new session, which will be queued if the active session limit is reached.

-

- -

Dive into the code to see how it works.

-

View the demo code on GitHub

-
- +

Welcome to the Waiting Room Demo

+

+ This demo showcases an effective way to manage website traffic during high-volume periods. + When the site is at full capacity, visitors are temporarily placed in a waiting room, ensuring a smooth user experience. +

+

+ This is configured for a maximum of ${maxUsers} active sessions, with each session lasting ${sessionDuration} seconds. +

+

+ Experience this firsthand by opening this link in multiple incognito/private browser sessions. + Once the site is at full capacity (2 active sessions), you will be placed in a waiting room until a spot opens up. +

+

Optionally, issue the

curl
command below to make multiple requests:

+
curl ${domain}/example/upstash-database
+ +

Dive into the code to see how it works.

+

View the demo code on GitHub

+ diff --git a/functions/general/sample-html-page.js b/functions/general/sample-html-page.js index 4fa960c..4fd459e 100644 --- a/functions/general/sample-html-page.js +++ b/functions/general/sample-html-page.js @@ -164,8 +164,8 @@ export async function handleHttpRequest(request, context) {

Transactional queries with a PlanetScale database.

  • -

    Waiting Room using PlanetScale (View Example)

    -

    A waiting room example using PlanetScale for session tracking.

    +

    Upstash Database (View Example)

    +

    A waiting room example using Upstash + Redis.

  • diff --git a/functions/waiting-room/constants.js b/functions/waiting-room/constants.js deleted file mode 100644 index 1462c8f..0000000 --- a/functions/waiting-room/constants.js +++ /dev/null @@ -1,77 +0,0 @@ -export const COOKIE_NAME_ID = '__edgio_session_id'; -export const COOKIE_NAME_TIME = '__edgio_session_last_update_time'; -export const TOTAL_ACTIVE_SESSIONS = 2; - -// Active sessions should persist longer than queued sessions -export const ACTIVE_SESSION_DURATION_SECONDS = 150; -export const QUEUED_SESSION_DURATION_SECONDS = 15; - -// Queries -export const CREATE_SESSION_QUERY = ` - -- Conditional insert if the session does not exist. - -- This is cheaper than a SELECT followed by an INSERT. - INSERT INTO sessions (sessionId, status, createdAt, updatedAt) - SELECT - :id, - IF((SELECT COUNT(*) FROM sessions WHERE status = 'active') >= :sessionsLimit, 'queued', 'active'), - NOW(), - NOW() - FROM dual - WHERE NOT EXISTS ( - SELECT 1 FROM sessions WHERE sessionId = :id - ); -`; - -export const GET_SESSION_QUERY = ` - -- Get the session data and the position in the queue. - SELECT - sessions_all.*, - IF(sessions_all.status = 'queued', queued_info.position, -1) AS position, - (SELECT COUNT(*) FROM sessions WHERE status = 'active') AS active_count, - (SELECT COUNT(*) FROM sessions WHERE status = 'queued') AS queued_count - FROM - (SELECT - sessionId, - status, - createdAt, - updatedAt - FROM sessions) AS sessions_all - LEFT JOIN - (SELECT - sessionId, - ROW_NUMBER() OVER (ORDER BY createdAt) AS position - FROM sessions - WHERE status = 'queued') AS queued_info - ON sessions_all.sessionId = queued_info.sessionId - WHERE sessions_all.sessionId = :id; -`; - -export const EXPIRE_SESSIONS_QUERY = ` - DELETE FROM sessions WHERE - (updatedAt < DATE_SUB(NOW(), INTERVAL :activeDuration SECOND) AND status = 'active') - OR (updatedAt < DATE_SUB(NOW(), INTERVAL :queuedDuration SECOND) AND status = 'queued'); -`; - -export const GET_SESSION_ID_QUERY = ` - SELECT sessionId FROM ( - SELECT sessionId FROM sessions WHERE sessionId = :id - UNION ALL - SELECT UUID() AS sessionId - ORDER BY (sessionId IS NOT NULL) DESC - LIMIT 1 - ) AS uuid_selection; -`; - -export const REFRESH_SESSION_QUERY = ` - UPDATE sessions - SET - updatedAt = :date, - status = :status - WHERE sessionId = :id; -`; - -export const AVAILABLE_STATUS_QUERY = ` -SELECT IF(COUNT(*) < :sessionsLimit, 'active', 'queued') as newStatus -FROM sessions -WHERE status = 'active'; -`; diff --git a/functions/waiting-room/index.js b/functions/waiting-room/index.js deleted file mode 100644 index a081d2c..0000000 --- a/functions/waiting-room/index.js +++ /dev/null @@ -1,78 +0,0 @@ -import { - getCookiesFromRequest, - setCookieToResponse, -} from '../../utils/cookies'; -import { setEnvFromContext } from '../../utils/polyfills/process.env'; -import waitingPage from './waiting-room-capacity.html'; -import landingPage from './waiting-room-landing.html'; -import { COOKIE_NAME_ID, COOKIE_NAME_TIME } from './constants'; -import { getSessionData } from './planetscale'; - -/** - * Main handler for the edge request. - */ -export async function handleHttpRequest(request, context) { - let response; - - // Set context environment variables to process.env - setEnvFromContext(context); - - const cookies = getCookiesFromRequest(request); - - // Get user ID from cookie or generate a new one - const sessionId = cookies[COOKIE_NAME_ID]; - - // Get the current number of active sessions and the active user - const { session, activeCount, queuedCount } = await getSessionData(sessionId); - - // Check capacity - if (session.status === 'active') { - response = await getDefaultResponse(request, session); - } else { - response = await getWaitingRoomResponse(request, session); - } - - // Update the session cookie with the latest timestamp - setSessionCookie(response, session.sessionId, session.updatedAt); - - return response; -} - -/** - * Handle the default response. - */ -async function getDefaultResponse(request, session) { - const response = new Response(landingPage({ requestUrl: request.url })); - response.headers.set('content-type', 'text/html;charset=UTF-8'); - - return response; -} -/** - * Response for the waiting room. - */ -async function getWaitingRoomResponse(request, session) { - // update the waiting page to show the position in the queue, replacing {{queuePosition}} - const body = waitingPage({ - queuePosition: session.position, - }); - - const response = new Response(body); - response.headers.set('content-type', 'text/html;charset=UTF-8'); - - return response; -} - -/** - * Sets the session cookie to the response. - * - * @param {Response} response - * @param {Object} session - * @param {number} date - * @returns {Promise} - */ -export async function setSessionCookie(response, sessionId, date) { - const now = date || Date.now(); - - setCookieToResponse(response, [[COOKIE_NAME_TIME, now.toString()]]); - setCookieToResponse(response, [[COOKIE_NAME_ID, sessionId]]); -} diff --git a/functions/waiting-room/ping.js b/functions/waiting-room/ping.js deleted file mode 100644 index b6b9d69..0000000 --- a/functions/waiting-room/ping.js +++ /dev/null @@ -1,47 +0,0 @@ -import { re } from 'semver'; -import { - getCookiesFromRequest, - setCookieToResponse, -} from '../../utils/cookies'; -import { setEnvFromContext } from '../../utils/polyfills/process.env'; -import '../../utils/polyfills/URL'; -import { - COOKIE_NAME_ID, - COOKIE_NAME_TIME, - ACTIVE_SESSION_DURATION_SECONDS, - QUEUED_SESSION_DURATION_SECONDS, -} from './constants'; -import { refreshSession } from './planetscale'; -import { setSessionCookie } from '.'; - -export async function handleHttpRequest(request, context) { - let resp; - - const redirectUrl = request.url.replace('/ping', ''); - const invalidResponse = Response.redirect(redirectUrl, 302); - - // Set context environment variables to process.env - setEnvFromContext(context); - - const cookies = getCookiesFromRequest(request); - - // Redirect to the waiting room to establish a session - if (!cookies[COOKIE_NAME_ID]) { - return invalidResponse; - } - - // Refresh the session - const { sessionId, updatedAt } = await refreshSession( - cookies[COOKIE_NAME_ID] - ); - - if (!sessionId) { - return invalidResponse; - } - - const response = new Response('pong'); - - setSessionCookie(response, sessionId, updatedAt); - - return response; -} diff --git a/functions/waiting-room/planetscale.js b/functions/waiting-room/planetscale.js deleted file mode 100644 index 339ed21..0000000 --- a/functions/waiting-room/planetscale.js +++ /dev/null @@ -1,156 +0,0 @@ -import { connect } from '@planetscale/database'; - -// polyfill required classes for Planetscale -import '../../utils/polyfills/Buffer'; -import '../../utils/polyfills/URL'; - -import createFetchWithOrigin from '../../utils/createFetchForOrigin'; -import { - TOTAL_ACTIVE_SESSIONS, - ACTIVE_SESSION_DURATION_SECONDS, - QUEUED_SESSION_DURATION_SECONDS, - CREATE_SESSION_QUERY, - GET_SESSION_QUERY, - EXPIRE_SESSIONS_QUERY, - GET_SESSION_ID_QUERY, - REFRESH_SESSION_QUERY, - AVAILABLE_STATUS_QUERY, -} from './constants'; - -const fetch = createFetchWithOrigin('planetscale'); - -let _conn = null; - -function getConnection() { - // define PLANETSCALE_USERNAME and PLANETSCALE_PASSWORD in a local .env file - // and in the Edgio Developer Console. Those values will be injected into - // the `context.environmentVars` property. - const config = { - host: process.env.PLANETSCALE_HOST, - username: process.env.PLANETSCALE_USERNAME, - password: process.env.PLANETSCALE_PASSWORD, - fetch, - }; - - if (!_conn) { - _conn = connect(config); - } - - return _conn; -} - -/** - * Gets the current session information, including session counts. - * - * @param {string} sessionId - * @returns {Promise<{session: {sessionId: string, status: string, position: number}, activeCount: number, queuedCount: number}>} - */ -export async function getSessionData(sessionId = null) { - const conn = getConnection(); - - // Validate the session ID, or generate a new one if the ID is invalid - sessionId = await getSessionID(sessionId); - - // Create a new session, active or queued depending on the current number of active sessions - const res = await conn.execute(CREATE_SESSION_QUERY, { - id: sessionId, - sessionsLimit: TOTAL_ACTIVE_SESSIONS, - }); - - if (res.rowsAffected > 0) { - console.log(`Created new session: ${sessionId}`); - } else { - // Refresh the session if it already exists - await refreshSession(sessionId); - console.log(`Using existing session: ${sessionId}`); - } - - // Get the current session information including total active and queued sessions - const sessionRes = await conn.execute(GET_SESSION_QUERY, { - id: sessionId, - }); - - const data = sessionRes.rows[0]; - - return { - session: { - sessionId: data.sessionId, - status: data.status, - position: data.position, - createdAt: data.createdAt, - updatedAt: data.updatedAt, - }, - activeCount: data.active_count, - queuedCount: data.queued_count, - }; -} - -/** - * Validates the session ID, or generates a new one if the ID is invalid. Also removes expired sessions. - * - * @param {string} sessionId - * @returns {Promise} - */ -async function getSessionID(sessionId) { - const conn = getConnection(); - - // Expire stale sessions prior to validating the session ID - await expireSessions(); - - const res = await conn.execute(GET_SESSION_ID_QUERY, { id: sessionId }); - - if (res.rows.length !== 1) { - throw new Error('Invalid session ID'); - } - - return res.rows[0].sessionId; -} - -/** - * Refreshes the session if the ID is valid. - * - * @param {string} sessionId - * @returns {Promise<{sessionId: string, updatedAt: Date}>} - */ -export async function refreshSession(sessionId) { - const conn = getConnection(); - - const updatedAt = new Date(); - - // Check if there is a spot available in the active sessions before refreshing the session - const status = await getAvailableStatus(); - - const res = await conn.execute(REFRESH_SESSION_QUERY, { - id: sessionId, - date: updatedAt, - status, - }); - - return res.rowsAffected === 1 ? { sessionId, updatedAt } : null; -} - -/** - * Expires sessions that have not been updated within the specified duration. - * - * @returns {Promise} - */ -async function expireSessions() { - const conn = getConnection(); - - const res = await conn.execute(EXPIRE_SESSIONS_QUERY, { - activeDuration: ACTIVE_SESSION_DURATION_SECONDS, - queuedDuration: QUEUED_SESSION_DURATION_SECONDS, - }); - - console.log('Expired sessions removed:', res.rowsAffected); -} - -async function getAvailableStatus() { - const conn = getConnection(); - - const res = await conn.execute(AVAILABLE_STATUS_QUERY, { - sessionsLimit: TOTAL_ACTIVE_SESSIONS, - }); - - return res.rows[0].newStatus; -} diff --git a/package-lock.json b/package-lock.json index 9e93c3d..4ced841 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,12 +23,12 @@ "whatwg-url": "^13.0.0" }, "devDependencies": { - "@edgio/cli": "^7.6.1", - "@edgio/core": "^7.6.1", - "@edgio/devtools": "^7.6.1", - "@edgio/next": "^7.6.1", - "@edgio/prefetch": "^7.6.1", - "@edgio/react": "^7.6.1", + "@edgio/cli": "^7.7.1", + "@edgio/core": "^7.7.1", + "@edgio/devtools": "^7.7.1", + "@edgio/next": "^7.7.1", + "@edgio/prefetch": "^7.7.1", + "@edgio/react": "^7.7.1", "@types/react": "18.2.45", "autoprefixer": "^10.0.1", "dotenv": "^16.3.1", @@ -90,12 +90,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -204,12 +204,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -552,9 +552,9 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", @@ -628,9 +628,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -2125,20 +2125,20 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -2146,9 +2146,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", - "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", @@ -2179,9 +2179,9 @@ } }, "node_modules/@edgio/cli": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@edgio/cli/-/cli-7.6.1.tgz", - "integrity": "sha512-dACyBDGRf1eDHONCzVQ4O61aKUJL6wJypxbXn7Qhhc59/ILN4YiU5Tq+f/hhYmjovnwUXxGYyDPEBlw+aDzgFQ==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@edgio/cli/-/cli-7.7.1.tgz", + "integrity": "sha512-QhLCzrE54JhSSh3O2sLWzjaovu1kU3soqfRXcTF/2XAwy65wjQZ0wBbt5sOYHnFHmQm1DKYAnzeGuw0TZdnHlw==", "dev": true, "dependencies": { "axios": "^1.6.0", @@ -2261,9 +2261,9 @@ } }, "node_modules/@edgio/core": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@edgio/core/-/core-7.6.1.tgz", - "integrity": "sha512-D6sl3O7Q5GNeqDMRPuwzVRMZSktevJg8G+aemiD8TM3qaIzcvhthsKaYUgotAYtC/Giq6GvL/YP7my4+TN5mfw==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@edgio/core/-/core-7.7.1.tgz", + "integrity": "sha512-8aufJM6ARKZBqmoC0S6SW0DTpeNo4ZtFRTvFxPD52ug+3nyCzoPzNX4QUw9UNaRQEYHvER31bDJeJxs+tlQZJg==", "dev": true, "dependencies": { "@babel/parser": "^7.18.9", @@ -2343,9 +2343,9 @@ } }, "node_modules/@edgio/devtools": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@edgio/devtools/-/devtools-7.6.1.tgz", - "integrity": "sha512-lac/9pYAxbTUm3ElOa7VEmVpjp3Mju6Aqwsfc0MnUWcvqlmzhmNjQoIfjIk0g6DrIFinVJlLWslcl7VBnKF2kg==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@edgio/devtools/-/devtools-7.7.1.tgz", + "integrity": "sha512-+AliDKr4r89rRGXwePfYwxGEUbFu1rNAdNrmi3edn9uGPjgERB5aYxoTChE1F8SOcbM0A2aIbFtvoTP8eUXDkw==", "dev": true, "dependencies": { "clsx": "^1.1.1", @@ -2354,9 +2354,9 @@ } }, "node_modules/@edgio/next": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@edgio/next/-/next-7.6.1.tgz", - "integrity": "sha512-4lZ21mwSXuaCWrAmpvwWXd4S6BmAQ2JrJmgbptnac4jNQ4T1mIIIjX3b8xtp0Uu1pwYzDMrGmWJn0UJkqAFS0A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@edgio/next/-/next-7.7.1.tgz", + "integrity": "sha512-sxmUNra0bffOUUTOt/3yD94F2vUWYDYg7vaF+uJnNbd4qxQa4t8MejxZZmo2CV/tE80ap/7asxLCL+HUcJg8fQ==", "dev": true, "dependencies": { "@vercel/nft": "^0.13.1", @@ -3424,12 +3424,12 @@ } }, "node_modules/@edgio/prefetch": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@edgio/prefetch/-/prefetch-7.6.1.tgz", - "integrity": "sha512-nD+WCapyNkTL3ts5D6RMkmqRXiifLC0VudHD2W+SET/GjTEZC8H6wji1zUxTUD/cF0uVxfE8W2+stEIEoYHEfA==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@edgio/prefetch/-/prefetch-7.7.1.tgz", + "integrity": "sha512-MmBUWcQ+df6hKIiosI0FUyxn0zHWVmoXDsVFBYvo6t7/va4JO203v4xN2tDQ7JTFj4GnjkHxJ8hii0WbzOzPlQ==", "dev": true, "dependencies": { - "@edgio/core": "^7.6.1", + "@edgio/core": "^7.7.1", "cheerio": "^1.0.0-rc.3", "json-query": "^2.2.2", "workbox-cacheable-response": "^5.1.2", @@ -3441,9 +3441,9 @@ } }, "node_modules/@edgio/react": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@edgio/react/-/react-7.6.1.tgz", - "integrity": "sha512-aRyzAsIfXVmjHxB9msoj++he/+owj0qDjPRl+d2kOXe8VbucauydtyO9MgQSb1WXlK6DaqMec10SCeTR7Sgceg==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@edgio/react/-/react-7.7.1.tgz", + "integrity": "sha512-zan96lbI2fLHbjcgOH1XNfE94F8qalhrtgGxt29Bc9REVQonNbDAm9od2K57M3cKTZp009Wb2aUgxEI1XYpdnw==", "dev": true, "dependencies": { "prop-types": "^15.8.1", @@ -5319,12 +5319,12 @@ } }, "node_modules/axios": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz", - "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==", + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dev": true, "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.4", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -9307,9 +9307,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 7c589df..b5da144 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,12 @@ "private": true, "license": "MIT", "devDependencies": { - "@edgio/cli": "^7.6.1", - "@edgio/core": "^7.6.1", - "@edgio/devtools": "^7.6.1", - "@edgio/next": "^7.6.1", - "@edgio/prefetch": "^7.6.1", - "@edgio/react": "^7.6.1", + "@edgio/cli": "^7.7.1", + "@edgio/core": "^7.7.1", + "@edgio/devtools": "^7.7.1", + "@edgio/next": "^7.7.1", + "@edgio/prefetch": "^7.7.1", + "@edgio/react": "^7.7.1", "@types/react": "18.2.45", "autoprefixer": "^10.0.1", "dotenv": "^16.3.1", diff --git a/routes.js b/routes.js index bf20f14..f27202b 100644 --- a/routes.js +++ b/routes.js @@ -67,9 +67,6 @@ export default new Router() .match('/example/planetscale-database', { edge_function: './functions/database/planetscale/index.js', }) - .match('/example/waiting-room', { - edge_function: './functions/waiting-room/index.js', - }) - .match('/example/waiting-room/ping', { - edge_function: './functions/waiting-room/ping.js', + .match('/example/upstash-database', { + edge_function: './functions/database/upstash/index.js', }); diff --git a/utils/polyfills/URL.js b/utils/polyfills/URL.js index 9a699cb..2144de7 100644 --- a/utils/polyfills/URL.js +++ b/utils/polyfills/URL.js @@ -1,5 +1,4 @@ -// npm install whatwg-url -import { URL, URLSearchParams } from 'whatwg-url'; +// npm install url-parse +import URL from 'url-parse'; global.URL = URL; -global.URLSearchParams = URLSearchParams;