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
+
+
+ +
+ +
+ { + this.props.formFields.map(field => { + return ( +
+ + +
+ ) + }) + } +
+ + +
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 ( +
+ {children} +
+ ) +} + +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 ( +
+ +
+ ); +} + +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 ( + <> +