-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add edge compatibility for custom frameworks and Next.JS (#918)
* Add support for polyfilling buffer and node for next * Use querystringify instead of querystring for edge compatibility * Add support for parse implementation using URLSearchParams * Add tests for parseParams * Run build-pretty on all files * Add a workflow for testing Next.JS with edge runtime * Add support for gzip compression * Add support for br compression * Disable brotli compression in edge * Remove unnecessary log * Replace parseParams with one URLSearchParams alternative * Add support for throwing a proper error when brotli doesn't work * Add support for CF workflow to test edge function compatibility * Get rid of netflify edge test function * Remove newly added conflicting route from netlify next test wf * Rename api/auth dynamic variable in next app router * Use ponyfilled process to refactor checking for test env * Refactor process and expose an util function to make it accessible * Add fallback implementation for accessing Buffer * Refactor base64 encoding/decoding to use ponyfilled buffer * Reuse ponyfilled buffer at more places * Remove polyfill functionality for buffer and process * Remove unused test file * Update some functions according to comments in PR * Update workflow to use more values from secrets * Get rid of pages directory in next emailpassword example * Remove use of getBuffer in loopback framework * Add error handling for possible malformed body in lambda request * Feat/hono api example repo (#2) * Add init support for testing hono deployments on CF with edge * Fix the directory path in hono workflow * Fix the deploy command * Fix tests to use assert instead * Remove unused build command * Update hono edge tests to test session endpoint as well * Update README to contain proper details * Drop brotli decompression support * Remove brotli as a dependency * Address requested changes * Fix a typo regarding boxPrimitives check * Update changelog with details of changes --------- Co-authored-by: Rishabh Poddar <[email protected]>
- Loading branch information
1 parent
6420511
commit 8fabd09
Showing
91 changed files
with
4,370 additions
and
226 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,43 @@ | ||
name: "Test edge function compatibility for Hono on Cloudflare Workers" | ||
on: push | ||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
env: | ||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||
APP_URL: ${{ secrets.CLOUDFLARE_HONO_APP_URL }} | ||
CLOUDFLARE_PROJECT_NAME: ${{ secrets.CLOUDFLARE_HONO_PROJECT_NAME }} | ||
TEST_DEPLOYED_VERSION: true | ||
defaults: | ||
run: | ||
working-directory: examples/cloudflare-workers/with-email-password-hono-be-only | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- run: echo $GITHUB_SHA | ||
- run: npm install git+https://github.com:supertokens/supertokens-node.git#$GITHUB_SHA | ||
- run: npm install | ||
- run: npm install [email protected] [email protected] puppeteer@^11.0.0 isomorphic-fetch@^3.0.0 | ||
|
||
- name: Replace APP_URL with deployed URL value | ||
run: | | ||
sed -i "s|process.env.REACT_APP_API_URL|\"${{ env.APP_URL }}\"|" config.ts | ||
sed -i "s|process.env.REACT_APP_WEBSITE_URL|\"${{ env.APP_URL }}\"|" config.ts | ||
- name: Deploy the changes | ||
run: npx wrangler deploy --name ${{ env.CLOUDFLARE_PROJECT_NAME }} index.ts | ||
|
||
- name: Run tests | ||
run: | | ||
( \ | ||
(echo "=========== Test attempt 1 ===========" && npx mocha --no-config --timeout 80000 test/**/*.test.js) || \ | ||
(echo "=========== Test attempt 2 ===========" && npx mocha --no-config --timeout 80000 test/**/*.test.js) || \ | ||
(echo "=========== Test attempt 3 ===========" && npx mocha --no-config --timeout 80000 test/**/*.test.js) \ | ||
) | ||
- name: The job has failed | ||
if: ${{ failure() }} | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: screenshots | ||
path: | | ||
./**/*screenshot.jpeg |
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,72 @@ | ||
name: "Test edge function compatibility for Next.js on Cloudflare Workers" | ||
on: push | ||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
env: | ||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||
APP_URL: ${{ secrets.CLOUDFLARE_APP_URL }} | ||
CLOUDFLARE_PROJECT_NAME: ${{ secrets.CLOUDFLARE_PROJECT_NAME }} | ||
TEST_DEPLOYED_VERSION: true | ||
defaults: | ||
run: | ||
working-directory: examples/next/with-emailpassword | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- run: echo $GITHUB_SHA | ||
- run: npm install git+https://github.com:supertokens/supertokens-node.git#$GITHUB_SHA | ||
- run: npm install | ||
- run: npm install [email protected] [email protected] puppeteer@^11.0.0 isomorphic-fetch@^3.0.0 | ||
|
||
# Step to update the runtime to edge to all files in app/api/ | ||
- name: Add runtime export to API files | ||
run: | | ||
find app/api -type f \( -name "*.js" -o -name "*.ts" \) -exec sed -i '1s/^/export const runtime = "edge";\n/' {} + | ||
echo 'export const runtime = "edge";' >> app/auth/[[...path]]/page.tsx | ||
# Install next on pages to build the app | ||
- name: Install next-on-pages | ||
run: npm install --save-dev @cloudflare/next-on-pages | ||
|
||
# Setup the compatibility flag to make non edge functions run | ||
- name: Create a wrangler.toml | ||
run: echo "compatibility_flags = [ "nodejs_compat" ]" >> wrangler.toml | ||
|
||
- name: Replace APP_URL with deployed URL value | ||
run: | | ||
sed -i "s|process.env.APP_URL|\"${{ env.APP_URL }}\"|" config/appInfo.ts | ||
- name: Build using next-on-pages | ||
run: npx next-on-pages | ||
|
||
- name: Publish to Cloudflare Pages | ||
id: deploy | ||
uses: cloudflare/pages-action@v1 | ||
with: | ||
apiToken: ${{ env.CLOUDFLARE_API_TOKEN }} | ||
accountId: ${{ env.CLOUDFLARE_ACCOUNT_ID }} | ||
projectName: ${{ env.CLOUDFLARE_PROJECT_NAME }} | ||
directory: "./examples/next/with-emailpassword/.vercel/output/static" | ||
wranglerVersion: "3" | ||
branch: "master" | ||
|
||
- name: Extract deployment info and save to JSON | ||
id: extract_deploy_info | ||
run: | | ||
DEPLOY_ID=${{ steps.deploy.outputs.id }} | ||
DEPLOY_URL=${{ steps.deploy.outputs.url }} | ||
echo "{\"deploy_url\": \"$DEPLOY_URL\", \"deploy_id\": \"$DEPLOY_ID\"}" > deployInfo.json | ||
- name: Run tests | ||
run: | | ||
( \ | ||
(echo "=========== Test attempt 1 ===========" && npx mocha --no-config --timeout 80000 test/**/*.test.js) || \ | ||
(echo "=========== Test attempt 2 ===========" && npx mocha --no-config --timeout 80000 test/**/*.test.js) || \ | ||
(echo "=========== Test attempt 3 ===========" && npx mocha --no-config --timeout 80000 test/**/*.test.js) \ | ||
) | ||
- name: The job has failed | ||
if: ${{ failure() }} | ||
uses: actions/upload-artifact@v3 | ||
with: | ||
name: screenshots | ||
path: | | ||
./**/*screenshot.jpeg |
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 |
---|---|---|
|
@@ -16,6 +16,7 @@ jobs: | |
- run: npm install git+https://github.com:supertokens/supertokens-node.git#$GITHUB_SHA | ||
- run: npm install | ||
- run: npm install [email protected] [email protected] puppeteer@^11.0.0 isomorphic-fetch@^3.0.0 | ||
|
||
- run: netlify deploy --alias 0 --build --json --auth=$NETLIFY_AUTH_TOKEN > deployInfo.json | ||
- run: cat deployInfo.json | ||
- run: | | ||
|
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
25 changes: 25 additions & 0 deletions
25
examples/cloudflare-workers/with-email-password-hono-be-only/README.md
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,25 @@ | ||
![SuperTokens banner](https://raw.githubusercontent.com/supertokens/supertokens-logo/master/images/Artboard%20%E2%80%93%2027%402x.png) | ||
|
||
# SuperTokens EmailPassword with Cloudflare Workers (HonoJS) on Edge Runtime | ||
|
||
This demo app uses HonoJS with Cloudflare Workers for the backend server. We use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) in the backend server to simulate the Cloudflare Worker runtime. This is a pure Edge runtime implementation (works without `nodejs_compat` flag). | ||
|
||
## Project setup | ||
|
||
Clone the repo, enter the directory, and use `npm` to install the project dependencies: | ||
|
||
```bash | ||
git clone https://github.com/supertokens/supertokens-node | ||
cd supertokens-node/examples/cloudflare-workers/with-be-emailpassword | ||
npm install | ||
``` | ||
|
||
## Run the demo app | ||
|
||
This compiles and serves the React app and starts the backend API server on port 3001. | ||
|
||
```bash | ||
npm run start | ||
``` | ||
|
||
The app will start on `http://localhost:3000` |
33 changes: 33 additions & 0 deletions
33
examples/cloudflare-workers/with-email-password-hono-be-only/config.ts
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,33 @@ | ||
import EmailPassword from "supertokens-node/recipe/emailpassword"; | ||
import Session from "supertokens-node/recipe/session"; | ||
import { TypeInput } from "supertokens-node/types"; | ||
import process from "process"; | ||
|
||
export const runtime = "edge"; | ||
|
||
export function getApiDomain() { | ||
const apiPort = process.env.REACT_APP_API_PORT || 3001; | ||
const apiUrl = process.env.REACT_APP_API_URL || `http://localhost:${apiPort}`; | ||
return apiUrl; | ||
} | ||
|
||
export function getWebsiteDomain() { | ||
const websitePort = process.env.REACT_APP_WEBSITE_PORT || 3000; | ||
const websiteUrl = process.env.REACT_APP_WEBSITE_URL || `http://localhost:${websitePort}`; | ||
return websiteUrl; | ||
} | ||
|
||
export const SuperTokensConfig: TypeInput = { | ||
supertokens: { | ||
// this is the location of the SuperTokens core. | ||
connectionURI: "https://try.supertokens.com", | ||
}, | ||
appInfo: { | ||
appName: "SuperTokens Demo App", | ||
apiDomain: getApiDomain(), | ||
websiteDomain: getWebsiteDomain(), | ||
}, | ||
// recipeList contains all the modules that you want to | ||
// use from SuperTokens. See the full list here: https://supertokens.com/docs/guides | ||
recipeList: [EmailPassword.init(), Session.init()], | ||
}; |
7 changes: 7 additions & 0 deletions
7
examples/cloudflare-workers/with-email-password-hono-be-only/hono.d.ts
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 { SessionContainer } from "supertokens-node/recipe/session"; | ||
|
||
declare module "hono" { | ||
interface HonoRequest { | ||
session?: SessionContainer; | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
examples/cloudflare-workers/with-email-password-hono-be-only/index.ts
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 @@ | ||
import { Hono } from "hono"; | ||
import { cors } from "hono/cors"; | ||
import supertokens from "supertokens-node"; | ||
import { middleware } from "./middleware"; | ||
import { getWebsiteDomain, SuperTokensConfig } from "./config"; | ||
import type { PageConfig } from "next"; | ||
|
||
export const config: PageConfig = { | ||
runtime: "edge", | ||
}; | ||
|
||
supertokens.init(SuperTokensConfig); | ||
|
||
const app = new Hono(); | ||
|
||
app.use("*", async (c, next) => { | ||
return await cors({ | ||
origin: getWebsiteDomain(), | ||
credentials: true, | ||
allowHeaders: ["Content-Type", ...supertokens.getAllCORSHeaders()], | ||
allowMethods: ["GET", "POST", "PUT", "PATCH", "DELETE"], | ||
})(c, next); | ||
}); | ||
|
||
// This exposes all the APIs from SuperTokens to the client. | ||
// and adds the session to the request object if one exists. | ||
app.use("*", middleware()); | ||
|
||
// An example API that requires session verification | ||
app.get("/sessioninfo", (c) => { | ||
let session = c.req.session; | ||
if (!session) { | ||
return c.text("Unauthorized", 401); | ||
} | ||
return c.json({ | ||
sessionHandle: session!.getHandle(), | ||
userId: session!.getUserId(), | ||
accessTokenPayload: session!.getAccessTokenPayload(), | ||
}); | ||
}); | ||
|
||
export default app; |
88 changes: 88 additions & 0 deletions
88
examples/cloudflare-workers/with-email-password-hono-be-only/middleware.ts
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,88 @@ | ||
import { Context, Next } from "hono"; | ||
import { getCookie } from "hono/cookie"; | ||
import { | ||
CollectingResponse, | ||
PreParsedRequest, | ||
middleware as customMiddleware, | ||
} from "supertokens-node/framework/custom"; | ||
import Session from "supertokens-node/recipe/session"; | ||
import { HTTPMethod } from "supertokens-node/types"; | ||
import { serialize } from "cookie"; | ||
|
||
export const runtime = "edge"; | ||
|
||
function setCookiesInHeaders(headers: Headers, cookies: CollectingResponse["cookies"]) { | ||
for (const cookie of cookies) { | ||
headers.append( | ||
"Set-Cookie", | ||
serialize(cookie.key, cookie.value, { | ||
domain: cookie.domain, | ||
expires: new Date(cookie.expires), | ||
httpOnly: cookie.httpOnly, | ||
path: cookie.path, | ||
sameSite: cookie.sameSite, | ||
secure: cookie.secure, | ||
}) | ||
); | ||
} | ||
} | ||
|
||
function copyHeaders(source: Headers, destination: Headers): void { | ||
for (const [key, value] of source.entries()) { | ||
destination.append(key, value); | ||
} | ||
} | ||
|
||
export const middleware = () => { | ||
return async function (c: Context, next: Next) { | ||
const request = new PreParsedRequest({ | ||
method: c.req.method as HTTPMethod, | ||
url: c.req.url, | ||
query: Object.fromEntries(new URL(c.req.url).searchParams.entries()), | ||
cookies: getCookie(c), | ||
headers: c.req.raw.headers as Headers, | ||
getFormBody: () => c.req.formData(), | ||
getJSONBody: () => c.req.json(), | ||
}); | ||
const baseResponse = new CollectingResponse(); | ||
|
||
const stMiddleware = customMiddleware(() => request); | ||
|
||
const { handled, error } = await stMiddleware(request, baseResponse); | ||
|
||
if (error) { | ||
throw error; | ||
} | ||
|
||
if (handled) { | ||
setCookiesInHeaders(baseResponse.headers, baseResponse.cookies); | ||
return new Response(baseResponse.body, { | ||
status: baseResponse.statusCode, | ||
headers: baseResponse.headers, | ||
}); | ||
} | ||
|
||
// Add session to c.req if it exists | ||
try { | ||
c.req.session = await Session.getSession(request, baseResponse, { | ||
sessionRequired: false, | ||
}); | ||
|
||
await next(); | ||
|
||
// Add cookies that were set by `getSession` to response | ||
setCookiesInHeaders(c.res.headers, baseResponse.cookies); | ||
// Copy headers that were set by `getSession` to response | ||
copyHeaders(baseResponse.headers, c.res.headers); | ||
return c.res; | ||
} catch (err) { | ||
if (Session.Error.isErrorFromSuperTokens(err)) { | ||
if (err.type === Session.Error.TRY_REFRESH_TOKEN || err.type === Session.Error.INVALID_CLAIMS) { | ||
return new Response("Unauthorized", { | ||
status: err.type === Session.Error.INVALID_CLAIMS ? 403 : 401, | ||
}); | ||
} | ||
} | ||
} | ||
}; | ||
}; |
Oops, something went wrong.