@mcaptcha/react-glue implementation

This commit is contained in:
realaravinth
2021-12-11 10:49:21 +05:30
parent 6db48ebec8
commit ec7bd379a3
31 changed files with 10521 additions and 974 deletions

View File

@@ -1,7 +1,6 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",

View File

@@ -6,10 +6,12 @@
"private": true,
"workspaces": {
"packages": [
"packages/vanilla"
"packages/vanilla",
"packages/react-glue"
],
"nohoist": [
"packages/*"
"packages/vanilla",
"packages/react-glue"
]
},
"license": "(MIT OR Apache-2.0)",

View File

@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@@ -0,0 +1,5 @@
build/
dist/
node_modules/
.snapshots/
*.min.js

View File

@@ -0,0 +1,47 @@
{
"parser": "@typescript-eslint/parser",
"extends": [
"standard",
"standard-react",
"plugin:prettier/recommended",
"prettier/standard",
"prettier/react",
"plugin:@typescript-eslint/eslint-recommended"
],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 2020,
"ecmaFeatures": {
"legacyDecorators": true,
"jsx": true
}
},
"settings": {
"react": {
"version": "16"
}
},
"rules": {
"@typescript-eslint/member-delimiter-style": [
"error",
{
"multiline": {
"delimiter": 'none',
"requireLast": true
},
"singleline": {
"delimiter": 'none',
"requireLast": false
}
}
],
"space-before-function-paren": 0,
"react/prop-types": 0,
"react/jsx-handler-names": 0,
"react/jsx-fragments": 0,
"react/no-unused-prop-types": 0,
"import/export": 0
}
}

View File

@@ -0,0 +1,10 @@
{
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"tabWidth": 2,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"trailingComma": "none"
}

View File

@@ -0,0 +1,4 @@
language: node_js
node_js:
- 12
- 10

View File

@@ -0,0 +1,30 @@
# react-glue
> Made with create-react-library
[![NPM](https://img.shields.io/npm/v/@mcaptcha-react-glue.svg)](https://www.npmjs.com/package/@mcaptcha/react-glue) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
## Install
```bash
npm install --save react-glue
```
## Usage
```tsx
import React, { Component } from 'react'
import MyComponent from 'react-glue'
import 'react-glue/dist/index.css'
class Example extends Component {
render() {
return <MyComponent siteKey="randomSiteKeyAsDisplayedInAdminPanel" />
}
}
```
## License
MIT © [Aravinth Manivannan &lt;realaravinth@batsense.net&gt;](https://github.com/Aravinth Manivannan &lt;realaravinth@batsense.net&gt;)

View File

@@ -0,0 +1,5 @@
This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
It is linked to the react-glue package in the parent directory for development purposes.
You can run `yarn install` and then `yarn start` to test your package.

View File

@@ -0,0 +1,44 @@
{
"name": "react-glue-example",
"homepage": ".",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
"build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
"test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
"eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
},
"dependencies": {
"@testing-library/jest-dom": "link:../node_modules/@testing-library/jest-dom",
"@testing-library/react": "link:../node_modules/@testing-library/react",
"@testing-library/user-event": "link:../node_modules/@testing-library/user-event",
"@types/jest": "link:../node_modules/@types/jest",
"@types/node": "link:../node_modules/@types/node",
"@types/react": "link:../node_modules/@types/react",
"@types/react-dom": "link:../node_modules/@types/react-dom",
"react": "link:../node_modules/react",
"react-dom": "link:../node_modules/react-dom",
"react-scripts": "link:../node_modules/react-scripts",
"typescript": "link:../node_modules/typescript",
"react-glue": "link:.."
},
"devDependencies": {
"@babel/plugin-syntax-object-rest-spread": "^7.8.3"
},
"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"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>react-glue</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@@ -0,0 +1,15 @@
{
"short_name": "react-glue",
"name": "react-glue",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
it('renders without crashing', () => {
const div = document.createElement('div')
ReactDOM.render(<App />, div)
ReactDOM.unmountComponentAtNode(div)
})

View File

@@ -0,0 +1,10 @@
import React from 'react'
import { MCaptchaWidget } from '@mcaptcha/react-glue'
import 'react-glue/dist/index.css'
const App = () => {
return <MCaptchaWidget siteKey='foo' />
}
export default App

View File

@@ -0,0 +1,14 @@
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -0,0 +1,7 @@
import './index.css'
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true
},
"include": ["src"],
"exclude": ["node_modules", "build"]
}

View File

@@ -0,0 +1,96 @@
{
"name": "@mcaptcha/react-glue",
"version": "0.1.0-alpha-1",
"description": "glue code to setup mCaptcha on your React website",
"author": "Aravinth Manivannan <realaravinth@batsense.net>",
"license": "(MIT OR Apache-2.0)",
"keywords": [
"mCaptcha",
"CAPTCHA",
"proof of work",
"react"
],
"homepage": "https://mcaptcha.org",
"browser": "dist/index.js",
"types": "dist/index.d.ts",
"bugs": {
"url": "https://github.com/mCaptcha/glue/issues",
"email": "realaravinth@batsense.net"
},
"funding": [
{
"type": "individual",
"url": "http://mcaptcha.org/donate"
},
{
"type": "liberapay",
"url": "https://liberapay.com/mcaptcha"
},
{
"type": "individual",
"url": "http://batsense.net/donate"
},
{
"type": "liberapay",
"url": "https://liberapay.com/realaravinth"
}
],
"repository": {
"type": "git",
"url": "https://github.com/mCaptcha/glue.git"
},
"main": "dist/index.js",
"module": "dist/index.modern.js",
"source": "src/index.tsx",
"engines": {
"node": ">=10"
},
"scripts": {
"build": "microbundle-crl --no-compress --format modern,cjs",
"start": "microbundle-crl watch --no-compress --format modern,cjs",
"prepare": "run-s build",
"test": "run-s test:unit test:build",
"test:build": "run-s build",
"test:lint": "eslint src/",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"predeploy": "cd example && yarn install && yarn run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"react": "^16.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@types/node": "^12.12.38",
"@types/react": "^16.9.27",
"@types/react-dom": "^16.9.7",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.7.0",
"eslint-config-standard": "^14.1.0",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-standard": "^4.0.1",
"gh-pages": "^2.2.0",
"microbundle-crl": "^0.13.10",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "^3.4.1",
"typescript": "^3.7.5"
},
"files": [
"dist"
]
}

View File

@@ -0,0 +1,5 @@
{
"env": {
"jest": true
}
}

View File

@@ -0,0 +1,50 @@
import React from 'react'
import { render, unmountComponentAtNode } from 'react-dom'
import { act } from 'react-dom/test-utils'
import { MCaptchaWidget, Config, INPUT_NAME, ConfigurationError } from '.'
let container = null
beforeEach(() => {
// setup a DOM element as a render target
container = document.createElement('div')
document.body.appendChild(container)
})
afterEach(() => {
// cleanup on exiting
if (container) {
unmountComponentAtNode(container)
container.remove()
container = null
}
})
describe('MCaptchaWidget', () => {
it('with site key', () => {
if (container) {
act(() => render(<MCaptchaWidget siteKey='foo' />, container))
let w = container.querySelector(`#${INPUT_NAME}`)
expect(w).toBeTruthy()
}
})
it('with widget link', () => {
if (container) {
act(() =>
render(<MCaptchaWidget widgetLink='https://example.com' />, container)
)
let w = container.querySelector(`#${INPUT_NAME}`)
expect(w).toBeTruthy()
}
})
it('without configuration params, should error out', () => {
if (container) {
try {
render(<MCaptchaWidget />, container)
} catch (e) {
expect(e.message).toBe(new ConfigurationError().message)
}
}
})
})

View File

@@ -0,0 +1,100 @@
import * as React from 'react'
import { useState, useEffect, ReactElement } from 'react'
/** Configuration for MCaptchaWidget */
export type Config = {
/** URL of the widget. Use this only when you are using a self-hosted
* instance of mCaptcha with a non-standard(unofficial) path(i.e, widgets
* are not served from `/widget/?stiekey=uniqueSitekey` */
widgetLink?: URL
/** site key as given in the admin dashboard. Widget
* link will be derived from this.
*/
siteKey?: string
}
/** configuration error thrown by MCaptchaWidget */
export class ConfigurationError extends Error {
/** error message */
message = 'Provide either widget link or site key to display mCaptcha widget'
}
export const INPUT_NAME = 'mcaptcha__token'
/**
* @param {URL?}widgetLink: URL of the widget. Use this only when you are using
* a self-hosted instance of mCaptcha with a non-standard(unofficial) path(i.e,
* widgets are not served from `/widget/?stiekey=uniqueSitekey`
* @param {string?}sitekey: site key as given in the admin dashboard. Widget
* link will be derived from this.
*
* @returns {ReactElement}: mCaptcha widget containing an input field, which
* will hold the verification token and an iframe containing the widget
*
* @throws {ConfigurationError}: This error is thrown when neither widget link
* nor site key is provided to this compoenent
*/
export const MCaptchaWidget = ({
widgetLink,
siteKey
}: Config): ReactElement => {
const containerStyle = {
width: '340px',
height: '78px'
}
let iframeSource: URL
if (widgetLink) {
iframeSource = widgetLink
} else if (siteKey) {
iframeSource = new URL(
`https://demo.mcaptcha.org/widget/?sitekey=${siteKey}`
)
} else {
throw new ConfigurationError()
}
const [token, setToken] = useState('')
const handle = (e: MessageEvent): void => {
if (new URL(e.origin).host !== iframeSource.host) {
console.error(
`expected message from ${iframeSource.host} but received message from ${e.origin}. Aborting.`
)
return
}
console.log(`received message, setting token to ${e.data.token}`)
setToken(e.data.token)
}
useEffect(() => {
window.addEventListener('message', handle)
const cleanup = (): void => window.removeEventListener('message', handle)
return cleanup
})
return (
<div style={containerStyle}>
<input
id={INPUT_NAME}
name={INPUT_NAME}
value={token}
hidden
required
type='text'
/>
<iframe
title='mCaptcha'
src={iframeSource.toString()}
role='presentation'
name='mcaptcha-widget__iframe'
id='mcaptcha-widget__iframe'
scrolling='no'
sandbox='allow-same-origin allow-scripts'
width='304'
height='78'
frameBorder='0'
/>
</div>
)
}
export default MCaptchaWidget

View File

@@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@@ -0,0 +1,9 @@
/* add css module styles here (optional) */
.test {
margin: 2em;
padding: 0.5em;
border: 2px solid #000;
font-size: 2em;
text-align: center;
}

17
packages/react-glue/src/typings.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
/**
* Default CSS definition for typescript,
* will be overridden with file-specific definitions by rollup
*/
declare module '*.css' {
const content: { [className: string]: string };
export default content;
}
interface SvgrComponent extends React.StatelessComponent<React.SVGAttributes<SVGElement>> {}
declare module '*.svg' {
const svgUrl: string;
const svgComponent: SvgrComponent;
export default svgUrl;
export { svgComponent as ReactComponent }
}

View File

@@ -0,0 +1,39 @@
{
"compilerOptions": {
"outDir": "dist",
"module": "esnext",
"lib": [
"dom",
"esnext"
],
"moduleResolution": "node",
"jsx": "react",
"sourceMap": true,
"declaration": true,
"esModuleInterop": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowSyntheticDefaultImports": true,
"target": "es5",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
},
"include": [
"src"
],
"exclude": [
"node_modules",
"dist",
"example"
]
}

View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs"
}
}

View File

@@ -0,0 +1,127 @@
Arguments:
/usr/bin/node /usr/bin/yarn test
PATH:
/usr/lib/safe-rm:/home/aravinth/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/usr/lib/safe-rm:/home/aravinth/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl:/home/aravinth/.local/bin/:/home/aravinth/.cargo/bin/:/home/aravinth/go/bin/:/home/aravinth/yarn/bin/:/home/aravinth/.gem/ruby/2.7.0/bin:/home/aravinth/.rustup/toolchains/*/bin/:/home/aravinth/workspace/fabric/bin:/home/aravinth/dotfiles/scripts:/home/aravinth/bin/:/home/aravinth/dotfiles/scripts/redis/:/home/aravinth/.fzf/bin:/opt/hub/bin:/usr/lib/jvm/default/bin:/home/aravinth/.local/bin/:/home/aravinth/.cargo/bin/:/home/aravinth/go/bin/:/home/aravinth/yarn/bin/:/home/aravinth/.gem/ruby/2.7.0/bin:/home/aravinth/.rustup/toolchains/*/bin/:/home/aravinth/workspace/fabric/bin:/home/aravinth/dotfiles/scripts:/home/aravinth/bin/:/home/aravinth/dotfiles/scripts/redis/:/opt/hub/bin
Yarn version:
1.22.17
Node version:
17.1.0
Platform:
linux x64
Trace:
SyntaxError: /home/aravinth/code/mCaptcha/glue/packages/react-glue/package.json: Unexpected token / in JSON at position 1301
at JSON.parse (<anonymous>)
at /usr/lib/node_modules/yarn/lib/cli.js:1625:59
at Generator.next (<anonymous>)
at step (/usr/lib/node_modules/yarn/lib/cli.js:310:30)
at /usr/lib/node_modules/yarn/lib/cli.js:321:13
npm manifest:
{
"name": "@mcaptcha/react-glue",
"version": "0.1.0-alpha-1",
"description": "glue code to setup mCaptcha on your React website",
"author": "Aravinth Manivannan <realaravinth@batsense.net>",
"license": "(MIT OR Apache-2.0)",
"keywords": [
"mCaptcha",
"CAPTCHA",
"proof of work",
"react"
],
"homepage": "https://mcaptcha.org",
"browser": "dist/index.js",
"types": "dist/index.d.ts",
"bugs": {
"url": "https://github.com/mCaptcha/glue/issues",
"email": "realaravinth@batsense.net"
},
"funding": [
{
"type": "individual",
"url": "http://mcaptcha.org/donate"
},
{
"type": "liberapay",
"url": "https://liberapay.com/mcaptcha"
},
{
"type": "individual",
"url": "http://batsense.net/donate"
},
{
"type": "liberapay",
"url": "https://liberapay.com/realaravinth"
}
],
"repository": {
"type": "git",
"url": "https://github.com/mCaptcha/glue.git"
},
"main": "dist/index.js",
"module": "dist/index.modern.js",
"source": "src/index.tsx",
"engines": {
"node": ">=10"
},
"scripts": {
"build": "microbundle-crl --no-compress --format modern,cjs",
"start": "microbundle-crl watch --no-compress --format modern,cjs",
"prepare": "run-s build",
//"test": "run-s test:unit test:lint test:build",
"test": "run-s test:unit test:build",
"test:build": "run-s build",
"test:lint": "eslint src/",
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"predeploy": "cd example && yarn install && yarn run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"react": "^16.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"@types/node": "^12.12.38",
"@types/react": "^16.9.27",
"@types/react-dom": "^16.9.7",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.7.0",
"eslint-config-standard": "^14.1.0",
"eslint-config-standard-react": "^9.2.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-standard": "^4.0.1",
"gh-pages": "^2.2.0",
"microbundle-crl": "^0.13.10",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "^3.4.1",
"typescript": "^3.7.5"
},
"files": [
"dist"
]
}
yarn manifest:
No manifest
Lockfile:
No lockfile

10753
yarn.lock

File diff suppressed because it is too large Load Diff