Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a recipeModule manager #16

Closed
14 tasks done
rishabhpoddar opened this issue Oct 13, 2020 · 8 comments
Closed
14 tasks done

Implement a recipeModule manager #16

rishabhpoddar opened this issue Oct 13, 2020 · 8 comments
Assignees
Labels
enhancement New feature or request

Comments

@rishabhpoddar
Copy link
Member

rishabhpoddar commented Oct 13, 2020

Should be responsible for:

  • Routing of an API to a module. The algorithm can be found here

  • Has a way for any module to get the appInfo if needed.

  • Has an init function that takes:

    • The hosts and apiKey
    • The appInfo
    • recipeList: moduleInterface[]
  • Has error handling routing for modules:

    • If the error has a rId:
      • If it matches any one of the module's being used (the first one it finds), call that module's error handler
      • If no modules match, then throw pass the error to the next error handler.
    • The error thrown from a module is:
    {
       rId: "",
       type: "",
       message: string,
       payload: any
    }
    
  • Handle CORS headers. Has a function like getAllCORSHeaders which:

    • calls the getAllCORSHeaders for each module
    • removes duplicates
    • returns the resulting strings in an array
  • Provides an abstract recipeModule class that modules must extend:

    • Provides methods to asks a module if they can handle the current route (excluding apiBasePath) via the middleware function.
    • Get and manage the module's rId. The rId will be an argument to the constructor.
    • Exposes a function that modules must implement to handle the current API request.
    • Gets all CORS headers from a module.
    • Asks a module if they can handle the current error thrown
    • Lets a module handle an error
  • Recipe modules must be built such that parent recipe's rId can be propagated to them. This is how

  • Enforce use of NormalisedURLDomain and NormalisedURLPath

@rishabhpoddar rishabhpoddar added the enhancement New feature or request label Oct 13, 2020
@rishabhpoddar rishabhpoddar changed the title Implement the module manager Implement a recipeModule manager Oct 15, 2020
@rishabhpoddar
Copy link
Member Author

Routing functionality

import * as supertokens from "supertokens-node";

let app = express();

app.use(supertokens.middleware());

app.use(supertokens.errorHandler());

@rishabhpoddar
Copy link
Member Author

rishabhpoddar commented Oct 16, 2020

If a recipe is being built on top of another one, how can its rId be propogated down?

Base recipe

class EmailPassword extends RecipeModule {
	
	static instance: undefined | EmailPassword

	static getInstanceOrThrowError() => {
		if (instance !== undefined) {
			return instance;
		}
		throw error("please call init..");
	}

	static init(options: {..}): ModuleInterface {
		constructor(options, "emailpassword")
		// create a singleton instance of EmailPassword
	}

	constructor({..}, recipeId) {
		super(recipeId)
		....
	}

	signUp(email, password) {
		let rId = super.getRecipeId();
		// do implementation...
	}

	static signUp(email, password) => {
		let instance = EmailPassword.__getInstanceIfDefined();
		return await instance.signUp(email, passowrd);
	}
}

New recipe

class EmailPasswordModified extends RecipeModule {

	static instance: undefined | EmailPasswordModified
	
	emailPasswordInstance: EmailPassword

	constructor({..}, recipeId) {
		super(recipeId)
		this.emailPasswordInstance = new EmailPassword(<options specific to this>, recipeId)
	}

	static getInstanceOrThrowError() => {
		if (instance !== undefined) {
			return instance;
		}
		throw error("please call init..");
	}

	static init(options: {..}): ModuleInterface {
		constructor(options, "emailpasswordmodified")
	}

	static signUp(email, password) => {
		let instance = EmailPasswordModified.__getInstanceIfDefined();
		return await instance.emailPasswordInstance.signUp(email, passowrd)
	}

}

Edit:

  • Changed __getInstanceIfDefined to getInstanceOrThrowError. @NkxxkN

@rishabhpoddar rishabhpoddar self-assigned this Oct 21, 2020
@rishabhpoddar rishabhpoddar changed the title Implement a recipeModule manager Implement a recipeModule manager skeleton and make that structure work with sessions Oct 22, 2020
@rishabhpoddar rishabhpoddar changed the title Implement a recipeModule manager skeleton and make that structure work with sessions Implement a recipeModule manager Oct 22, 2020
@rishabhpoddar
Copy link
Member Author

In the config for supertokens.init, we do not pass a recipeModule[]. Instead, we pass a (() => RecipeModule)[] which will allow the recipes to be initialised after the supertokens.init has finished initialising info that it has been given.

This implies that the init functions of the recipes must not return a RecipeModule, but instead, should return a () => RecipeModule

@rishabhpoddar
Copy link
Member Author

The routing algorithm:

  • If request does not start with apiBasePath, skip
  • Case rid is present:
    • Loop through all modules to find the one with the matching rId (find the first matching one)
      • rid doesn't match, then skip.
    • Ask the matching recipe module if they can handle the path and method.
      • If not, then skip
    • Pass the task to the that module
  • Case no rid is present:
    • Loop through all modules to find the first one that can handle the task based on the path and method
      • If none, then skip
    • Pass the task to the that module

@NkxxkN , I have written the routing algo in a bit more detail here (in comparison to the one written in the frontend package). It still follows the same login though. Lmk if this is fine with you and if you have done something very differently on the frontend.

@kant01ne
Copy link
Contributor

Loop through all modules to find the one with the matching rId (find the first matching one

Do you loop through recipes and then through features? i.e. where and how do you store the routes? what's the data structure for it?

Seems to be working from a logic perspective!

@rishabhpoddar
Copy link
Member Author

The routing code is here

The notion of a feature is slightly loose in the backend since it's just a bunch of functions and APIs being exposed by each feature. So there isn't really a concept of an explicit feature like there is on the frontend (cause frontend features are much more complex and have themes)

Do you loop through recipes and then through features?

where and how do you store the routes?

The abstract recipe base class has an abstract function getAPIsHandled which returns an array that contains the following object:

{
    pathWithoutApiBasePath: string,
    method: HTTPMethod,
    id: string, disabled: boolean
}

The actual recipe implements this function to return the array of these objects - one per support API / route.

@kant01ne
Copy link
Contributor

Ok looks good to me!

@rishabhpoddar
Copy link
Member Author

rishabhpoddar commented Oct 23, 2020

@NkxxkN. I am done with this issue. The PR is #25, but since it's a complete rewrite of most of the lib anyway, I am pushing it to branch 3.0. So you can see the code there if you'd like.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants