Skip to content

Commit

Permalink
feat: improved edge function compatibility (#702)
Browse files Browse the repository at this point in the history
* feat: use built-in fetch when available

* chore: bump version

* feat: make it possible to use our middleware in some unsupported frameworks

* feat: add edge function testing (#704)

* feat: add netlify.toml to next example

* docs(examples): add postinstall script to install root deps in CI deploys

* docs(examples): update appinfo to point to the netlify deployment if defined

* docs(examples): update appinfo to point to the netlify deployment

* docs(examples): update appinfo to point to the netlify deployment

* test: update example tests

* chore: prettier

* test: add edge function test

* test: make sure the nextjs test call refresh to check edge compatibility

* test: add shebang to utils script

* test: save the output of the deployment in case of a failure

* test: save the output of the deployment in case of a failure

* test: use netlify_site_id secret

* test: build before deploying to netlify

* test: add TEST_DEPLOYED_VERSION flag to the edge function test gh action

* test: check if prod deploy in netlify works

* ci: debug netlify deployment

* ci: fixing netlify test deployment

* ci: fixing netlify test deployment

* ci: remove earlier fix to check if test fails in CI

* Revert "ci: remove earlier fix to check if test fails in CI"

This reverts commit f4aeedd.

* feat: move handleCall from nextjs appdir example

* fix: getAppDirRequestHandler export

* feat: PartialNextRequest should accept string as method

* build: ignore new util script when publishing

* feat: add verifySession for custom frameworks with a new callback to save the session obj

* feat: add export for custom framework verifySession

* test: add type checking test for getAppDirRequestHandler and custom framework verifySession

* feat: add custom as an option to the framework config

* test: add tests for custom framework

---------

Co-authored-by: Rishabh Poddar <[email protected]>
  • Loading branch information
porcellus and rishabhpoddar authored Oct 10, 2023
1 parent 843cf5e commit 367d41b
Show file tree
Hide file tree
Showing 53 changed files with 1,350 additions and 170 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/test-edge-function.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: "Test edge function compatibility"
on: push
jobs:
test:
runs-on: ubuntu-latest
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.netlify_auth_token }}
NETLIFY_SITE_ID: ${{ secrets.netlify_site_id }}
TEST_DEPLOYED_VERSION: true
defaults:
run:
working-directory: examples/next/with-emailpassword
steps:
- uses: actions/checkout@v2
- run: echo $GITHUB_REF_NAME
- 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: |
( \
(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
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ coreDriverInterfaceSupported.json
examples/
add-ts-no-check.js
docs/
.prettierignore
.prettierignore
updateLibInExample
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Adds support for configuring multiple frontend domains to be used with the same backend
- Added a new `origin` property to `appInfo`, this can be configured to be a function which allows you to conditionally return the value of the frontend domain. This property will replace `websiteDomain` in a future release of `supertokens-node`
- `websiteDomain` inside `appInfo` is now optional. Using `origin` is recommended over using `websiteDomain`. This is not a breaking change and using `websiteDomain` will continue to work
- Added a "custom" framework you can use in framework normally not supported by our SDK
- Added a next13 app router compatible request handler.

### Fixed

- Fixed an issue where calling signinup for thirdparty recipe would result in a "clone is not a function" error

### Changes

- Using built-in fetch whenever available instead of cross-fetch
- Improved edge-function support

## [16.2.1] - 2023-10-06

- Slight refactors logic to code for social providers to make it consistent across all providers
Expand Down
3 changes: 2 additions & 1 deletion examples/aws/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ describe("SuperTokens Example Basic tests", function () {
]);
await submitForm(page);
await page.waitForNavigation();
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".sessionButton");
let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
Expand Down
3 changes: 2 additions & 1 deletion examples/express/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ describe("SuperTokens Example Basic tests", function () {
{ name: "password", value: testPW },
]);
await submitForm(page);
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".sessionButton");
let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
Expand Down
3 changes: 2 additions & 1 deletion examples/fastify/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ describe("SuperTokens Example Basic tests", function () {
{ name: "password", value: testPW },
]);
await submitForm(page);
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".sessionButton");
let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
Expand Down
3 changes: 2 additions & 1 deletion examples/hapi/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ describe("SuperTokens Example Basic tests", function () {
]);
await submitForm(page);
await page.waitForNavigation();
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".sessionButton");
let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
Expand Down
3 changes: 2 additions & 1 deletion examples/koa/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ describe("SuperTokens Example Basic tests", function () {
{ name: "password", value: testPW },
]);
await submitForm(page);
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".sessionButton");
let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
Expand Down
3 changes: 2 additions & 1 deletion examples/loopback/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ describe("SuperTokens Example Basic tests", function () {
]);
await submitForm(page);
await page.waitForNavigation();
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".sessionButton");
let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
Expand Down
3 changes: 2 additions & 1 deletion examples/nest/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ describe("SuperTokens Example Basic tests", function () {
]);
await submitForm(page);
await page.waitForNavigation();
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".sessionButton");
let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
Expand Down
7 changes: 6 additions & 1 deletion examples/next/with-emailpassword/config/appInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ const port = process.env.APP_PORT || 3000;

const apiBasePath = "/api/auth/";

export const websiteDomain = process.env.APP_URL || process.env.NEXT_PUBLIC_APP_URL || `http://localhost:${port}`;
export const websiteDomain =
process.env.APP_URL ||
process.env.NEXT_PUBLIC_APP_URL ||
process.env.DEPLOY_URL ||
process.env.URL ||
`http://localhost:${port}`;

export const appInfo = {
appName: "SuperTokens Demo App",
Expand Down
4 changes: 4 additions & 0 deletions examples/next/with-emailpassword/netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[build]
command = "NEXT_PUBLIC_APP_URL=$DEPLOY_URL npm run build"
functions = "netlify/functions"
publish = ".next"
1 change: 1 addition & 0 deletions examples/next/with-emailpassword/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/next/with-emailpassword/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"private": true,
"scripts": {
"postinstall": "../../../updateLibInExample",
"dev": "next dev",
"build": "next build",
"start": "next start"
Expand Down
28 changes: 25 additions & 3 deletions examples/next/with-emailpassword/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,19 @@ const EmailPassword = require("supertokens-node/recipe/emailpassword");
// Run the tests in a DOM environment.
require("jsdom-global")();

const apiDomain = "http://localhost:3001";
const websiteDomain = "http://localhost:3000";
let deployInfo;

if (process.env.TEST_DEPLOYED_VERSION) {
deployInfo = require("../deployInfo.json");

if (!deployInfo.deploy_url) {
throw new Error("Deployment failed or json error. " + JSON.stringify(deployInfo));
}
}

const apiDomain = deployInfo?.deploy_url ?? "http://localhost:3000";
const websiteDomain = deployInfo?.deploy_url ?? "http://localhost:3000";

SuperTokensNode.init({
supertokens: {
// We are running these tests without running a local ST instance
Expand Down Expand Up @@ -80,8 +91,19 @@ describe("SuperTokens Example Basic tests", function () {
]);
await submitForm(page);
await page.waitForNavigation();
const user = await EmailPassword.getUserByEmail("public", email);
const userList = await SuperTokensNode.listUsersByAccountInfo("public", { email });
const user = userList[0];
const callApiBtn = await page.waitForSelector(".ProtectedHome_sessionButton__ihFAK");

// we save the cookies..
let originalCookies = (await page._client.send("Network.getAllCookies")).cookies;

// we set the old cookies with invalid access token
originalCookies = originalCookies.map((c) =>
c.name === "sAccessToken" || c.name === "st-access-token" ? { ...c, value: "broken" } : c
);
await page.setCookie(...originalCookies);

let setAlertContent;
let alertContent = new Promise((res) => (setAlertContent = res));
page.on("dialog", async (dialog) => {
Expand Down
3 changes: 3 additions & 0 deletions framework/custom/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "../../lib/build/framework/custom";
import * as _default from "../../lib/build/framework/custom";
export default _default;
6 changes: 6 additions & 0 deletions framework/custom/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
exports.__esModule = true;
__export(require("../../lib/build/framework/custom"));
109 changes: 109 additions & 0 deletions lib/build/framework/custom/framework.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// @ts-nocheck
import type { HTTPMethod } from "../../types";
import { BaseRequest } from "../request";
import { BaseResponse } from "../response";
import { SessionContainerInterface } from "../../recipe/session/types";
declare type RequestInfo = {
url: string;
method: HTTPMethod;
headers: Headers;
cookies: Record<string, string>;
query: Record<string, string>;
getJSONBody: () => Promise<any>;
getFormBody: () => Promise<any>;
setSession?: (session: SessionContainerInterface) => void;
};
export declare class PreParsedRequest extends BaseRequest {
private request;
private _session?;
get session(): SessionContainerInterface | undefined;
set session(value: SessionContainerInterface | undefined);
constructor(request: RequestInfo);
getFormData: () => Promise<any>;
getKeyValueFromQuery: (key: string) => string | undefined;
getJSONBody: () => Promise<any>;
getMethod: () => HTTPMethod;
getCookieValue: (key: string) => string | undefined;
getHeaderValue: (key: string) => string | undefined;
getOriginalURL: () => string;
}
export declare type CookieInfo = {
key: string;
value: string;
domain: string | undefined;
secure: boolean;
httpOnly: boolean;
expires: number;
path: string;
sameSite: "strict" | "lax" | "none";
};
export declare class CollectingResponse extends BaseResponse {
statusCode: number;
readonly headers: Headers;
readonly cookies: CookieInfo[];
body?: string;
constructor();
sendHTMLResponse: (html: string) => void;
setHeader: (key: string, value: string, allowDuplicateKey: boolean) => void;
removeHeader: (key: string) => void;
setCookie: (
key: string,
value: string,
domain: string | undefined,
secure: boolean,
httpOnly: boolean,
expires: number,
path: string,
sameSite: "strict" | "lax" | "none"
) => void;
/**
* @param {number} statusCode
*/
setStatusCode: (statusCode: number) => void;
sendJSONResponse: (content: any) => void;
}
export declare type NextFunction = (err?: any) => void;
export declare const middleware: <OrigReqType = BaseRequest, OrigRespType = BaseResponse>(
wrapRequest?: (req: OrigReqType) => BaseRequest,
wrapResponse?: (req: OrigRespType) => BaseResponse
) => (
request: OrigReqType,
response: OrigRespType,
next?: NextFunction | undefined
) => Promise<
| {
handled: boolean;
error?: undefined;
}
| {
error: any;
handled?: undefined;
}
>;
export declare const errorHandler: () => (
err: any,
request: BaseRequest,
response: BaseResponse,
next: NextFunction
) => Promise<void>;
export declare const CustomFrameworkWrapper: {
middleware: <OrigReqType = BaseRequest, OrigRespType = BaseResponse>(
wrapRequest?: (req: OrigReqType) => BaseRequest,
wrapResponse?: (req: OrigRespType) => BaseResponse
) => (
request: OrigReqType,
response: OrigRespType,
next?: NextFunction | undefined
) => Promise<
| {
handled: boolean;
error?: undefined;
}
| {
error: any;
handled?: undefined;
}
>;
errorHandler: () => (err: any, request: BaseRequest, response: BaseResponse, next: NextFunction) => Promise<void>;
};
export {};
Loading

0 comments on commit 367d41b

Please sign in to comment.