diff --git a/.babelrc b/.babelrc
new file mode 100644
index 000000000..c520f2e90
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,7 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env"
+ ]
+ ]
+ }
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 000000000..35545a2ba
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+lib/build/* linguist-generated=true
\ No newline at end of file
diff --git a/.mocharc.yml b/.mocharc.yml
index 54a8c2fa7..016865972 100644
--- a/.mocharc.yml
+++ b/.mocharc.yml
@@ -2,6 +2,4 @@ exit: true
spec: test/**/*.test.js
reporter: spec
slow: 20000
-timeout: 30000
-require:
- - isomorphic-fetch
\ No newline at end of file
+timeout: 30000
\ No newline at end of file
diff --git a/.npmignore b/.npmignore
index a34626ed2..fae3aab2d 100644
--- a/.npmignore
+++ b/.npmignore
@@ -2,9 +2,11 @@
!**/*.d.ts
.git/
test/
+react-test-app/
hooks/
.circleci/
lib/tsconfig.json
+lib/babel.config.json
.mocharc.yml
lib/.prettierrc
lib/tslint.json
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0e28ebda2..ee65da888 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,63 @@
# Contributing
-TODO
\ No newline at end of file
+We're so excited you're interested in helping with SuperTokens! We are happy to help you get started, even if you don't have any previous open-source experience :blush:
+
+## New to Open Source?
+1. Take a look at [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github)
+2. Go through the [SuperTokens Code of Conduct](https://github.com/supertokens/supertokens-auth-react/blob/master/CODE_OF_CONDUCT.md)
+
+## Where to ask Questions?
+1. Check our [Github Issues](https://github.com/supertokens/supertokens-auth-react/issues) to see if someone has already answered your question.
+2. Join our community on [Discord](https://supertokens.io/discord) and feel free to ask us your questions
+
+
+## Development Setup
+
+### Prerequisites
+- OS: Linux or macOS
+- Nodejs & npm
+- IDE: [VSCode](https://code.visualstudio.com/download)(recommended) or equivalent IDE
+
+### Project Setup
+1. `git clone https://github.com/supertokens/supertokens-auth-react.git`
+3. `cd supertokens-auth-react`
+4. Install the project dependencies
+ ```
+ npm i -d
+ ```
+5. Add git pre-commit hooks
+ ```
+ npm run set-up-hooks
+ ```
+
+## Modifying Code
+1. Open the `supertokens-auth-react` project in your IDE.
+2. You can start modifying the code.
+3. After modification, you need to build the project:
+ ```
+ npm run build-pretty
+ ```
+
+## Testing
+1. `npm run test`
+
+
+## Run the test application.
+1. `(cd react-test-app && npm install)`
+2. `./testApp.sh --start`
+
+
+## Pull Request
+1. Before submitting a pull request make sure all tests have passed
+2. Reference the relevant issue or pull request and give a clear description of changes/features added when submitting a pull request
+
+## SuperTokens Community
+SuperTokens is made possible by a passionate team and a strong community of developers. If you have any questions or would like to get more involved in the SuperTokens community you can check out:
+ - [Github Issues](https://github.com/supertokens/supertokens-auth-react/issues)
+ - [Discord](https://supertokens.io/discord)
+ - [Twitter](https://twitter.com/supertokensio)
+ - or [email us](mailto:team@supertokens.io)
+
+Additional resources you might find useful:
+ - [SuperTokens Docs](https://supertokens.io/docs/community/getting-started/installation)
+ - [Blog Posts](https://supertokens.io/blog/)
\ No newline at end of file
diff --git a/index.d.ts b/index.d.ts
deleted file mode 100644
index c10750f87..000000000
--- a/index.d.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import SuperTokens from './lib/build';
-
-export default SuperTokens;
\ No newline at end of file
diff --git a/lib/babel.config.js b/lib/babel.config.js
new file mode 100644
index 000000000..053a3c34d
--- /dev/null
+++ b/lib/babel.config.js
@@ -0,0 +1,4 @@
+module.exports = {
+ presets: ['@babel/preset-typescript', '@babel/preset-env', '@babel/preset-react'],
+ plugins: ["transform-class-properties"]
+}
\ No newline at end of file
diff --git a/lib/build/components/superTokensRoute.d.ts b/lib/build/components/superTokensRoute.d.ts
new file mode 100644
index 000000000..ba4e878b1
--- /dev/null
+++ b/lib/build/components/superTokensRoute.d.ts
@@ -0,0 +1,2 @@
+///
+export declare function getSuperTokensRoutesForReactRouterDom(): JSX.Element[];
diff --git a/lib/build/components/superTokensRoute.js b/lib/build/components/superTokensRoute.js
new file mode 100644
index 000000000..57f7e4be9
--- /dev/null
+++ b/lib/build/components/superTokensRoute.js
@@ -0,0 +1,153 @@
+"use strict";
+
+function _typeof(obj) {
+ "@babel/helpers - typeof";
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+ _typeof = function _typeof(obj) {
+ return typeof obj;
+ };
+ } else {
+ _typeof = function _typeof(obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
+ ? "symbol"
+ : typeof obj;
+ };
+ }
+ return _typeof(obj);
+}
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.getSuperTokensRoutesForReactRouterDom = getSuperTokensRoutesForReactRouterDom;
+
+var React = _interopRequireWildcard(require("react"));
+
+var _superTokens = _interopRequireDefault(require("../superTokens"));
+
+var _utils = require("../utils");
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function _getRequireWildcardCache() {
+ if (typeof WeakMap !== "function") return null;
+ var cache = new WeakMap();
+ _getRequireWildcardCache = function _getRequireWildcardCache() {
+ return cache;
+ };
+ return cache;
+}
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ }
+ if (obj === null || (_typeof(obj) !== "object" && typeof obj !== "function")) {
+ return { default: obj };
+ }
+ var cache = _getRequireWildcardCache();
+ if (cache && cache.has(obj)) {
+ return cache.get(obj);
+ }
+ var newObj = {};
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
+ if (desc && (desc.get || desc.set)) {
+ Object.defineProperty(newObj, key, desc);
+ } else {
+ newObj[key] = obj[key];
+ }
+ }
+ }
+ newObj["default"] = obj;
+ if (cache) {
+ cache.set(obj, newObj);
+ }
+ return newObj;
+}
+
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+
+/*
+ * SuperTokensRouteWithRecipeId
+ * Using react-router-dom, we can only match based on the route and not on the combination of path and query params.
+ * having one route per component would lead to clashes when two components have the same route but different recipeId,
+ * the first one would always take precedence.
+ * Hence, the component rendered in the Route is an abstraction that decides which Feature to render based
+ * on the rId.
+ * See SuperTokensRouteWithRecipeId below.
+ */
+function getSuperTokensRoutesForReactRouterDom() {
+ try {
+ var pathsToComponentWithRecipeIdMap = {};
+
+ _superTokens["default"].getRecipeList().map(function(recipe) {
+ var features = recipe.getFeatures();
+ return Object.keys(features).map(function(featurePath) {
+ var fullPath = "".concat(_superTokens["default"].getAppInfo().websiteBasePath).concat(featurePath); // If no components yet for this route, initialize empty array.
+
+ if (pathsToComponentWithRecipeIdMap[fullPath] === undefined) {
+ pathsToComponentWithRecipeIdMap[fullPath] = [];
+ }
+
+ pathsToComponentWithRecipeIdMap[fullPath].push({
+ rid: recipe.getRecipeId(),
+ component: features[featurePath]
+ });
+ });
+ });
+
+ return Object.keys(pathsToComponentWithRecipeIdMap).map(function(path) {
+ return SuperTokensRouteWithRecipeId(path, pathsToComponentWithRecipeIdMap[path]);
+ });
+ } catch (e) {
+ return [];
+ }
+}
+
+function SuperTokensRouteWithRecipeId(path, routeComponents) {
+ var Route = require("react-router-dom").Route;
+
+ var recipeId = (0, _utils.getRecipeIdFromSearch)(window.location.search); // If recipeId provided, try to find a match.
+
+ if (recipeId !== null) {
+ for (var i = 0; i < routeComponents.length; i++) {
+ if (recipeId === routeComponents[i].rid) {
+ return /*#__PURE__*/ React.createElement(Route, {
+ exact: true,
+ key: "st-".concat(path),
+ path: path,
+ component: routeComponents[i].component
+ });
+ }
+ }
+ } // Otherwise, If no recipe Id provided, or if no recipe id matches, return the first matching component.
+
+ return /*#__PURE__*/ React.createElement(Route, {
+ exact: true,
+ key: "st-".concat(path),
+ path: path,
+ component: routeComponents[0].component
+ });
+}
diff --git a/lib/build/constants.d.ts b/lib/build/constants.d.ts
new file mode 100644
index 000000000..a1fbe43fe
--- /dev/null
+++ b/lib/build/constants.d.ts
@@ -0,0 +1,5 @@
+export declare const RECIPE_ID_QUERY_PARAM = "rid";
+export declare const DEFAULT_API_BASE_PATH = "/auth";
+export declare const DEFAULT_WEBSITE_BASE_PATH = "/auth";
+export declare const ST_ROOT_CONTAINER = "supertokens-root";
+export declare const CLASS_UNKNOWN_RECIPE_ID = "supertokens-unknown-recipe-id";
diff --git a/lib/build/constants.js b/lib/build/constants.js
new file mode 100644
index 000000000..08d2edf6d
--- /dev/null
+++ b/lib/build/constants.js
@@ -0,0 +1,29 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.ST_ROOT_CONTAINER = exports.DEFAULT_WEBSITE_BASE_PATH = exports.DEFAULT_API_BASE_PATH = exports.RECIPE_ID_QUERY_PARAM = void 0;
+
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+var RECIPE_ID_QUERY_PARAM = "rid";
+exports.RECIPE_ID_QUERY_PARAM = RECIPE_ID_QUERY_PARAM;
+var DEFAULT_API_BASE_PATH = "/auth";
+exports.DEFAULT_API_BASE_PATH = DEFAULT_API_BASE_PATH;
+var DEFAULT_WEBSITE_BASE_PATH = "/auth";
+exports.DEFAULT_WEBSITE_BASE_PATH = DEFAULT_WEBSITE_BASE_PATH;
+var ST_ROOT_CONTAINER = "supertokens-root";
+exports.ST_ROOT_CONTAINER = ST_ROOT_CONTAINER;
diff --git a/lib/build/index.d.ts b/lib/build/index.d.ts
index d90c5667a..1da229243 100644
--- a/lib/build/index.d.ts
+++ b/lib/build/index.d.ts
@@ -1,2 +1,13 @@
-export default class SuperTokens {
+///
+import { SuperTokensConfig } from "./types";
+import SuperTokens from "./superTokens";
+export default class SuperTokensAPIWrapper {
+ static init(config: SuperTokensConfig): void;
+ static canHandleRoute(): boolean;
+ static getRoutingComponent(): import("react").ComponentClass<{}, any> | undefined;
+ static getSuperTokensRoutesForReactRouterDom(): JSX.Element[];
}
+export declare const canHandleRoute: typeof SuperTokensAPIWrapper.canHandleRoute;
+export declare const init: typeof SuperTokensAPIWrapper.init;
+export declare const getRoutingComponent: typeof SuperTokensAPIWrapper.getRoutingComponent;
+export declare const getSuperTokensRoutesForReactRouterDom: typeof SuperTokens.getSuperTokensRoutesForReactRouterDom;
diff --git a/lib/build/index.js b/lib/build/index.js
index 4287b3f42..99b291b4d 100644
--- a/lib/build/index.js
+++ b/lib/build/index.js
@@ -1,21 +1,84 @@
"use strict";
-/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
- *
- * This software is licensed under the Apache License, Version 2.0 (the
- * "License") as published by the Apache Software Foundation.
- *
- * You may not use this file except in compliance with the License. You may
- * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.getSuperTokensRoutesForReactRouterDom = exports.getRoutingComponent = exports.init = exports.canHandleRoute = exports[
+ "default"
+] = void 0;
+
+var _superTokens = _interopRequireDefault(require("./superTokens"));
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+/*
+ * API Wrapper exposed to user.
*/
-Object.defineProperty(exports, "__esModule", { value: true });
-var SuperTokens = /** @class */ (function() {
- function SuperTokens() {}
- return SuperTokens;
+var SuperTokensAPIWrapper = /*#__PURE__*/ (function() {
+ function SuperTokensAPIWrapper() {
+ _classCallCheck(this, SuperTokensAPIWrapper);
+ }
+
+ _createClass(SuperTokensAPIWrapper, null, [
+ {
+ key: "init",
+ value: function init(config) {
+ _superTokens["default"].init(config);
+ }
+ },
+ {
+ key: "canHandleRoute",
+ value: function canHandleRoute() {
+ return _superTokens["default"].canHandleRoute();
+ }
+ },
+ {
+ key: "getRoutingComponent",
+ value: function getRoutingComponent() {
+ return _superTokens["default"].getRoutingComponent();
+ }
+ },
+ {
+ key: "getSuperTokensRoutesForReactRouterDom",
+ value: function getSuperTokensRoutesForReactRouterDom() {
+ return _superTokens["default"].getSuperTokensRoutesForReactRouterDom();
+ }
+ }
+ ]);
+
+ return SuperTokensAPIWrapper;
})();
-exports.default = SuperTokens;
+
+exports["default"] = SuperTokensAPIWrapper;
+var canHandleRoute = SuperTokensAPIWrapper.canHandleRoute;
+exports.canHandleRoute = canHandleRoute;
+var init = SuperTokensAPIWrapper.init;
+exports.init = init;
+var getRoutingComponent = SuperTokensAPIWrapper.getRoutingComponent;
+exports.getRoutingComponent = getRoutingComponent;
+var getSuperTokensRoutesForReactRouterDom = _superTokens["default"].getSuperTokensRoutesForReactRouterDom;
+exports.getSuperTokensRoutesForReactRouterDom = getSuperTokensRoutesForReactRouterDom;
diff --git a/lib/build/recipe/emailpassword/SignInAndUp.js b/lib/build/recipe/emailpassword/SignInAndUp.js
new file mode 100644
index 000000000..3e760edc1
--- /dev/null
+++ b/lib/build/recipe/emailpassword/SignInAndUp.js
@@ -0,0 +1,247 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = void 0;
+
+var React = _interopRequireWildcard(require("react"));
+
+var _constants = require("../../constants");
+
+var _ = _interopRequireWildcard(require("."));
+
+var _emotion = _interopRequireDefault(require("react-shadow/emotion"));
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function _getRequireWildcardCache() {
+ if (typeof WeakMap !== "function") return null;
+ var cache = new WeakMap();
+ _getRequireWildcardCache = function _getRequireWildcardCache() {
+ return cache;
+ };
+ return cache;
+}
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ }
+ if (obj === null || (_typeof(obj) !== "object" && typeof obj !== "function")) {
+ return { default: obj };
+ }
+ var cache = _getRequireWildcardCache();
+ if (cache && cache.has(obj)) {
+ return cache.get(obj);
+ }
+ var newObj = {};
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
+ if (desc && (desc.get || desc.set)) {
+ Object.defineProperty(newObj, key, desc);
+ } else {
+ newObj[key] = obj[key];
+ }
+ }
+ }
+ newObj["default"] = obj;
+ if (cache) {
+ cache.set(obj, newObj);
+ }
+ return newObj;
+}
+
+function _typeof(obj) {
+ "@babel/helpers - typeof";
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+ _typeof = function _typeof(obj) {
+ return typeof obj;
+ };
+ } else {
+ _typeof = function _typeof(obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
+ ? "symbol"
+ : typeof obj;
+ };
+ }
+ return _typeof(obj);
+}
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: { value: subClass, writable: true, configurable: true }
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
+}
+
+function _setPrototypeOf(o, p) {
+ _setPrototypeOf =
+ Object.setPrototypeOf ||
+ function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
+ return _setPrototypeOf(o, p);
+}
+
+function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+ result = Reflect.construct(Super, arguments, NewTarget);
+ } else {
+ result = Super.apply(this, arguments);
+ }
+ return _possibleConstructorReturn(this, result);
+ };
+}
+
+function _possibleConstructorReturn(self, call) {
+ if (call && (_typeof(call) === "object" || typeof call === "function")) {
+ return call;
+ }
+ return _assertThisInitialized(self);
+}
+
+function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+ return self;
+}
+
+function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
+ try {
+ Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf
+ ? Object.getPrototypeOf
+ : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
+}
+
+/*
+ * Component.
+ */
+var SignInAndUp = /*#__PURE__*/ (function(_React$Component) {
+ _inherits(SignInAndUp, _React$Component);
+
+ var _super = _createSuper(SignInAndUp);
+
+ function SignInAndUp() {
+ var _temp, _this;
+
+ _classCallCheck(this, SignInAndUp);
+
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _possibleConstructorReturn(
+ _this,
+ ((_temp = _this = _super.call.apply(_super, [this].concat(args))),
+ (_this.getRecipeInstanceOrThrow = function() {
+ var instance;
+
+ if (_this.props.__internal !== undefined && _this.props.__internal.instance !== undefined) {
+ instance = _this.props.__internal.instance;
+ } else {
+ instance = _["default"].getInstanceIfDefined();
+ }
+
+ return instance;
+ }),
+ _temp)
+ );
+ }
+
+ _createClass(SignInAndUp, [
+ {
+ key: "render",
+ value: function render() {
+ return /*#__PURE__*/ React.createElement(
+ _emotion["default"].div,
+ {
+ id: _constants.ST_ROOT_CONTAINER
+ },
+ /*#__PURE__*/ React.createElement(
+ _.SignInAndUpTheme, // TODO Get the form Fields from the recipe.
+ {
+ formFields: [
+ {
+ id: "email",
+ label: "Email",
+ placeholder: "youremail@example.com",
+ validate: function validate(email) {
+ return new Promise(function(resolve) {
+ return resolve(true);
+ });
+ }
+ },
+ {
+ id: "password",
+ label: "Password",
+ placeholder: "Enter your password",
+ validate: function validate(password) {
+ return new Promise(function(resolve) {
+ return resolve(true);
+ });
+ }
+ }
+ ]
+ }
+ )
+ );
+ }
+ }
+ ]);
+
+ return SignInAndUp;
+})(React.Component);
+
+var _default = SignInAndUp;
+exports["default"] = _default;
diff --git a/lib/build/recipe/emailpassword/SignInAndUpTheme.d.ts b/lib/build/recipe/emailpassword/SignInAndUpTheme.d.ts
new file mode 100644
index 000000000..dda3e8f44
--- /dev/null
+++ b/lib/build/recipe/emailpassword/SignInAndUpTheme.d.ts
@@ -0,0 +1,6 @@
+import * as React from "react";
+import { ThemeProps } from "../../types";
+declare class SignInAndUpTheme extends React.Component {
+ render(): JSX.Element;
+}
+export default SignInAndUpTheme;
diff --git a/lib/build/recipe/emailpassword/SignInAndUpTheme.js b/lib/build/recipe/emailpassword/SignInAndUpTheme.js
new file mode 100644
index 000000000..daa757919
--- /dev/null
+++ b/lib/build/recipe/emailpassword/SignInAndUpTheme.js
@@ -0,0 +1,271 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = void 0;
+
+var React = _interopRequireWildcard(require("react"));
+
+var _styles = require("../../styles/styles");
+
+var _core = require("@emotion/core");
+
+function _getRequireWildcardCache() {
+ if (typeof WeakMap !== "function") return null;
+ var cache = new WeakMap();
+ _getRequireWildcardCache = function _getRequireWildcardCache() {
+ return cache;
+ };
+ return cache;
+}
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ }
+ if (obj === null || (_typeof(obj) !== "object" && typeof obj !== "function")) {
+ return { default: obj };
+ }
+ var cache = _getRequireWildcardCache();
+ if (cache && cache.has(obj)) {
+ return cache.get(obj);
+ }
+ var newObj = {};
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
+ if (desc && (desc.get || desc.set)) {
+ Object.defineProperty(newObj, key, desc);
+ } else {
+ newObj[key] = obj[key];
+ }
+ }
+ }
+ newObj["default"] = obj;
+ if (cache) {
+ cache.set(obj, newObj);
+ }
+ return newObj;
+}
+
+function _typeof(obj) {
+ "@babel/helpers - typeof";
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+ _typeof = function _typeof(obj) {
+ return typeof obj;
+ };
+ } else {
+ _typeof = function _typeof(obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
+ ? "symbol"
+ : typeof obj;
+ };
+ }
+ return _typeof(obj);
+}
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: { value: subClass, writable: true, configurable: true }
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
+}
+
+function _setPrototypeOf(o, p) {
+ _setPrototypeOf =
+ Object.setPrototypeOf ||
+ function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
+ return _setPrototypeOf(o, p);
+}
+
+function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+ result = Reflect.construct(Super, arguments, NewTarget);
+ } else {
+ result = Super.apply(this, arguments);
+ }
+ return _possibleConstructorReturn(this, result);
+ };
+}
+
+function _possibleConstructorReturn(self, call) {
+ if (call && (_typeof(call) === "object" || typeof call === "function")) {
+ return call;
+ }
+ return _assertThisInitialized(self);
+}
+
+function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+ return self;
+}
+
+function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
+ try {
+ Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf
+ ? Object.getPrototypeOf
+ : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
+}
+
+/*
+ * Component.
+ */
+var SignInAndUpTheme = /*#__PURE__*/ (function(_React$Component) {
+ _inherits(SignInAndUpTheme, _React$Component);
+
+ var _super = _createSuper(SignInAndUpTheme);
+
+ function SignInAndUpTheme() {
+ _classCallCheck(this, SignInAndUpTheme);
+
+ return _super.apply(this, arguments);
+ }
+
+ _createClass(SignInAndUpTheme, [
+ {
+ key: "render",
+ value: function render() {
+ return (0, _core.jsx)(
+ "div",
+ {
+ css: _styles.defaultStyles.container
+ },
+ (0, _core.jsx)(
+ "div",
+ {
+ css: _styles.defaultStyles.row
+ },
+ (0, _core.jsx)(
+ "div",
+ {
+ css: styles.header
+ },
+ (0, _core.jsx)(
+ "div",
+ {
+ css: styles.headerTitle
+ },
+ "Sign In"
+ ),
+ (0, _core.jsx)(
+ "div",
+ {
+ css: styles.headerSubtitle
+ },
+ (0, _core.jsx)("div", null, "Not registered yet?"),
+ (0, _core.jsx)(
+ "div",
+ {
+ css: styles.signUpLink
+ },
+ "Sign up"
+ )
+ )
+ ),
+ (0, _core.jsx)("div", {
+ css: _styles.defaultStyles.divider
+ }),
+ (0, _core.jsx)(
+ "form",
+ null,
+ this.props.formFields.map(function(field) {
+ return (0, _core.jsx)(
+ "div",
+ {
+ key: field.id
+ },
+ (0, _core.jsx)("label", null, field.label),
+ (0, _core.jsx)("input", {
+ name: field.id,
+ placeholder: field.placeholder
+ })
+ );
+ })
+ ),
+ (0, _core.jsx)("button", null, " Sign In "),
+ (0, _core.jsx)("div", null, "Forgot password?")
+ )
+ );
+ }
+ }
+ ]);
+
+ return SignInAndUpTheme;
+})(React.Component);
+
+var styles = {
+ header: {
+ height: "141px"
+ },
+ headerTitle: {
+ paddingTop: "49px",
+ fontSize: _styles.palette.fonts.size[1],
+ lineHeight: "40px",
+ letterSpacing: "0.28px",
+ fontWeight: 700,
+ fontFamily: _styles.palette.fonts.primary,
+ color: _styles.palette.colors.primary
+ },
+ headerSubtitle: {
+ fontSize: _styles.palette.fonts.size[0],
+ fontWeight: 400,
+ color: _styles.palette.colors.secondary,
+ fontFamily: _styles.palette.fonts.primary
+ },
+ signUpLink: {
+ color: "blue"
+ }
+};
+var _default = SignInAndUpTheme;
+exports["default"] = _default;
diff --git a/lib/build/recipe/emailpassword/SignInUp.d.ts b/lib/build/recipe/emailpassword/SignInUp.d.ts
new file mode 100644
index 000000000..ad79af1a2
--- /dev/null
+++ b/lib/build/recipe/emailpassword/SignInUp.d.ts
@@ -0,0 +1,7 @@
+import * as React from "react";
+import { RecipeModuleProps } from '../../types';
+declare class SignInUp extends React.Component {
+ getRecipeInstanceOrThrow: () => import("../recipeModule").default;
+ render(): JSX.Element;
+}
+export default SignInUp;
diff --git a/lib/build/recipe/emailpassword/SignInUp.js b/lib/build/recipe/emailpassword/SignInUp.js
new file mode 100644
index 000000000..34e81d9f5
--- /dev/null
+++ b/lib/build/recipe/emailpassword/SignInUp.js
@@ -0,0 +1,247 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = void 0;
+
+var React = _interopRequireWildcard(require("react"));
+
+var _constants = require("../../constants");
+
+var _ = _interopRequireWildcard(require("./"));
+
+var _emotion = _interopRequireDefault(require("react-shadow/emotion"));
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function _getRequireWildcardCache() {
+ if (typeof WeakMap !== "function") return null;
+ var cache = new WeakMap();
+ _getRequireWildcardCache = function _getRequireWildcardCache() {
+ return cache;
+ };
+ return cache;
+}
+
+function _interopRequireWildcard(obj) {
+ if (obj && obj.__esModule) {
+ return obj;
+ }
+ if (obj === null || (_typeof(obj) !== "object" && typeof obj !== "function")) {
+ return { default: obj };
+ }
+ var cache = _getRequireWildcardCache();
+ if (cache && cache.has(obj)) {
+ return cache.get(obj);
+ }
+ var newObj = {};
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
+ if (desc && (desc.get || desc.set)) {
+ Object.defineProperty(newObj, key, desc);
+ } else {
+ newObj[key] = obj[key];
+ }
+ }
+ }
+ newObj["default"] = obj;
+ if (cache) {
+ cache.set(obj, newObj);
+ }
+ return newObj;
+}
+
+function _typeof(obj) {
+ "@babel/helpers - typeof";
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+ _typeof = function _typeof(obj) {
+ return typeof obj;
+ };
+ } else {
+ _typeof = function _typeof(obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
+ ? "symbol"
+ : typeof obj;
+ };
+ }
+ return _typeof(obj);
+}
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: { value: subClass, writable: true, configurable: true }
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
+}
+
+function _setPrototypeOf(o, p) {
+ _setPrototypeOf =
+ Object.setPrototypeOf ||
+ function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
+ return _setPrototypeOf(o, p);
+}
+
+function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+ result = Reflect.construct(Super, arguments, NewTarget);
+ } else {
+ result = Super.apply(this, arguments);
+ }
+ return _possibleConstructorReturn(this, result);
+ };
+}
+
+function _possibleConstructorReturn(self, call) {
+ if (call && (_typeof(call) === "object" || typeof call === "function")) {
+ return call;
+ }
+ return _assertThisInitialized(self);
+}
+
+function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+ return self;
+}
+
+function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
+ try {
+ Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf
+ ? Object.getPrototypeOf
+ : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
+}
+
+/*
+ * Component.
+ */
+var SignInUp = /*#__PURE__*/ (function(_React$Component) {
+ _inherits(SignInUp, _React$Component);
+
+ var _super = _createSuper(SignInUp);
+
+ function SignInUp() {
+ var _temp, _this;
+
+ _classCallCheck(this, SignInUp);
+
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
+ args[_key] = arguments[_key];
+ }
+
+ return _possibleConstructorReturn(
+ _this,
+ ((_temp = _this = _super.call.apply(_super, [this].concat(args))),
+ (_this.getRecipeInstanceOrThrow = function() {
+ var instance;
+
+ if (_this.props.__internal !== undefined && _this.props.__internal.instance !== undefined) {
+ instance = _this.props.__internal.instance;
+ } else {
+ instance = _["default"].getInstanceIfDefined();
+ }
+
+ return instance;
+ }),
+ _temp)
+ );
+ }
+
+ _createClass(SignInUp, [
+ {
+ key: "render",
+ value: function render() {
+ return /*#__PURE__*/ React.createElement(
+ _emotion["default"].div,
+ {
+ id: _constants.ST_ROOT_CONTAINER
+ },
+ /*#__PURE__*/ React.createElement(
+ _.SignInAndUpTheme, // TODO Get the form Fields from the recipe.
+ {
+ formFields: [
+ {
+ id: "email",
+ label: "Email",
+ placeholder: "youremail@example.com",
+ validate: function validate(email) {
+ return new Promise(function(resolve) {
+ return resolve(true);
+ });
+ }
+ },
+ {
+ id: "password",
+ label: "Password",
+ placeholder: "Enter your password",
+ validate: function validate(password) {
+ return new Promise(function(resolve) {
+ return resolve(true);
+ });
+ }
+ }
+ ]
+ }
+ )
+ );
+ }
+ }
+ ]);
+
+ return SignInUp;
+})(React.Component);
+
+var _default = SignInUp;
+exports["default"] = _default;
diff --git a/lib/build/recipe/emailpassword/index.d.ts b/lib/build/recipe/emailpassword/index.d.ts
new file mode 100644
index 000000000..bd407a5fb
--- /dev/null
+++ b/lib/build/recipe/emailpassword/index.d.ts
@@ -0,0 +1,11 @@
+import RecipeModule from "../recipeModule";
+import { EmailPasswordConfig } from "../../types";
+import SignInUp from "./SignInUp";
+import SignInAndUpTheme from "./SignInAndUpTheme";
+export default class EmailPassword extends RecipeModule {
+ static instance?: EmailPassword;
+ constructor(config: EmailPasswordConfig);
+ static init(config: EmailPasswordConfig): RecipeModule;
+ static getInstanceIfDefined(): EmailPassword;
+}
+export { SignInUp, SignInAndUpTheme };
diff --git a/lib/build/recipe/emailpassword/index.js b/lib/build/recipe/emailpassword/index.js
new file mode 100644
index 000000000..0f8d62e57
--- /dev/null
+++ b/lib/build/recipe/emailpassword/index.js
@@ -0,0 +1,234 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+Object.defineProperty(exports, "SignInAndUp", {
+ enumerable: true,
+ get: function get() {
+ return _SignInAndUp["default"];
+ }
+});
+Object.defineProperty(exports, "SignInAndUpTheme", {
+ enumerable: true,
+ get: function get() {
+ return _SignInAndUpTheme["default"];
+ }
+});
+exports["default"] = void 0;
+
+var _recipeModule = _interopRequireDefault(require("../recipeModule"));
+
+var _SignInAndUp = _interopRequireDefault(require("./SignInAndUp"));
+
+var _SignInAndUpTheme = _interopRequireDefault(require("./SignInAndUpTheme"));
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function _typeof(obj) {
+ "@babel/helpers - typeof";
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+ _typeof = function _typeof(obj) {
+ return typeof obj;
+ };
+ } else {
+ _typeof = function _typeof(obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype
+ ? "symbol"
+ : typeof obj;
+ };
+ }
+ return _typeof(obj);
+}
+
+function ownKeys(object, enumerableOnly) {
+ var keys = Object.keys(object);
+ if (Object.getOwnPropertySymbols) {
+ var symbols = Object.getOwnPropertySymbols(object);
+ if (enumerableOnly)
+ symbols = symbols.filter(function(sym) {
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
+ });
+ keys.push.apply(keys, symbols);
+ }
+ return keys;
+}
+
+function _objectSpread(target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i] != null ? arguments[i] : {};
+ if (i % 2) {
+ ownKeys(Object(source), true).forEach(function(key) {
+ _defineProperty(target, key, source[key]);
+ });
+ } else if (Object.getOwnPropertyDescriptors) {
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
+ } else {
+ ownKeys(Object(source)).forEach(function(key) {
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
+ });
+ }
+ }
+ return target;
+}
+
+function _defineProperty(obj, key, value) {
+ if (key in obj) {
+ Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true });
+ } else {
+ obj[key] = value;
+ }
+ return obj;
+}
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: { value: subClass, writable: true, configurable: true }
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
+}
+
+function _setPrototypeOf(o, p) {
+ _setPrototypeOf =
+ Object.setPrototypeOf ||
+ function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
+ return _setPrototypeOf(o, p);
+}
+
+function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+ result = Reflect.construct(Super, arguments, NewTarget);
+ } else {
+ result = Super.apply(this, arguments);
+ }
+ return _possibleConstructorReturn(this, result);
+ };
+}
+
+function _possibleConstructorReturn(self, call) {
+ if (call && (_typeof(call) === "object" || typeof call === "function")) {
+ return call;
+ }
+ return _assertThisInitialized(self);
+}
+
+function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+ return self;
+}
+
+function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
+ try {
+ Date.prototype.toString.call(Reflect.construct(Date, [], function() {}));
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
+
+function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf
+ ? Object.getPrototypeOf
+ : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
+}
+
+/*
+ * Class.
+ */
+var EmailPassword = /*#__PURE__*/ (function(_RecipeModule) {
+ _inherits(EmailPassword, _RecipeModule);
+
+ var _super = _createSuper(EmailPassword);
+
+ function EmailPassword(config) {
+ _classCallCheck(this, EmailPassword);
+
+ return _super.call(
+ this,
+ _objectSpread(
+ _objectSpread({}, config),
+ {},
+ {
+ recipeId: "email-password",
+ features: {
+ "/": _SignInAndUp["default"]
+ }
+ }
+ )
+ );
+ }
+
+ _createClass(EmailPassword, null, [
+ {
+ key: "init",
+ value: function init(config) {
+ return function() {
+ EmailPassword.instance = new EmailPassword(config);
+ return EmailPassword.instance;
+ };
+ }
+ },
+ {
+ key: "getInstanceIfDefined",
+ value: function getInstanceIfDefined() {
+ if (EmailPassword.instance === undefined) {
+ throw Error(
+ "No instance of ".concat(
+ EmailPassword.constructor.name,
+ ' found. Make sure to call the "init" method.'
+ )
+ ); // TODO Add relevant doc.
+ }
+
+ return EmailPassword.instance;
+ }
+ }
+ ]);
+
+ return EmailPassword;
+})(_recipeModule["default"]);
+
+exports["default"] = EmailPassword;
diff --git a/lib/build/recipe/recipeModule.d.ts b/lib/build/recipe/recipeModule.d.ts
new file mode 100644
index 000000000..d4e95e32b
--- /dev/null
+++ b/lib/build/recipe/recipeModule.d.ts
@@ -0,0 +1,12 @@
+import { RouteToFeatureComponentMap, RecipeModuleConfig } from "../types";
+import { ComponentClass } from "react";
+import SuperTokensUrl from "../superTokensUrl";
+export default abstract class RecipeModule {
+ private features;
+ private recipeId;
+ constructor(config: RecipeModuleConfig);
+ getRecipeId: () => string;
+ getFeatures: () => RouteToFeatureComponentMap;
+ canHandleRoute: (url: SuperTokensUrl) => boolean;
+ getRoutingComponent: (url: SuperTokensUrl) => ComponentClass<{}, any> | undefined;
+}
diff --git a/lib/build/recipe/recipeModule.js b/lib/build/recipe/recipeModule.js
new file mode 100644
index 000000000..ef6712f0f
--- /dev/null
+++ b/lib/build/recipe/recipeModule.js
@@ -0,0 +1,66 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = void 0;
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+
+/*
+ * Class.
+ */
+var RecipeModule = function RecipeModule(config) {
+ var _this = this;
+
+ _classCallCheck(this, RecipeModule);
+
+ this.getRecipeId = function() {
+ return _this.recipeId;
+ };
+
+ this.getFeatures = function() {
+ return _this.features;
+ };
+
+ this.canHandleRoute = function(url) {
+ return _this.getRoutingComponent(url) !== undefined;
+ };
+
+ this.getRoutingComponent = function(url) {
+ // If rId from URL exists and doesn't match, or if route path doesn't start with return undefined.
+ if (url.recipeId !== null && url.recipeId !== _this.recipeId) {
+ return undefined;
+ }
+
+ return _this.features[url.normalisedPathnameWithoutWebsiteBasePath];
+ };
+
+ this.recipeId = config.recipeId;
+ this.features = config.features;
+};
+
+exports["default"] = RecipeModule;
diff --git a/lib/build/styles/styles.d.ts b/lib/build/styles/styles.d.ts
new file mode 100644
index 000000000..0dd4d8ea0
--- /dev/null
+++ b/lib/build/styles/styles.d.ts
@@ -0,0 +1,36 @@
+export declare const palette: {
+ colors: {
+ background: string;
+ primary: string;
+ secondary: string;
+ };
+ fonts: {
+ size: number[];
+ primary: string;
+ };
+};
+export declare const defaultStyles: {
+ container: {
+ maxWidth: string;
+ width: string;
+ margin: string;
+ minWidth: string;
+ height: string;
+ borderRadius: string;
+ boxShadow: string;
+ backgroundColor: string;
+ "@media (max-width: 440px)": {
+ minWidth: string;
+ };
+ };
+ row: {
+ margin: string;
+ width: string;
+ };
+ divider: {
+ margin: string;
+ borderBottom: string;
+ display: string;
+ alignItems: string;
+ };
+};
diff --git a/lib/build/styles/styles.js b/lib/build/styles/styles.js
new file mode 100644
index 000000000..d2f601bd1
--- /dev/null
+++ b/lib/build/styles/styles.js
@@ -0,0 +1,67 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.defaultStyles = exports.palette = void 0;
+
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Palette
+ */
+var palette = {
+ colors: {
+ background: "white",
+ primary: "#222222",
+ secondary: "#656565"
+ },
+ fonts: {
+ size: [16, 28],
+ primary: "Rubik"
+ }
+};
+/*
+ * Default styles.
+ */
+
+exports.palette = palette;
+var defaultStyles = {
+ container: {
+ maxWidth: "524px",
+ width: "60vw",
+ margin: "0 auto",
+ minWidth: "420px",
+ height: "498px",
+ borderRadius: "8px",
+ boxShadow: "1px 1px 10px rgba(0,0,0,0.16)",
+ backgroundColor: palette.colors.background,
+ "@media (max-width: 440px)": {
+ minWidth: "320px"
+ }
+ },
+ row: {
+ margin: "0 auto",
+ width: "60%"
+ },
+ divider: {
+ margin: "1em -1em",
+ borderBottom: "0.3px solid #dddddd",
+ display: "flex",
+ alignItems: "center"
+ }
+};
+exports.defaultStyles = defaultStyles;
diff --git a/lib/build/superTokensUrl.d.ts b/lib/build/superTokensUrl.d.ts
new file mode 100644
index 000000000..06e8e6810
--- /dev/null
+++ b/lib/build/superTokensUrl.d.ts
@@ -0,0 +1,7 @@
+export default class SuperTokensUrl {
+ recipeId: string | null;
+ normalisedPathname: string;
+ matchesBasePath: boolean;
+ normalisedPathnameWithoutWebsiteBasePath: string;
+ constructor();
+}
diff --git a/lib/build/superTokensUrl.js b/lib/build/superTokensUrl.js
new file mode 100644
index 000000000..1310c19ae
--- /dev/null
+++ b/lib/build/superTokensUrl.js
@@ -0,0 +1,37 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = void 0;
+
+var _superTokens = _interopRequireDefault(require("./superTokens"));
+
+var _utils = require("./utils");
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+/*
+ * Class.
+ */
+var SuperTokensUrl = function SuperTokensUrl() {
+ _classCallCheck(this, SuperTokensUrl);
+
+ this.recipeId = (0, _utils.getRecipeIdFromSearch)(window.location.search);
+ this.normalisedPathname = (0, _utils.removePendingSlashFromPath)(window.location.pathname);
+ this.matchesBasePath = this.normalisedPathname.startsWith(_superTokens["default"].getAppInfo().websiteBasePath);
+ this.normalisedPathnameWithoutWebsiteBasePath = (0, _utils.getNormalisedRouteWithoutWebsiteBasePath)(
+ this.normalisedPathname,
+ _superTokens["default"].getAppInfo().websiteBasePath
+ );
+};
+
+exports["default"] = SuperTokensUrl;
diff --git a/lib/build/supertokens.d.ts b/lib/build/supertokens.d.ts
new file mode 100644
index 000000000..3d3e50ed2
--- /dev/null
+++ b/lib/build/supertokens.d.ts
@@ -0,0 +1,22 @@
+import RecipeModule from "./recipe/recipeModule";
+import { AppInfo, SuperTokensConfig } from "./types";
+import { ComponentClass } from "react";
+export default class SuperTokens {
+ private static instance?;
+ private appInfo;
+ private recipeList;
+ constructor(config: SuperTokensConfig);
+ static init(config: SuperTokensConfig): void;
+ private static getInstanceIfDefined;
+ static getAppInfo(): AppInfo;
+ static canHandleRoute(): boolean;
+ static getRoutingComponent(): ComponentClass | undefined;
+ static getRecipeList(): RecipeModule[];
+ static getNormalisedURLPathOrDefault(defaultPath: string, path?: string): string;
+ static getSuperTokensRoutesForReactRouterDom(): JSX.Element[];
+ getAppInfo: () => AppInfo;
+ canHandleRoute: () => boolean;
+ getRoutingComponent: () => ComponentClass<{}, any> | undefined;
+ getRecipeList: () => RecipeModule[];
+ static reset(): void;
+}
diff --git a/lib/build/supertokens.js b/lib/build/supertokens.js
new file mode 100644
index 000000000..9e1fd5e34
--- /dev/null
+++ b/lib/build/supertokens.js
@@ -0,0 +1,216 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports["default"] = void 0;
+
+var _constants = require("./constants");
+
+var _superTokensUrl = _interopRequireDefault(require("./superTokensUrl"));
+
+var _utils = require("./utils");
+
+function _interopRequireDefault(obj) {
+ return obj && obj.__esModule ? obj : { default: obj };
+}
+
+function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+}
+
+function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+}
+
+function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+}
+
+var _require = require("./components/superTokensRoute"),
+ _getSuperTokensRoutesForReactRouterDom = _require.getSuperTokensRoutesForReactRouterDom;
+/*
+ * Class.
+ */
+
+var SuperTokens = /*#__PURE__*/ (function() {
+ /*
+ * Static Attributes.
+ */
+
+ /*
+ * Instance Attributes.
+ */
+
+ /*
+ * Constructor.
+ */
+ function SuperTokens(config) {
+ var _this = this;
+
+ _classCallCheck(this, SuperTokens);
+
+ this.recipeList = [];
+
+ this.getAppInfo = function() {
+ return _this.appInfo;
+ };
+
+ this.canHandleRoute = function() {
+ var url = new _superTokensUrl["default"](); // If pathname doesn't start with websiteBasePath, return false.
+
+ if (!url.matchesBasePath) {
+ return false;
+ }
+
+ return _this.recipeList.some(function(recipe) {
+ return recipe.canHandleRoute(url);
+ });
+ };
+
+ this.getRoutingComponent = function() {
+ var url = new _superTokensUrl["default"](); // If pathname doesn't start with websiteBasePath, return false.
+
+ if (!url.matchesBasePath) {
+ return undefined;
+ }
+
+ var component;
+
+ for (var i = 0; i < _this.recipeList.length; i++) {
+ component = _this.recipeList[i].getRoutingComponent(url);
+
+ if (component !== undefined) {
+ break;
+ }
+ }
+
+ return component;
+ };
+
+ this.getRecipeList = function() {
+ return _this.recipeList;
+ };
+
+ this.appInfo = {
+ appName: config.appInfo.appName,
+ apiDomain: (0, _utils.normaliseURLDomainOrThrowError)(config.appInfo.apiDomain),
+ websiteDomain: (0, _utils.normaliseURLDomainOrThrowError)(config.appInfo.websiteDomain),
+ apiBasePath: SuperTokens.getNormalisedURLPathOrDefault(
+ _constants.DEFAULT_API_BASE_PATH,
+ config.appInfo.apiBasePath
+ ),
+ websiteBasePath: SuperTokens.getNormalisedURLPathOrDefault(
+ _constants.DEFAULT_WEBSITE_BASE_PATH,
+ config.appInfo.websiteBasePath
+ ),
+ logoFullURL: config.appInfo.logoFullURL
+ };
+
+ if (config.recipeList === undefined) {
+ throw new Error("No recipeList provided to SuperTokens."); // TODO Add link to appropriate docs.
+ }
+
+ this.recipeList = config.recipeList.map(function(recipe) {
+ return recipe();
+ });
+ }
+ /*
+ * Static Methods.
+ */
+
+ _createClass(SuperTokens, null, [
+ {
+ key: "init",
+ value: function init(config) {
+ if (SuperTokens.instance !== undefined) {
+ throw new Error("SuperTokens was already initialized");
+ }
+
+ SuperTokens.instance = new SuperTokens(config);
+ }
+ },
+ {
+ key: "getInstanceIfDefined",
+ value: function getInstanceIfDefined() {
+ if (SuperTokens.instance === undefined) {
+ throw new Error("SuperTokens must be initialized before calling this method.");
+ }
+
+ return SuperTokens.instance;
+ }
+ },
+ {
+ key: "getAppInfo",
+ value: function getAppInfo() {
+ return SuperTokens.getInstanceIfDefined().getAppInfo();
+ }
+ },
+ {
+ key: "canHandleRoute",
+ value: function canHandleRoute() {
+ return SuperTokens.getInstanceIfDefined().canHandleRoute();
+ }
+ },
+ {
+ key: "getRoutingComponent",
+ value: function getRoutingComponent() {
+ return SuperTokens.getInstanceIfDefined().getRoutingComponent();
+ }
+ },
+ {
+ key: "getRecipeList",
+ value: function getRecipeList() {
+ return SuperTokens.getInstanceIfDefined().getRecipeList();
+ }
+ },
+ {
+ key: "getNormalisedURLPathOrDefault",
+ value: function getNormalisedURLPathOrDefault(defaultPath, path) {
+ if (path !== undefined) {
+ return (0, _utils.normaliseURLPathOrThrowError)(path);
+ } else {
+ return defaultPath;
+ }
+ }
+ },
+ {
+ key: "getSuperTokensRoutesForReactRouterDom",
+ value: function getSuperTokensRoutesForReactRouterDom() {
+ return _getSuperTokensRoutesForReactRouterDom();
+ }
+ /*
+ * Instance Methods.
+ */
+ },
+ {
+ key: "reset",
+
+ /*
+ * Tests methods.
+ */
+ value: function reset() {
+ if (!(0, _utils.isTest)()) {
+ return;
+ }
+
+ SuperTokens.instance = undefined;
+ return;
+ }
+ }
+ ]);
+
+ return SuperTokens;
+})();
+
+exports["default"] = SuperTokens;
diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts
new file mode 100644
index 000000000..6b4761964
--- /dev/null
+++ b/lib/build/types.d.ts
@@ -0,0 +1,55 @@
+import { ComponentClass } from "react";
+import RecipeModule from "./recipe/recipeModule";
+export declare type SuperTokensConfig = {
+ appInfo: AppInfoUserInput;
+ recipeList: RecipeModule[];
+};
+declare type AppInfoBase = {
+ appName: string;
+ apiDomain: string;
+ websiteDomain: string;
+ logoFullURL?: string;
+};
+export declare type AppInfoUserInput = AppInfoBase & {
+ apiBasePath?: string;
+ websiteBasePath?: string;
+};
+export declare type AppInfo = AppInfoBase & {
+ apiBasePath: string;
+ websiteBasePath: string;
+};
+export declare type EmailPasswordConfig = RecipeModuleConfig & {
+ signInAndUpFeature: any;
+ resetPasswordUsingTokenFeature: any;
+};
+export declare type RouteToFeatureComponentMap = {
+ [route: string]: ComponentClass;
+};
+export declare type RecipeModuleConfig = {
+ features: RouteToFeatureComponentMap;
+ recipeId: string;
+};
+export declare type ComponentWithRecipeId = {
+ rid: string;
+ component: ComponentClass;
+};
+export declare type PathToComponentWithRecipeIdMap = {
+ [path: string]: ComponentWithRecipeId[];
+};
+declare type InternalRecipeModuleProps = {
+ instance: RecipeModule;
+};
+export declare type RecipeModuleProps = {
+ __internal?: InternalRecipeModuleProps;
+};
+export declare type ThemeProps = {
+ formFields: FormFieldsProps[];
+};
+export declare type FormFieldsProps = {
+ id: string;
+ label: string;
+ placeholder?: string;
+ validate?: (value: string) => Promise;
+ optional?: boolean;
+};
+export {};
diff --git a/lib/build/types.js b/lib/build/types.js
new file mode 100644
index 000000000..3918c74e4
--- /dev/null
+++ b/lib/build/types.js
@@ -0,0 +1 @@
+"use strict";
diff --git a/lib/build/utils.d.ts b/lib/build/utils.d.ts
new file mode 100644
index 000000000..2cc12ef4d
--- /dev/null
+++ b/lib/build/utils.d.ts
@@ -0,0 +1,6 @@
+export declare function getRecipeIdFromSearch(search: string): string | null;
+export declare function removePendingSlashFromPath(path: string): string;
+export declare function getNormalisedRouteWithoutWebsiteBasePath(path: string, basePath: string): string;
+export declare function normaliseURLDomainOrThrowError(input: string): string;
+export declare function normaliseURLPathOrThrowError(input: string): string;
+export declare function isTest(): boolean;
diff --git a/lib/build/utils.js b/lib/build/utils.js
new file mode 100644
index 000000000..0ceed8497
--- /dev/null
+++ b/lib/build/utils.js
@@ -0,0 +1,177 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.getRecipeIdFromSearch = getRecipeIdFromSearch;
+exports.removePendingSlashFromPath = removePendingSlashFromPath;
+exports.getNormalisedRouteWithoutWebsiteBasePath = getNormalisedRouteWithoutWebsiteBasePath;
+exports.normaliseURLDomainOrThrowError = normaliseURLDomainOrThrowError;
+exports.normaliseURLPathOrThrowError = normaliseURLPathOrThrowError;
+exports.isTest = isTest;
+
+var _constants = require("./constants");
+
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * getRecipeIdFromPath
+ * Input:
+ * Output: The "rid" query param if present, null otherwise.
+ */
+function getRecipeIdFromSearch(search) {
+ var urlParams = new URLSearchParams(search);
+ return urlParams.get(_constants.RECIPE_ID_QUERY_PARAM);
+}
+/*
+ * removePendingSlashFromPath
+ * Input: string path (compatible with url).
+ * Output path without pending "/" at the end.
+ */
+
+function removePendingSlashFromPath(path) {
+ // Remove pending "/""
+ while (path.length > 1 && path.endsWith("/")) {
+ path = path.slice(0, -1);
+ }
+
+ return path;
+}
+/*
+ * getNormalisedRouteWithoutWebsiteBasePath
+ * Input: string path
+ * Output path without the website base path.
+ */
+
+function getNormalisedRouteWithoutWebsiteBasePath(path, basePath) {
+ // If base path is present, remove it.
+ if (path.startsWith(basePath)) {
+ var newPath = path.slice(basePath.length);
+
+ if (newPath.length === 0) {
+ newPath = "/";
+ }
+
+ return newPath;
+ } // Otherwise, return url unchanged.
+
+ return path;
+}
+/*
+ * normaliseUrlOrThrowError
+ * Input: string url (or domain).
+ * Output: A url with appropriate protocol.
+ */
+
+function normaliseURLDomainOrThrowError(input) {
+ function isAnIpAddress(ipaddress) {
+ return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
+ ipaddress
+ );
+ }
+
+ input = input.trim().toLowerCase();
+
+ try {
+ if (!input.startsWith("http://") && !input.startsWith("https://") && !input.startsWith("supertokens://")) {
+ throw new Error("converting to proper URL");
+ }
+
+ var urlObj = new URL(input);
+
+ if (urlObj.protocol === "supertokens:") {
+ if (urlObj.hostname.startsWith("localhost") || isAnIpAddress(urlObj.hostname)) {
+ input = "http://" + urlObj.host;
+ } else {
+ input = "https://" + urlObj.host;
+ }
+ } else {
+ input = urlObj.protocol + "//" + urlObj.host;
+ }
+
+ return input;
+ } catch (err) {} // not a valid URL
+
+ if (input.indexOf(".") === 0) {
+ input = input.substr(1);
+ } // If the input contains a . it means they have given a domain name.
+ // So we try assuming that they have given a domain name
+
+ if (
+ (input.indexOf(".") !== -1 || input.startsWith("localhost")) &&
+ !input.startsWith("http://") &&
+ !input.startsWith("https://")
+ ) {
+ // The supertokens:// signifies to the recursive call that the call was made by us.
+ input = "supertokens://" + input; // at this point, it should be a valid URL. So we test that before doing a recursive call
+
+ try {
+ new URL(input);
+ return normaliseURLDomainOrThrowError(input);
+ } catch (err) {}
+ }
+
+ throw new Error("Please provide a valid domain name");
+}
+
+function normaliseURLPathOrThrowError(input) {
+ input = input.trim().toLowerCase();
+
+ try {
+ if (!input.startsWith("http://") && !input.startsWith("https://")) {
+ throw new Error("converting to proper URL");
+ }
+
+ var urlObj = new URL(input);
+ input = urlObj.pathname;
+
+ if (input.charAt(input.length - 1) === "/") {
+ return input.substr(0, input.length - 1);
+ }
+
+ return input;
+ } catch (err) {} // not a valid URL
+ // If the input contains a . it means they have given a domain name.
+ // So we try assuming that they have given a domain name + path
+
+ if (
+ (input.indexOf(".") !== -1 || input.startsWith("localhost")) &&
+ !input.startsWith("http://") &&
+ !input.startsWith("https://")
+ ) {
+ input = "http://" + input;
+ return normaliseURLPathOrThrowError(input);
+ }
+
+ if (input.charAt(0) !== "/") {
+ input = "/" + input;
+ } // at this point, we should be able to convert it into a fake URL and recursively call this function.
+
+ try {
+ // test that we can convert this to prevent an infinite loop
+ new URL("http://example.com" + input);
+ return normaliseURLPathOrThrowError("http://example.com" + input);
+ } catch (err) {
+ throw new Error("Please provide a valid URL path");
+ }
+}
+/*
+ * isTest
+ */
+
+function isTest() {
+ return process.env.TEST_MODE === "testing";
+}
diff --git a/lib/build/version.js b/lib/build/version.js
index 35e2ffd33..71e9cdf8c 100644
--- a/lib/build/version.js
+++ b/lib/build/version.js
@@ -1,5 +1,10 @@
"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.package_version = void 0;
+
/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
*
* This software is licensed under the Apache License, Version 2.0 (the
@@ -14,4 +19,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
* License for the specific language governing permissions and limitations
* under the License.
*/
-exports.package_version = "0.0.1";
+var package_version = "0.0.1";
+exports.package_version = package_version;
diff --git a/lib/ts/components/superTokensRoute.tsx b/lib/ts/components/superTokensRoute.tsx
new file mode 100644
index 000000000..260d19b31
--- /dev/null
+++ b/lib/ts/components/superTokensRoute.tsx
@@ -0,0 +1,79 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+
+import * as React from "react";
+import SuperTokens from "../superTokens";
+import { ComponentWithRecipeId, PathToComponentWithRecipeIdMap } from "../types";
+import { getRecipeIdFromSearch } from "../utils";
+
+/*
+* SuperTokensRouteWithRecipeId
+* Using react-router-dom, we can only match based on the route and not on the combination of path and query params.
+* having one route per component would lead to clashes when two components have the same route but different recipeId,
+* the first one would always take precedence.
+* Hence, the component rendered in the Route is an abstraction that decides which Feature to render based
+* on the rId.
+* See SuperTokensRouteWithRecipeId below.
+*/
+export function getSuperTokensRoutesForReactRouterDom (): JSX.Element[] {
+ try {
+ let pathsToComponentWithRecipeIdMap: PathToComponentWithRecipeIdMap = {};
+ SuperTokens.getRecipeList().map(recipe => {
+ const features = recipe.getFeatures();
+ return Object.keys(features).map(featurePath => {
+ const fullPath = `${SuperTokens.getAppInfo().websiteBasePath}${featurePath}`;
+
+ // If no components yet for this route, initialize empty array.
+ if(pathsToComponentWithRecipeIdMap[fullPath] === undefined) {
+ pathsToComponentWithRecipeIdMap[fullPath] = []
+ }
+
+ pathsToComponentWithRecipeIdMap[fullPath].push({
+ rid: recipe.getRecipeId(),
+ component: features[featurePath]
+ })
+
+ })
+ });
+ return Object.keys(pathsToComponentWithRecipeIdMap).map(path => SuperTokensRouteWithRecipeId(path, pathsToComponentWithRecipeIdMap[path]));
+ } catch (e) {
+ return [];
+ }
+
+
+}
+
+
+function SuperTokensRouteWithRecipeId(path: string, routeComponents: ComponentWithRecipeId[]): JSX.Element {
+ const Route = require('react-router-dom').Route;
+ const recipeId = getRecipeIdFromSearch(window.location.search);
+
+ // If recipeId provided, try to find a match.
+ if (recipeId !== null) {
+ for (let i = 0; i < routeComponents.length; i++) {
+ if (recipeId === routeComponents[i].rid) {
+ return ;
+ }
+ }
+ }
+
+ // Otherwise, If no recipe Id provided, or if no recipe id matches, return the first matching component.
+ return
+
+}
\ No newline at end of file
diff --git a/lib/ts/constants.ts b/lib/ts/constants.ts
new file mode 100644
index 000000000..05567dabc
--- /dev/null
+++ b/lib/ts/constants.ts
@@ -0,0 +1,22 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+export const RECIPE_ID_QUERY_PARAM = "rid";
+
+export const DEFAULT_API_BASE_PATH = "/auth";
+
+export const DEFAULT_WEBSITE_BASE_PATH = "/auth";
+
+export const ST_ROOT_CONTAINER = "supertokens-root";
diff --git a/lib/ts/index.ts b/lib/ts/index.ts
index b600af6f3..9e92aedbe 100644
--- a/lib/ts/index.ts
+++ b/lib/ts/index.ts
@@ -13,4 +13,35 @@
* under the License.
*/
-export default class SuperTokens {}
+/*
+ * Imports.
+ */
+import { SuperTokensConfig } from "./types";
+import SuperTokens from "./superTokens";
+
+/*
+ * API Wrapper exposed to user.
+ */
+
+export default class SuperTokensAPIWrapper {
+ static init(config: SuperTokensConfig) {
+ SuperTokens.init(config);
+ }
+
+ static canHandleRoute(): boolean {
+ return SuperTokens.canHandleRoute();
+ }
+
+ static getRoutingComponent() {
+ return SuperTokens.getRoutingComponent();
+ }
+
+ static getSuperTokensRoutesForReactRouterDom() {
+ return SuperTokens.getSuperTokensRoutesForReactRouterDom();
+ }
+}
+
+export const canHandleRoute = SuperTokensAPIWrapper.canHandleRoute;
+export const init = SuperTokensAPIWrapper.init;
+export const getRoutingComponent = SuperTokensAPIWrapper.getRoutingComponent;
+export const getSuperTokensRoutesForReactRouterDom = SuperTokens.getSuperTokensRoutesForReactRouterDom;
diff --git a/lib/ts/recipe/emailpassword/SignInAndUp.tsx b/lib/ts/recipe/emailpassword/SignInAndUp.tsx
new file mode 100644
index 000000000..bc28de98f
--- /dev/null
+++ b/lib/ts/recipe/emailpassword/SignInAndUp.tsx
@@ -0,0 +1,70 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+import * as React from "react";
+import { ST_ROOT_CONTAINER } from "../../constants";
+import { RecipeModuleProps } from '../../types';
+import EmailPassword from ".";
+import {SignInAndUpTheme} from '.';
+import root from 'react-shadow/emotion';
+
+/*
+ * Component.
+ */
+class SignInAndUp extends React.Component {
+ getRecipeInstanceOrThrow = () => {
+ let instance;
+ if (this.props.__internal !== undefined && this.props.__internal.instance !== undefined) {
+ instance = this.props.__internal.instance;
+ } else {
+ instance = EmailPassword.getInstanceIfDefined();
+ }
+ return instance;
+ }
+
+ render () {
+ return (
+
+ {
+ return new Promise(resolve => resolve(true));
+ },
+ },{
+ id: 'password',
+ label: 'Password',
+ placeholder: 'Enter your password',
+ validate: (password: string) => {
+ return new Promise(resolve => resolve(true));
+ }
+ }
+ ]}
+
+ />
+
+ );
+ }
+
+}
+
+export default SignInAndUp;
\ No newline at end of file
diff --git a/lib/ts/recipe/emailpassword/SignInAndUpTheme.tsx b/lib/ts/recipe/emailpassword/SignInAndUpTheme.tsx
new file mode 100644
index 000000000..b33316a1b
--- /dev/null
+++ b/lib/ts/recipe/emailpassword/SignInAndUpTheme.tsx
@@ -0,0 +1,95 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+import * as React from "react";
+import {defaultStyles, palette} from '../../styles/styles';
+import { ThemeProps } from "../../types";
+
+
+/** @jsx jsx */
+import { jsx } from '@emotion/core';
+
+/*
+ * Component.
+ */
+class SignInAndUpTheme extends React.Component {
+ render () {
+ return (
+
+
+
+
+
Sign In
+
+
Not registered yet?
+
Sign up
+
+
+
+
+
+
+
+
+
Forgot password?
+
+
+
+
);
+ }
+
+}
+const styles = {
+ header: {
+ height: '141px'
+ },
+ headerTitle: {
+ paddingTop: "49px",
+ fontSize: palette.fonts.size[1],
+ lineHeight: "40px",
+ letterSpacing: "0.28px",
+ fontWeight: 700,
+ fontFamily: palette.fonts.primary,
+ color: palette.colors.primary,
+ },
+ headerSubtitle: {
+ fontSize: palette.fonts.size[0],
+ fontWeight: 400,
+ color: palette.colors.secondary,
+ fontFamily: palette.fonts.primary
+ },
+ signUpLink: {
+ color: 'blue'
+ }
+};
+
+
+export default SignInAndUpTheme;
\ No newline at end of file
diff --git a/lib/ts/recipe/emailpassword/index.ts b/lib/ts/recipe/emailpassword/index.ts
new file mode 100644
index 000000000..b59690036
--- /dev/null
+++ b/lib/ts/recipe/emailpassword/index.ts
@@ -0,0 +1,55 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+import RecipeModule from "../recipeModule";
+import { EmailPasswordConfig } from "../../types";
+import SignInAndUp from "./SignInAndUp";
+import SignInAndUpTheme from "./SignInAndUpTheme";
+/*
+ * Class.
+ */
+export default class EmailPassword extends RecipeModule {
+ static instance?: EmailPassword;
+
+ constructor(config: EmailPasswordConfig) {
+ super({
+ ...config,
+ recipeId: "email-password",
+ features: {
+ "/": SignInAndUp
+ }
+ });
+ }
+
+ static init(config: EmailPasswordConfig): () => RecipeModule {
+ return (): RecipeModule => {
+ EmailPassword.instance = new EmailPassword(config);
+ return EmailPassword.instance;
+ };
+ }
+
+ static getInstanceIfDefined(): EmailPassword {
+ if (EmailPassword.instance === undefined) {
+ throw Error(`No instance of ${EmailPassword.constructor.name} found. Make sure to call the "init" method.`); // TODO Add relevant doc.
+ }
+
+ return EmailPassword.instance;
+ }
+}
+
+export { SignInAndUp, SignInAndUpTheme };
diff --git a/lib/ts/recipe/recipeModule.ts b/lib/ts/recipe/recipeModule.ts
new file mode 100644
index 000000000..f4700cc8c
--- /dev/null
+++ b/lib/ts/recipe/recipeModule.ts
@@ -0,0 +1,55 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+import { RouteToFeatureComponentMap, RecipeModuleConfig } from "../types";
+import { ComponentClass } from "react";
+import SuperTokensUrl from "../superTokensUrl";
+
+/*
+ * Class.
+ */
+export default abstract class RecipeModule {
+ private features: RouteToFeatureComponentMap;
+ private recipeId: string;
+
+ constructor(config: RecipeModuleConfig) {
+ this.recipeId = config.recipeId;
+ this.features = config.features;
+ }
+
+ getRecipeId = (): string => {
+ return this.recipeId;
+ };
+
+ getFeatures = (): RouteToFeatureComponentMap => {
+ return this.features;
+ };
+
+ canHandleRoute = (url: SuperTokensUrl): boolean => {
+ return this.getRoutingComponent(url) !== undefined;
+ };
+
+ getRoutingComponent = (url: SuperTokensUrl): ComponentClass | undefined => {
+ // If rId from URL exists and doesn't match, or if route path doesn't start with return undefined.
+ if (url.recipeId !== null && url.recipeId !== this.recipeId) {
+ return undefined;
+ }
+
+ return this.features[url.normalisedPathnameWithoutWebsiteBasePath];
+ };
+}
diff --git a/lib/ts/styles/styles.ts b/lib/ts/styles/styles.ts
new file mode 100644
index 000000000..c3ac0c8bd
--- /dev/null
+++ b/lib/ts/styles/styles.ts
@@ -0,0 +1,60 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Palette
+ */
+export const palette = {
+ colors: {
+ background: "white",
+ primary: "#222222",
+ secondary: "#656565"
+ },
+
+ fonts: {
+ size: [16, 28],
+ primary: "Rubik"
+ }
+};
+
+/*
+ * Default styles.
+ */
+
+export const defaultStyles = {
+ container: {
+ maxWidth: "524px",
+ width: "60vw",
+ margin: "0 auto",
+ minWidth: "420px",
+ height: "498px",
+ borderRadius: "8px",
+ boxShadow: "1px 1px 10px rgba(0,0,0,0.16)",
+ backgroundColor: palette.colors.background,
+ "@media (max-width: 440px)": {
+ minWidth: "320px"
+ }
+ },
+ row: {
+ margin: "0 auto",
+ width: "60%"
+ },
+ divider: {
+ margin: "1em -1em",
+ borderBottom: "0.3px solid #dddddd",
+ display: "flex",
+ alignItems: "center"
+ }
+};
diff --git a/lib/ts/superTokens.ts b/lib/ts/superTokens.ts
new file mode 100644
index 000000000..b1970bd9e
--- /dev/null
+++ b/lib/ts/superTokens.ts
@@ -0,0 +1,167 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports.
+ */
+import RecipeModule from "./recipe/recipeModule";
+import { DEFAULT_API_BASE_PATH, DEFAULT_WEBSITE_BASE_PATH } from "./constants";
+import { AppInfo, SuperTokensConfig } from "./types";
+import { ComponentClass } from "react";
+import SuperTokensUrl from "./superTokensUrl";
+import { isTest, normaliseURLPathOrThrowError, normaliseURLDomainOrThrowError } from "./utils";
+const { getSuperTokensRoutesForReactRouterDom } = require("./components/superTokensRoute");
+
+/*
+ * Class.
+ */
+
+export default class SuperTokens {
+ /*
+ * Static Attributes.
+ */
+ private static instance?: SuperTokens;
+
+ /*
+ * Instance Attributes.
+ */
+ private appInfo: AppInfo;
+ private recipeList: RecipeModule[] = [];
+
+ /*
+ * Constructor.
+ */
+ constructor(config: SuperTokensConfig) {
+ this.appInfo = {
+ appName: config.appInfo.appName,
+ apiDomain: normaliseURLDomainOrThrowError(config.appInfo.apiDomain),
+ websiteDomain: normaliseURLDomainOrThrowError(config.appInfo.websiteDomain),
+ apiBasePath: SuperTokens.getNormalisedURLPathOrDefault(DEFAULT_API_BASE_PATH, config.appInfo.apiBasePath),
+ websiteBasePath: SuperTokens.getNormalisedURLPathOrDefault(
+ DEFAULT_WEBSITE_BASE_PATH,
+ config.appInfo.websiteBasePath
+ ),
+ logoFullURL: config.appInfo.logoFullURL
+ };
+
+ if (config.recipeList === undefined) {
+ throw new Error("No recipeList provided to SuperTokens."); // TODO Add link to appropriate docs.
+ }
+
+ this.recipeList = config.recipeList.map(recipe => {
+ return recipe();
+ });
+ }
+
+ /*
+ * Static Methods.
+ */
+ static init(config: SuperTokensConfig): void {
+ if (SuperTokens.instance !== undefined) {
+ throw new Error("SuperTokens was already initialized");
+ }
+
+ SuperTokens.instance = new SuperTokens(config);
+ }
+
+ private static getInstanceIfDefined(): SuperTokens {
+ if (SuperTokens.instance === undefined) {
+ throw new Error("SuperTokens must be initialized before calling this method.");
+ }
+
+ return SuperTokens.instance;
+ }
+
+ static getAppInfo(): AppInfo {
+ return SuperTokens.getInstanceIfDefined().getAppInfo();
+ }
+
+ static canHandleRoute(): boolean {
+ return SuperTokens.getInstanceIfDefined().canHandleRoute();
+ }
+
+ static getRoutingComponent(): ComponentClass | undefined {
+ return SuperTokens.getInstanceIfDefined().getRoutingComponent();
+ }
+
+ static getRecipeList(): RecipeModule[] {
+ return SuperTokens.getInstanceIfDefined().getRecipeList();
+ }
+
+ static getNormalisedURLPathOrDefault(defaultPath: string, path?: string): string {
+ if (path !== undefined) {
+ return normaliseURLPathOrThrowError(path);
+ } else {
+ return defaultPath;
+ }
+ }
+
+ static getSuperTokensRoutesForReactRouterDom(): JSX.Element[] {
+ return getSuperTokensRoutesForReactRouterDom();
+ }
+
+ /*
+ * Instance Methods.
+ */
+ getAppInfo = (): AppInfo => {
+ return this.appInfo;
+ };
+
+ canHandleRoute = (): boolean => {
+ const url = new SuperTokensUrl();
+
+ // If pathname doesn't start with websiteBasePath, return false.
+ if (!url.matchesBasePath) {
+ return false;
+ }
+
+ return this.recipeList.some(recipe => recipe.canHandleRoute(url));
+ };
+
+ getRoutingComponent = (): ComponentClass | undefined => {
+ const url = new SuperTokensUrl();
+
+ // If pathname doesn't start with websiteBasePath, return false.
+ if (!url.matchesBasePath) {
+ return undefined;
+ }
+
+ let component: ComponentClass | undefined;
+ for (let i = 0; i < this.recipeList.length; i++) {
+ component = this.recipeList[i].getRoutingComponent(url);
+ if (component !== undefined) {
+ break;
+ }
+ }
+
+ return component;
+ };
+
+ getRecipeList = (): RecipeModule[] => {
+ return this.recipeList;
+ };
+
+ /*
+ * Tests methods.
+ */
+ static reset(): void {
+ if (!isTest()) {
+ return;
+ }
+
+ SuperTokens.instance = undefined;
+ return;
+ }
+}
diff --git a/lib/ts/superTokensUrl.ts b/lib/ts/superTokensUrl.ts
new file mode 100644
index 000000000..7db1514f8
--- /dev/null
+++ b/lib/ts/superTokensUrl.ts
@@ -0,0 +1,38 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+import SuperTokens from "./superTokens";
+import { getRecipeIdFromSearch, removePendingSlashFromPath, getNormalisedRouteWithoutWebsiteBasePath } from "./utils";
+
+/*
+ * Class.
+ */
+
+export default class SuperTokensUrl {
+ recipeId: string | null;
+ normalisedPathname: string;
+ matchesBasePath: boolean;
+ normalisedPathnameWithoutWebsiteBasePath: string;
+
+ constructor() {
+ this.recipeId = getRecipeIdFromSearch(window.location.search);
+ this.normalisedPathname = removePendingSlashFromPath(window.location.pathname);
+ this.matchesBasePath = this.normalisedPathname.startsWith(SuperTokens.getAppInfo().websiteBasePath);
+ this.normalisedPathnameWithoutWebsiteBasePath = getNormalisedRouteWithoutWebsiteBasePath(
+ this.normalisedPathname,
+ SuperTokens.getAppInfo().websiteBasePath
+ );
+ }
+}
diff --git a/lib/ts/types.ts b/lib/ts/types.ts
new file mode 100644
index 000000000..e87db1dc4
--- /dev/null
+++ b/lib/ts/types.ts
@@ -0,0 +1,152 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+import { ComponentClass } from "react";
+import RecipeModule from "./recipe/recipeModule";
+
+/*
+ * Config Types.
+ */
+
+export type SuperTokensConfig = {
+ /*
+ * Configurations for authentication.
+ */
+ appInfo: AppInfoUserInput;
+
+ /*
+ * List of recipes for authentication and session management.
+ */
+ recipeList: (() => RecipeModule)[];
+};
+
+type AppInfoBase = {
+ /*
+ * The name of your application.
+ */
+ appName: string;
+
+ /*
+ * The API that connects with your application.
+ */
+ apiDomain: string;
+
+ /*
+ * The domain on which your application runs.
+ */
+ websiteDomain: string;
+
+ /*
+ * (Optional) URL for your logo that will be displayed on the login form.
+ */
+ logoFullURL?: string;
+};
+
+export type AppInfoUserInput = AppInfoBase & {
+ /*
+ * The base path for SuperTokens middleware in your API.
+ * Default to `/auth`
+ */
+ apiBasePath?: string;
+
+ /*
+ * The base path for SuperTokens middleware in your front end application.
+ * Default to `/auth`
+ */
+ websiteBasePath?: string;
+};
+
+export type AppInfo = AppInfoBase & {
+ /*
+ * The base path for SuperTokens middleware in your API.
+ * Default to `/auth`
+ */
+ apiBasePath: string;
+
+ /*
+ * The base path for SuperTokens middleware in your front end application.
+ * Default to `/auth`
+ */
+ websiteBasePath: string;
+};
+
+export type EmailPasswordConfig = RecipeModuleConfig & {
+ /*
+ * Sign In and Sign Up feature.
+ */
+ signInAndUpFeature: any;
+
+ /*
+ * Reset password Using Token feature.
+ */
+ resetPasswordUsingTokenFeature: any;
+};
+
+/*
+ * Routing manipulation types.
+ */
+export type RouteToFeatureComponentMap = {
+ [route: string]: ComponentClass;
+};
+
+export type RecipeModuleConfig = {
+ /*
+ * Features that the module responds to.
+ */
+ features: RouteToFeatureComponentMap;
+
+ /*
+ * Unique Identifier of a module.
+ */
+ recipeId: string;
+};
+
+export type ComponentWithRecipeId = {
+ /*
+ * recipeId of the component.
+ */
+ rid: string;
+
+ /*
+ * Component.
+ */
+ component: ComponentClass;
+};
+
+export type PathToComponentWithRecipeIdMap = {
+ [path: string]: ComponentWithRecipeId[];
+};
+
+/*
+ * Props Types.
+ */
+export type RecipeModuleProps = {
+ __internal?: InternalRecipeModuleProps;
+};
+
+type InternalRecipeModuleProps = {
+ instance: RecipeModule;
+};
+
+export type ThemeProps = {
+ formFields: FormFieldsProps[];
+};
+
+export type FormFieldsProps = {
+ id: string;
+ label: string;
+ placeholder?: string;
+ validate?: (value: string) => Promise;
+ optional?: boolean;
+};
diff --git a/lib/ts/utils.ts b/lib/ts/utils.ts
new file mode 100644
index 000000000..bc03b9d3b
--- /dev/null
+++ b/lib/ts/utils.ts
@@ -0,0 +1,167 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { RECIPE_ID_QUERY_PARAM } from "./constants";
+
+/*
+ * getRecipeIdFromPath
+ * Input:
+ * Output: The "rid" query param if present, null otherwise.
+ */
+export function getRecipeIdFromSearch(search: string): string | null {
+ const urlParams = new URLSearchParams(search);
+ return urlParams.get(RECIPE_ID_QUERY_PARAM);
+}
+
+/*
+ * removePendingSlashFromPath
+ * Input: string path (compatible with url).
+ * Output path without pending "/" at the end.
+ */
+export function removePendingSlashFromPath(path: string): string {
+ // Remove pending "/""
+ while (path.length > 1 && path.endsWith("/")) {
+ path = path.slice(0, -1);
+ }
+
+ return path;
+}
+
+/*
+ * getNormalisedRouteWithoutWebsiteBasePath
+ * Input: string path
+ * Output path without the website base path.
+ */
+export function getNormalisedRouteWithoutWebsiteBasePath(path: string, basePath: string): string {
+ // If base path is present, remove it.
+ if (path.startsWith(basePath)) {
+ let newPath = path.slice(basePath.length);
+ if (newPath.length === 0) {
+ newPath = "/";
+ }
+ return newPath;
+ }
+
+ // Otherwise, return url unchanged.
+ return path;
+}
+
+/*
+ * normaliseUrlOrThrowError
+ * Input: string url (or domain).
+ * Output: A url with appropriate protocol.
+ */
+export function normaliseURLDomainOrThrowError(input: string): string {
+ function isAnIpAddress(ipaddress: string) {
+ return /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
+ ipaddress
+ );
+ }
+
+ input = input.trim().toLowerCase();
+
+ try {
+ if (!input.startsWith("http://") && !input.startsWith("https://") && !input.startsWith("supertokens://")) {
+ throw new Error("converting to proper URL");
+ }
+ let urlObj = new URL(input);
+ if (urlObj.protocol === "supertokens:") {
+ if (urlObj.hostname.startsWith("localhost") || isAnIpAddress(urlObj.hostname)) {
+ input = "http://" + urlObj.host;
+ } else {
+ input = "https://" + urlObj.host;
+ }
+ } else {
+ input = urlObj.protocol + "//" + urlObj.host;
+ }
+
+ return input;
+ } catch (err) {}
+ // not a valid URL
+
+ if (input.indexOf(".") === 0) {
+ input = input.substr(1);
+ }
+
+ // If the input contains a . it means they have given a domain name.
+ // So we try assuming that they have given a domain name
+ if (
+ (input.indexOf(".") !== -1 || input.startsWith("localhost")) &&
+ !input.startsWith("http://") &&
+ !input.startsWith("https://")
+ ) {
+ // The supertokens:// signifies to the recursive call that the call was made by us.
+ input = "supertokens://" + input;
+
+ // at this point, it should be a valid URL. So we test that before doing a recursive call
+ try {
+ new URL(input);
+ return normaliseURLDomainOrThrowError(input);
+ } catch (err) {}
+ }
+
+ throw new Error("Please provide a valid domain name");
+}
+
+export function normaliseURLPathOrThrowError(input: string): string {
+ input = input.trim().toLowerCase();
+
+ try {
+ if (!input.startsWith("http://") && !input.startsWith("https://")) {
+ throw new Error("converting to proper URL");
+ }
+ let urlObj = new URL(input);
+ input = urlObj.pathname;
+
+ if (input.charAt(input.length - 1) === "/") {
+ return input.substr(0, input.length - 1);
+ }
+
+ return input;
+ } catch (err) {}
+ // not a valid URL
+
+ // If the input contains a . it means they have given a domain name.
+ // So we try assuming that they have given a domain name + path
+ if (
+ (input.indexOf(".") !== -1 || input.startsWith("localhost")) &&
+ !input.startsWith("http://") &&
+ !input.startsWith("https://")
+ ) {
+ input = "http://" + input;
+ return normaliseURLPathOrThrowError(input);
+ }
+
+ if (input.charAt(0) !== "/") {
+ input = "/" + input;
+ }
+
+ // at this point, we should be able to convert it into a fake URL and recursively call this function.
+ try {
+ // test that we can convert this to prevent an infinite loop
+ new URL("http://example.com" + input);
+
+ return normaliseURLPathOrThrowError("http://example.com" + input);
+ } catch (err) {
+ throw new Error("Please provide a valid URL path");
+ }
+}
+
+/*
+ * isTest
+ */
+export function isTest(): boolean {
+ return process.env.TEST_MODE === "testing";
+}
diff --git a/lib/tsconfig.json b/lib/tsconfig.json
index 9ca26c386..906a1c689 100644
--- a/lib/tsconfig.json
+++ b/lib/tsconfig.json
@@ -8,7 +8,8 @@
"module": "commonJS",
"outDir": "build",
"moduleResolution": "node",
- "declaration": true
+ "declaration": true,
+ "jsx": "react"
},
"compileOnSave": true
}
\ No newline at end of file
diff --git a/package.json b/package.json
index 43ab8f267..c2d726d2f 100644
--- a/package.json
+++ b/package.json
@@ -2,18 +2,39 @@
"name": "supertokens-auth-react",
"version": "0.0.1",
"description": "ReactJS SDK that provides login functionality with SuperTokens.",
- "main": "index.js",
- "dependencies": {},
+ "main": "./lib/build/index.js",
"devDependencies": {
- "isomorphic-fetch": "2.2.1",
+ "@babel/cli": "^7.12.1",
+ "@babel/core": "^7.12.1",
+ "@babel/preset-env": "^7.12.1",
+ "@babel/preset-react": "^7.12.1",
+ "@babel/preset-typescript": "^7.12.1",
+ "@babel/register": "^7.12.1",
+ "@types/node": "^14.11.10",
+ "@types/react": "^16.9.53",
+ "@types/react-router-dom": "^5.1.6",
+ "babel-plugin-transform-class-properties": "^6.24.1",
+ "jsdom": "16.4.0",
+ "jsdom-global": "3.0.2",
"prettier": "1.18.2",
- "typescript": "3.5.2",
- "axios": "*"
+ "puppeteer": "^5.3.1",
+ "react": "^16.14.0",
+ "react-dom": "^16.0.0",
+ "react-router-dom": "^5.2.0",
+ "regenerator-runtime": "^0.13.7",
+ "typescript": "3.5.2"
+ },
+ "peerDependencies": {
+ "react": "^16.14.0",
+ "react-router-dom": "^5.2.0"
},
"scripts": {
- "test": "TEST_MODE=testing npx mocha --timeout 500000",
+ "test": "npm run build-pretty && TEST_MODE=testing mocha --require @babel/register --timeout 500000",
"build-check": "cd lib && npx tsc -p tsconfig.json --noEmit",
- "build": "cd lib && npx tsc -p tsconfig.json",
+ "tsc": "cd lib && npx tsc -p tsconfig.json",
+ "build": "npm run build-check && npm run compile",
+ "compile": "cd lib && babel ./ts/ --out-dir build/ --extensions '.ts,.tsx'",
+ "watch": "cd lib && babel --watch ./ts/ --out-dir ./build/ --extensions '.ts,.tsx'",
"pretty": "cd lib && npx prettier --write --config .prettierrc \"ts/**/*.ts\" \"build/**/*.js\" \"../test/**/*.js\"",
"build-pretty": "npm run build && npm run pretty",
"pretty-check": "cd lib && npx prettier --check --config .prettierrc \"ts/**/*.ts\" \"build/**/*.js\" \"../test/**/*.js\"",
@@ -34,11 +55,18 @@
"safari"
],
"contributors": [
- "rishabhpoddar"
+ "rishabhpoddar",
+ "NkxxkN"
],
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/supertokens/supertokens-auth-react/issues"
},
- "homepage": "https://github.com/supertokens/supertokens-auth-react#readme"
+ "homepage": "https://github.com/supertokens/supertokens-auth-react#readme",
+ "dependencies": {
+ "@emotion/core": "^10.0.35",
+ "emotion": "^10.0.27",
+ "emotion-server": "^10.0.27",
+ "react-shadow": "^18.4.2"
+ }
}
diff --git a/react-test-app/.gitignore b/react-test-app/.gitignore
new file mode 100644
index 000000000..4d29575de
--- /dev/null
+++ b/react-test-app/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/react-test-app/README.md b/react-test-app/README.md
new file mode 100644
index 000000000..af2802f9a
--- /dev/null
+++ b/react-test-app/README.md
@@ -0,0 +1,20 @@
+
+
+# Setup
+
+1. `npm install`
+2. `npm start`
+
+Errors:
+If you already have react-router-dom installed in the `supertokens-auth-react` package, both packages will clash, and you will see the following error:
+
+`× Error: Invariant failed: You should not use outside a `.
+
+Make sure to remove, or move the react-dom-router from `supertokens-auth-react` while you are using the test-app.
+
+`mv node_modules/react-router-dom node_modules/react-router-dom-test`
+
+
+And move it back if you are working on the main package:
+
+`mv node_modules/react-router-dom-test node_modules/react-router-dom`
\ No newline at end of file
diff --git a/react-test-app/package.json b/react-test-app/package.json
new file mode 100644
index 000000000..ebe1b725d
--- /dev/null
+++ b/react-test-app/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "react-test-app",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "react": "^16.14.0",
+ "react-dom": "^16.14.0",
+ "react-scripts": "3.4.3",
+ "supertokens-auth-react": "file:../",
+ "react-router-dom": "^5.2.0"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": "react-app"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/react-test-app/public/favicon.ico b/react-test-app/public/favicon.ico
new file mode 100644
index 000000000..bcd5dfd67
Binary files /dev/null and b/react-test-app/public/favicon.ico differ
diff --git a/react-test-app/public/index.html b/react-test-app/public/index.html
new file mode 100644
index 000000000..aa069f27c
--- /dev/null
+++ b/react-test-app/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ React App
+
+
+
+
+
+
+
diff --git a/react-test-app/public/logo192.png b/react-test-app/public/logo192.png
new file mode 100644
index 000000000..fc44b0a37
Binary files /dev/null and b/react-test-app/public/logo192.png differ
diff --git a/react-test-app/public/logo512.png b/react-test-app/public/logo512.png
new file mode 100644
index 000000000..a4e47a654
Binary files /dev/null and b/react-test-app/public/logo512.png differ
diff --git a/react-test-app/public/manifest.json b/react-test-app/public/manifest.json
new file mode 100644
index 000000000..080d6c77a
--- /dev/null
+++ b/react-test-app/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/react-test-app/src/App.css b/react-test-app/src/App.css
new file mode 100644
index 000000000..b2e879f22
--- /dev/null
+++ b/react-test-app/src/App.css
@@ -0,0 +1,41 @@
+.App {
+ text-align: center;
+ background-color: #fafafa;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+
+}
+
+h1 {
+ color: green
+}
+
+.App-header {
+ background-color: #fafafa;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: #000;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/react-test-app/src/App.js b/react-test-app/src/App.js
new file mode 100644
index 000000000..1b74eaa55
--- /dev/null
+++ b/react-test-app/src/App.js
@@ -0,0 +1,66 @@
+import React from 'react';
+import './App.css';
+import './App.css';
+import AppWithoutRouter from './AppWithoutRouter';
+import AppWithReactDomRouter from './AppWithReactDomRouter';
+
+/* SuperTokens imports */
+import SuperTokens from 'supertokens-auth-react';
+import EmailPassword from 'supertokens-auth-react/recipe/emailpassword';
+SuperTokens.init({
+ appInfo: {
+ appName: "SuperTokens",
+ websiteDomain: "localhost:3031",
+ apiDomain: "localhost:9090" // Not used yet.
+ },
+ recipeList: [
+ EmailPassword.init()
+ ]
+});
+
+
+/* App */
+function App() {
+ const router = getRouterFromLocationQueryParams();
+
+ if (router === 'no-router') {
+ return
+ }
+
+ return
+}
+
+function getRouterFromLocationQueryParams() {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get('router');
+}
+
+export default App;
+
+export function BaseComponent ({children}) {
+ return (
+
+ )
+}
+
+export function Home () {
+ return (
+ /Home
+ )
+}
+
+export function About () {
+ return (
+ /About
+
+ )
+}
+
+export function Contact () {
+ return (
+ /Contact
+
+ )
+}
\ No newline at end of file
diff --git a/react-test-app/src/AppWithReactDomRouter.js b/react-test-app/src/AppWithReactDomRouter.js
new file mode 100644
index 000000000..63b8054b6
--- /dev/null
+++ b/react-test-app/src/AppWithReactDomRouter.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import {
+ BrowserRouter as Router,
+ Switch,
+ Route,
+ Link
+} from "react-router-dom";
+import {getSuperTokensRoutesForReactRouterDom} from 'supertokens-auth-react';
+
+import {BaseComponent, Home, About, Contact} from './App';
+
+function AppWithReactDomRouter() {
+ return (
+
+
+
+ With Routing
+
+
+ {getSuperTokensRoutesForReactRouterDom()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function Nav () {
+ return (
+
+ )
+}
+
+export default AppWithReactDomRouter;
diff --git a/react-test-app/src/AppWithoutRouter.js b/react-test-app/src/AppWithoutRouter.js
new file mode 100644
index 000000000..2cfdae80f
--- /dev/null
+++ b/react-test-app/src/AppWithoutRouter.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import {BaseComponent, Home} from './App';
+import {canHandleRoute, getRoutingComponent} from 'supertokens-auth-react';
+
+function AppWithoutRouter() {
+
+ return (
+ <>
+
+ Without Routing
+
+ >
+ )
+}
+
+function Routing () {
+ if (canHandleRoute()) {
+ const SuperTokensComponent = getRoutingComponent();
+
+ return (
+
+ );
+ }
+
+ // Custom router...
+
+ return (
+
+ )
+}
+
+function Nav () {
+ return (
+
+ )
+}
+export default AppWithoutRouter;
diff --git a/react-test-app/src/index.css b/react-test-app/src/index.css
new file mode 100644
index 000000000..ec2585e8c
--- /dev/null
+++ b/react-test-app/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/react-test-app/src/index.js b/react-test-app/src/index.js
new file mode 100644
index 000000000..6832e7832
--- /dev/null
+++ b/react-test-app/src/index.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+import App from './App';
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root')
+);
diff --git a/recipe/emailpassword/index.d.ts b/recipe/emailpassword/index.d.ts
new file mode 100644
index 000000000..22cd992a8
--- /dev/null
+++ b/recipe/emailpassword/index.d.ts
@@ -0,0 +1,17 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+*/
+import EmailPassword from '../../lib/build/recipe/emailpassword';
+
+export default EmailPassword;
\ No newline at end of file
diff --git a/index.js b/recipe/emailpassword/index.js
similarity index 93%
rename from index.js
rename to recipe/emailpassword/index.js
index b7f377fc8..4ee0f0853 100644
--- a/index.js
+++ b/recipe/emailpassword/index.js
@@ -17,4 +17,4 @@ function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
exports.__esModule = true;
-__export(require("./lib/build"));
+__export(require("../../lib/build/recipe/emailpassword"));
\ No newline at end of file
diff --git a/index.ts b/recipe/emailpassword/index.ts
similarity index 88%
rename from index.ts
rename to recipe/emailpassword/index.ts
index 4ae635f84..0651320e5 100644
--- a/index.ts
+++ b/recipe/emailpassword/index.ts
@@ -12,6 +12,6 @@
* License for the specific language governing permissions and limitations
* under the License.
*/
-import SuperTokens from './lib/ts';
+import EmailPassword from '../../lib/ts/recipe/emailpassword';
-export default SuperTokens;
\ No newline at end of file
+export default EmailPassword;
\ No newline at end of file
diff --git a/test/end-to-end/routing.test.js b/test/end-to-end/routing.test.js
new file mode 100644
index 000000000..035745840
--- /dev/null
+++ b/test/end-to-end/routing.test.js
@@ -0,0 +1,133 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports
+ */
+
+/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */
+import regeneratorRuntime from "regenerator-runtime";
+import assert from "assert";
+import { spawn } from "child_process";
+import puppeteer from "puppeteer";
+import { ST_ROOT_CONTAINER } from "../../lib/build/constants";
+
+// Run the tests in a DOM environment.
+require("jsdom-global")();
+
+const TEST_APP_BASE_URL = "http://localhost:3031";
+/*
+ * Tests.
+ */
+describe("SuperTokens Routing in Test App", function() {
+ let testAppChildProcess, browser;
+ const SignInButtonQuerySelector = `document.querySelector('#${ST_ROOT_CONTAINER}').shadowRoot.querySelector('button').innerText`;
+
+ before(async function() {
+ testAppChildProcess = spawn("./testApp.sh", ["--start", "--no-build"]);
+
+ testAppChildProcess.stderr.on("data", function(data) {
+ console.log("stderr:" + data);
+ });
+
+ testAppChildProcess.stdout.on("data", function(data) {
+ if (data.toString().startsWith("LOGS:")) {
+ console.log(data.toString());
+ }
+ });
+
+ await new Promise(r => setTimeout(r, 3000));
+
+ browser = await puppeteer.launch({
+ args: ["--no-sandbox", "--disable-setuid-sandbox"],
+ headless: true
+ });
+ });
+
+ after(async function() {
+ await browser.close();
+ testAppChildProcess.kill();
+ spawn("./testApp.sh", ["--stop"]);
+ });
+
+ describe("using react-router-dom", function() {
+ it("/about should not load any SuperTokens components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/about`);
+ const superTokensComponent = await page.$(`.${ST_ROOT_CONTAINER}`);
+ assert.strictEqual(superTokensComponent, null);
+ });
+
+ it("/auth should load SignInUp components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/auth`, { waitUntil: "domcontentloaded" });
+ const signInButton = await page.evaluateHandle(SignInButtonQuerySelector);
+ assert.notStrictEqual(signInButton, null);
+ assert.strictEqual(signInButton._remoteObject.value, "Sign In");
+ });
+
+ it("/auth?rid=email-password should load SignInUp components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/auth?rid=email-password`, { waitUntil: "domcontentloaded" });
+ const signInButton = await page.evaluateHandle(SignInButtonQuerySelector);
+ assert.notStrictEqual(signInButton, null);
+ assert.strictEqual(signInButton._remoteObject.value, "Sign In");
+ });
+
+ it("/auth?rid=unknown-rid should load first components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/auth?rid=unknown`);
+ const signInButton = await page.evaluateHandle(SignInButtonQuerySelector);
+ assert.notStrictEqual(signInButton, null);
+ assert.strictEqual(signInButton._remoteObject.value, "Sign In");
+ });
+ });
+
+ describe("without react-router-dom", function() {
+ it("/about should not load any SuperTokens components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/about?router=no-router`, { waitUntil: "domcontentloaded" });
+ const superTokensComponent = await page.$(`.${ST_ROOT_CONTAINER}`);
+ assert.strictEqual(superTokensComponent, null);
+ });
+
+ it("/auth should load SignInUp components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/auth?router=no-router`, { waitUntil: "domcontentloaded" });
+ const signInButton = await page.evaluateHandle(SignInButtonQuerySelector);
+ assert.notStrictEqual(signInButton, null);
+ assert.strictEqual(signInButton._remoteObject.value, "Sign In");
+ });
+
+ it("/auth?rid=email-password should load SignInUp components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/auth?router=no-router&rid=email-password`, {
+ waitUntil: "domcontentloaded"
+ });
+ const signInButton = await page.evaluateHandle(SignInButtonQuerySelector);
+ assert.notStrictEqual(signInButton, null);
+ assert.strictEqual(signInButton._remoteObject.value, "Sign In");
+ });
+
+ it("/auth?rid=unknown-rid should not load any SuperTokens components.", async function() {
+ const page = await browser.newPage();
+ await page.goto(`${TEST_APP_BASE_URL}/auth?router=no-router&rid=unknown`, {
+ waitUntil: "domcontentloaded"
+ });
+ const superTokensComponent = await page.$(`#${ST_ROOT_CONTAINER}`);
+ assert.strictEqual(superTokensComponent, null);
+ });
+ });
+});
diff --git a/test/helpers.js b/test/helpers.js
new file mode 100644
index 000000000..7f7fff9a2
--- /dev/null
+++ b/test/helpers.js
@@ -0,0 +1,30 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Helpers.
+ */
+
+export function mockWindowLocation(url) {
+ try {
+ const location = new URL(url);
+ global.window = Object.create(window);
+ Object.defineProperty(window, "location", {
+ value: location
+ });
+ } catch (e) {
+ throw Error(`Failed to mock window location object with ${url}`, e);
+ }
+}
diff --git a/test/unit/index.test.js b/test/unit/index.test.js
new file mode 100644
index 000000000..06ce8335e
--- /dev/null
+++ b/test/unit/index.test.js
@@ -0,0 +1,191 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Imports
+ */
+
+/* https://github.com/babel/babel/issues/9849#issuecomment-487040428 */
+import regeneratorRuntime from "regenerator-runtime";
+import SuperTokens from "../../lib/build/superTokens";
+import EmailPassword, { SignInAndUp } from "../../lib/build/recipe/emailpassword";
+import { DEFAULT_WEBSITE_BASE_PATH, DEFAULT_API_BASE_PATH } from "../../lib/build/constants";
+import assert from "assert";
+import { mockWindowLocation } from "../helpers";
+
+// Run the tests in a DOM environment.
+require("jsdom-global")();
+
+/*
+ * Consts.
+ */
+const defaultConfigs = {
+ appInfo: {
+ appName: "SuperTokens",
+ websiteDomain: "supertokens.io",
+ apiDomain: "api.supertokens.io"
+ },
+ recipeList: []
+};
+
+/*
+ * Tests.
+ */
+describe("SuperTokens", function() {
+ afterEach(async function() {
+ SuperTokens.reset();
+ });
+
+ it("Initializing SuperTokens config with default values", async function() {
+ SuperTokens.init(defaultConfigs);
+ assert.strictEqual(SuperTokens.getAppInfo().appName, defaultConfigs.appInfo.appName);
+ assert.strictEqual(SuperTokens.getAppInfo().websiteDomain, `https://${defaultConfigs.appInfo.websiteDomain}`);
+ assert.strictEqual(SuperTokens.getAppInfo().apiDomain, `https://${defaultConfigs.appInfo.apiDomain}`);
+ assert.strictEqual(SuperTokens.getAppInfo().apiBasePath, DEFAULT_API_BASE_PATH);
+ assert.strictEqual(SuperTokens.getAppInfo().websiteBasePath, DEFAULT_WEBSITE_BASE_PATH);
+ assert.strictEqual(SuperTokens.getAppInfo().logoFullURL, undefined);
+ });
+
+ it("Initializing SuperTokens twice should throw", async function() {
+ SuperTokens.init(defaultConfigs);
+ assert.throws(
+ () => {
+ SuperTokens.init(defaultConfigs);
+ },
+ Error,
+ "SuperTokens was already initialized"
+ );
+ });
+
+ it("Initializing SuperTokens with corrupted URL should throw", async function() {
+ assert.throws(
+ () => {
+ SuperTokens.init({
+ ...defaultConfigs,
+ appInfo: {
+ ...defaultConfigs.appInfo,
+ apiDomain: ":"
+ }
+ });
+ },
+ Error,
+ "There was an error parsing the url you provided: (:). Please make sure it is correct."
+ );
+ assert.throws(
+ () => {
+ SuperTokens.init({
+ ...defaultConfigs,
+ appInfo: {
+ ...defaultConfigs.appInfo,
+ websiteDomain: "http:://malformed.url"
+ }
+ });
+ },
+ Error,
+ "There was an error parsing the url you provided: (http:://malformed.url). Please make sure it is correct."
+ );
+ });
+
+ it("Initializing SuperTokens with logofullURL", async function() {
+ const logoFullURL = "https://my.beautiful.logo.com/logo.png";
+ SuperTokens.init({
+ ...defaultConfigs,
+ appInfo: {
+ logoFullURL,
+ ...defaultConfigs.appInfo
+ }
+ });
+ assert.strictEqual(SuperTokens.getAppInfo().logoFullURL, logoFullURL);
+ });
+
+ it("Initializing SuperTokens with localhost and unsecure protocol", async function() {
+ const websiteDomain = "localhost:4000";
+ const apiDomain = "http://api.supertokens.io";
+ SuperTokens.init({
+ ...defaultConfigs,
+ appInfo: {
+ ...defaultConfigs.appInfo,
+ websiteDomain,
+ apiDomain
+ }
+ });
+ assert.strictEqual(SuperTokens.getAppInfo().websiteDomain, `http://${websiteDomain}`);
+ assert.strictEqual(SuperTokens.getAppInfo().apiDomain, apiDomain);
+ });
+
+ it("Initializing SuperTokens with EmailPassword module", async function() {
+ SuperTokens.init({
+ ...defaultConfigs,
+ recipeList: [EmailPassword.init()]
+ });
+ assert.strictEqual(SuperTokens.getRecipeList().length, 1);
+ });
+
+ it("SuperTokens canHandleRoute should work approriately", async function() {
+ SuperTokens.init({
+ ...defaultConfigs,
+ recipeList: [EmailPassword.init()]
+ });
+
+ const randomWebsitePath = SuperTokens.getAppInfo().websiteDomain;
+
+ mockWindowLocation(`${randomWebsitePath}/blog/`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), false);
+ mockWindowLocation(`${randomWebsitePath}/blog/.`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), false);
+ mockWindowLocation(`${randomWebsitePath}/blog/auth`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), false);
+ mockWindowLocation(`${randomWebsitePath}/auth/404`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), false);
+ mockWindowLocation(`${randomWebsitePath}/auth`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), true);
+ mockWindowLocation(`${randomWebsitePath}/auth/`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), true);
+ mockWindowLocation(`${randomWebsitePath}/auth/.`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), true);
+ mockWindowLocation(`${randomWebsitePath}/auth?rid=email-password`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), true);
+ mockWindowLocation(`${randomWebsitePath}/auth?rid=unknown-id`);
+ assert.strictEqual(SuperTokens.canHandleRoute(), false);
+ });
+
+ it("SuperTokens getRoutingComponent should work approriately", async function() {
+ SuperTokens.init({
+ ...defaultConfigs,
+ recipeList: [EmailPassword.init()]
+ });
+
+ const randomWebsitePath = SuperTokens.getAppInfo().websiteDomain;
+
+ mockWindowLocation(`${randomWebsitePath}/blog/`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), undefined);
+ mockWindowLocation(`${randomWebsitePath}/blog/.`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), undefined);
+ mockWindowLocation(`${randomWebsitePath}/blog/auth`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), undefined);
+ mockWindowLocation(`${randomWebsitePath}/auth/404`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), undefined);
+ mockWindowLocation(`${randomWebsitePath}/auth`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), SignInAndUp);
+ mockWindowLocation(`${randomWebsitePath}/auth/`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), SignInAndUp);
+ mockWindowLocation(`${randomWebsitePath}/auth/.`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), SignInAndUp);
+ mockWindowLocation(`${randomWebsitePath}/auth?rid=email-password`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), SignInAndUp);
+ mockWindowLocation(`${randomWebsitePath}/auth?rid=unknown-id`);
+ assert.strictEqual(SuperTokens.getRoutingComponent(), undefined);
+ });
+});
diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js
new file mode 100644
index 000000000..2c051505c
--- /dev/null
+++ b/test/unit/utils.test.js
@@ -0,0 +1,144 @@
+/* Copyright (c) 2020, VRAI Labs and/or its affiliates. All rights reserved.
+ *
+ * This software is licensed under the Apache License, Version 2.0 (the
+ * "License") as published by the Apache Software Foundation.
+ *
+ * You may not use this file except in compliance with the License. You may
+ * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+ normaliseURLPathOrThrowError,
+ normaliseURLDomainOrThrowError,
+ getNormalisedRouteWithoutWebsiteBasePath,
+ removePendingSlashFromPath,
+ getRecipeIdFromSearch
+} from "../../lib/build/utils";
+
+const assert = require("assert");
+import { mockWindowLocation } from "../helpers";
+
+describe("Config tests", function() {
+ beforeEach(async function() {
+ global.document = {};
+ mockWindowLocation("http://localhost.org");
+ });
+
+ it("testing URL path normalisation", async function() {
+ assert(normaliseURLPathOrThrowError("http://api.example.com") === "");
+ assert(normaliseURLPathOrThrowError("https://api.example.com") === "");
+ assert(normaliseURLPathOrThrowError("http://api.example.com?hello=1") === "");
+ assert(normaliseURLPathOrThrowError("http://api.example.com/hello") === "/hello");
+ assert(normaliseURLPathOrThrowError("http://api.example.com/") === "");
+ assert(normaliseURLPathOrThrowError("http://api.example.com:8080") === "");
+ assert(normaliseURLPathOrThrowError("http://api.example.com#random2") === "");
+ assert(normaliseURLPathOrThrowError("api.example.com/") === "");
+ assert(normaliseURLPathOrThrowError("api.example.com#random") === "");
+ assert(normaliseURLPathOrThrowError(".example.com") === "");
+ assert(normaliseURLPathOrThrowError("api.example.com/?hello=1&bye=2") === "");
+ assert(normaliseURLPathOrThrowError(window.location.hostname) === "");
+
+ assert(normaliseURLPathOrThrowError("http://api.example.com/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("http://1.2.3.4/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("1.2.3.4/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("https://api.example.com/one/two/") === "/one/two");
+ assert(normaliseURLPathOrThrowError("http://api.example.com/one/two?hello=1") === "/one/two");
+ assert(normaliseURLPathOrThrowError("http://api.example.com/hello/") === "/hello");
+ assert(normaliseURLPathOrThrowError("http://api.example.com/one/two/") === "/one/two");
+ assert(normaliseURLPathOrThrowError("http://api.example.com:8080/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("http://api.example.com/one/two#random2") === "/one/two");
+ assert(normaliseURLPathOrThrowError("api.example.com/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("api.example.com/one/two/#random") === "/one/two");
+ assert(normaliseURLPathOrThrowError(".example.com/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("api.example.com/one/two?hello=1&bye=2") === "/one/two");
+ assert(normaliseURLPathOrThrowError(window.location.hostname + "/one/two") === "/one/two");
+
+ assert(normaliseURLPathOrThrowError("/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("one/two/") === "/one/two");
+ assert(normaliseURLPathOrThrowError("/one") === "/one");
+ assert(normaliseURLPathOrThrowError("one") === "/one");
+ assert(normaliseURLPathOrThrowError("one/") === "/one");
+ assert(normaliseURLPathOrThrowError("/one/two/") === "/one/two");
+ assert(normaliseURLPathOrThrowError("/one/two?hello=1") === "/one/two");
+ assert(normaliseURLPathOrThrowError("one/two?hello=1") === "/one/two");
+ assert(normaliseURLPathOrThrowError("/one/two/#random") === "/one/two");
+ assert(normaliseURLPathOrThrowError("one/two#random") === "/one/two");
+
+ assert(normaliseURLPathOrThrowError("localhost:4000/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("127.0.0.1:4000/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("127.0.0.1/one/two") === "/one/two");
+ assert(normaliseURLPathOrThrowError("https://127.0.0.1:80/one/two") === "/one/two");
+ });
+
+ it("testing URL domain normalisation", async function() {
+ assert(normaliseURLDomainOrThrowError("http://api.example.com") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("https://api.example.com") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError("http://api.example.com?hello=1") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("http://api.example.com/hello") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("http://api.example.com/") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("http://api.example.com:8080") === "http://api.example.com:8080");
+ assert(normaliseURLDomainOrThrowError("http://api.example.com#random2") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("api.example.com/") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError("api.example.com") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError("api.example.com#random") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError(".example.com") === "https://example.com");
+ assert(normaliseURLDomainOrThrowError("api.example.com/?hello=1&bye=2") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError(window.location.hostname) === "http://localhost.org");
+ assert(normaliseURLDomainOrThrowError("localhost") === "http://localhost");
+ assert(normaliseURLDomainOrThrowError("https://localhost") === "https://localhost");
+
+ assert(normaliseURLDomainOrThrowError("http://api.example.com/one/two") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("http://1.2.3.4/one/two") === "http://1.2.3.4");
+ assert(normaliseURLDomainOrThrowError("https://1.2.3.4/one/two") === "https://1.2.3.4");
+ assert(normaliseURLDomainOrThrowError("1.2.3.4/one/two") === "http://1.2.3.4");
+ assert(normaliseURLDomainOrThrowError("https://api.example.com/one/two/") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError("http://api.example.com/one/two?hello=1") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("http://api.example.com/one/two#random2") === "http://api.example.com");
+ assert(normaliseURLDomainOrThrowError("api.example.com/one/two") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError("api.example.com/one/two/#random") === "https://api.example.com");
+ assert(normaliseURLDomainOrThrowError(".example.com/one/two") === "https://example.com");
+ assert(normaliseURLDomainOrThrowError(window.location.hostname + "/one/two") === "http://localhost.org");
+ assert(normaliseURLDomainOrThrowError("localhost:4000") === "http://localhost:4000");
+ assert(normaliseURLDomainOrThrowError("127.0.0.1:4000") === "http://127.0.0.1:4000");
+ assert(normaliseURLDomainOrThrowError("127.0.0.1") === "http://127.0.0.1");
+ assert(normaliseURLDomainOrThrowError("https://127.0.0.1:80/") === "https://127.0.0.1:80");
+
+ try {
+ normaliseURLDomainOrThrowError("/one/two");
+ assert(false);
+ } catch (err) {
+ assert(err.message === "Please provide a valid domain name");
+ }
+ });
+
+ it("testing removing website base path from normalised path", async function() {
+ assert.strictEqual(getNormalisedRouteWithoutWebsiteBasePath("/auth/login", "/auth"), "/login");
+ assert.strictEqual(getNormalisedRouteWithoutWebsiteBasePath("/auth", "/auth"), "/");
+ assert.strictEqual(getNormalisedRouteWithoutWebsiteBasePath("/auth/", "/auth"), "/");
+ assert.strictEqual(getNormalisedRouteWithoutWebsiteBasePath("/auth/", "/customBasePath"), "/auth/");
+ assert.strictEqual(getNormalisedRouteWithoutWebsiteBasePath("/auth/login", "/customBasePath"), "/auth/login");
+ });
+
+ it("testing removing pending slashed from normalised path", async function() {
+ assert.strictEqual(removePendingSlashFromPath(""), "");
+ assert.strictEqual(removePendingSlashFromPath("/"), "/");
+ assert.strictEqual(removePendingSlashFromPath("/auth/login"), "/auth/login");
+ assert.strictEqual(removePendingSlashFromPath("/auth/login/"), "/auth/login");
+ assert.strictEqual(removePendingSlashFromPath("/auth/login//"), "/auth/login");
+ });
+
+ it("get recipe Id from URL search", async function() {
+ assert.strictEqual(getRecipeIdFromSearch(""), null);
+ assert.strictEqual(getRecipeIdFromSearch("?rid=test"), "test");
+ assert.strictEqual(getRecipeIdFromSearch("?rid=2"), "2");
+ assert.strictEqual(getRecipeIdFromSearch("?gid=blue&rid=green&foo=bar"), "green");
+ assert.strictEqual(getRecipeIdFromSearch("?rId=blue&rid=green"), "green");
+ assert.strictEqual(getRecipeIdFromSearch("?rId=blue&foo=bar"), null);
+ });
+});
diff --git a/testApp.sh b/testApp.sh
new file mode 100755
index 000000000..d57dc9fa8
--- /dev/null
+++ b/testApp.sh
@@ -0,0 +1,33 @@
+
+function startTestApp () {
+ (
+ # go to test app.
+ cd react-test-app/
+ # Run static react app on PORT 3031.
+ BROWSER=none PORT=3031 npm run start
+ )
+}
+
+
+
+
+# Start test app.
+if [[ $1 == "--start" ]]; then
+ # Build if --no-build is not present.
+ if [[ $2 != "--no-build" ]]; then
+ npm run pretty
+ npm run build
+ fi
+ echo "LOGS: Starting test example app"
+ # Prevent clashes on react-router-dom, and react.
+ mv node_modules/react-router-dom node_modules/react-router-dom-tmp
+ mv node_modules/react node_modules/react-tmp
+ startTestApp
+fi
+
+# Add back react-router-dom on stop.
+if [[ $1 == "--stop" ]]; then
+ echo "LOGS: Adding back node_modules/react-router-dom"
+ mv node_modules/react-router-dom-tmp node_modules/react-router-dom
+ mv node_modules/react-tmp node_modules/react
+fi
\ No newline at end of file