├── .gitignore ├── README.md ├── config └── rollup.style.config.js ├── demo ├── README.md ├── package.json ├── src │ ├── antd.html │ ├── antd.tsx │ ├── bootstrap.html │ ├── bootstrap.tsx │ ├── components │ │ ├── App.less │ │ ├── App.tsx │ │ ├── MakeInvoice.less │ │ ├── MakeInvoice.tsx │ │ ├── SendPayment.tsx │ │ ├── SignMessage.tsx │ │ └── VerifyMessage.tsx │ ├── index.html │ ├── index.tsx │ ├── material-ui.html │ ├── material-ui.tsx │ ├── reactstrap.html │ ├── reactstrap.tsx │ ├── semantic-ui.html │ └── semantic-ui.tsx ├── static │ ├── CNAME │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon.ico ├── tsconfig.json └── webpack.config.js ├── lerna.json ├── package.json ├── packages ├── react-webln-fallback-antd │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── CLIHelp.tsx │ │ ├── MakeInvoice.tsx │ │ ├── SendPayment.tsx │ │ ├── SignMessage.tsx │ │ ├── VerifyMessage.tsx │ │ ├── index.tsx │ │ └── umd.tsx │ ├── tsconfig.json │ └── webpack.config.js ├── react-webln-fallback-bootstrap │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── CLIHelp.tsx │ │ ├── MakeInvoice.tsx │ │ ├── SendPayment.tsx │ │ ├── SignMessage.tsx │ │ ├── VerifyMessage.tsx │ │ ├── index.tsx │ │ └── umd.tsx │ ├── tsconfig.json │ └── webpack.config.js ├── react-webln-fallback-core │ ├── .babelrc │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── components │ │ │ ├── MakeInvoiceInstructions.tsx │ │ │ └── ReactWebLNFallback.tsx │ │ ├── i18n │ │ │ ├── en.json │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── cli.ts │ │ │ ├── provider.ts │ │ │ └── signature.ts │ └── tsconfig.json ├── react-webln-fallback-material-ui │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── CLIHelp.tsx │ │ ├── MakeInvoice.tsx │ │ ├── SendPayment.tsx │ │ ├── SignMessage.tsx │ │ ├── VerifyMessage.tsx │ │ ├── index.tsx │ │ └── umd.tsx │ ├── tsconfig.json │ └── webpack.config.js ├── react-webln-fallback-reactstrap │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── CLIHelp.tsx │ │ ├── MakeInvoice.tsx │ │ ├── SendPayment.tsx │ │ ├── SignMessage.tsx │ │ ├── VerifyMessage.tsx │ │ ├── index.tsx │ │ └── umd.tsx │ ├── tsconfig.json │ └── webpack.config.js └── react-webln-fallback-semantic-ui │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── CLIHelp.tsx │ ├── MakeInvoice.tsx │ ├── SendPayment.tsx │ ├── SignMessage.tsx │ ├── VerifyMessage.tsx │ ├── index.tsx │ └── umd.tsx │ ├── tsconfig.json │ └── webpack.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | cjs 4 | esm 5 | umd 6 | *.log 7 | .rpt2_cache 8 | .vscode 9 | webpack-stats.json 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React WebLN Fallback 2 | 3 | This is a set of React components and standalone libraries that provides fallback behavior for Lightning apps that use [WebLN](https://webln.dev/), allowing for developers to use a consistent API while still handling users that don't have WebLN clients in their browsers with a first-class user experience. 4 | 5 | ## Demo Example 6 | 7 | Check out the demo here: https://react-fallback.webln.dev/ 8 | 9 | ## Limitations 10 | 11 | Before you dive in, you should know what this library **doesn't** do: 12 | 13 | * **It doesn't handle payment events**. These components do not hook into your node, so you will need to trigger some methods on certain node events yourself. Otherwise the components will remain open and inert, since they aren't listening on their own. See [Event Handling](#event-handling) for more information. 14 | * **It doesn't handle validation**. This may change in future versions, but validation requires importing some heavy libraries, and should be done by your app anyway. Trusting input from all WebLN clients isn't a good idea, since even apps or extensions could provide you invalid data. 15 | * **It doesn't handle every browser under the sun.** It hasn't been tested extensively, but a lot of the component libraries used don't support old versions of IE and Safari. 16 | 17 | ## Styles 18 | 19 | React WebLN Fallback comes in 4 styles: 20 | 21 | * [`react-webln-fallback-antd`](https://www.npmjs.com/package/react-webln-fallback-antd) - using [Ant Design](https://ant.design/) 22 | * [`react-webln-fallback-bootstrap`](https://www.npmjs.com/package/react-webln-fallback-bootstrap) - using [Bootstrap](https://getbootstrap.com/) and [`react-bootstrap`](https://www.npmjs.com/package/react-bootstrap) 23 | * [`react-webln-fallback-reactstrap`](https://www.npmjs.com/package/react-webln-fallback-reactstrap) - using [Bootstrap](https://getbootstrap.com/) and [`reactstrap`](https://www.npmjs.com/package/reactstrap) 24 | * [`react-webln-fallback-material-ui`](https://www.npmjs.com/package/react-webln-fallback-material-ui) - using [Material UI](https://material-ui.com/) 25 | * [`react-webln-fallback-semantic-ui`](https://www.npmjs.com/package/react-webln-fallback-semantic-ui) - using [Semantic UI](https://react.semantic-ui.com/) 26 | 27 | You can preview all of these styles in the [demo](https://react-fallback.webln.dev/). 28 | 29 | Using a style component does **not** include the styles for the associated library, the expectation is that you have included the styles somewhere in your project. Please refer to the associated library for instructions on including its styling. 30 | 31 | If you the available styles aren't to your liking, you can create your own using any of the above as an example. The [`react-webln-fallback-core`](https://www.npmjs.com/package/react-webln-fallback-core) package provides most of the utilities you'll need. 32 | 33 | ## Installation & usage (React) 34 | 35 | Include the component towards the top of your root component and you're good to go: 36 | 37 | ```tsx 38 | import { ReactWebLNFallback } from 'react-webln-fallback-[style]'; 39 | 40 | ReactDOM.render( 41 | <> 42 | 43 | 44 | 45 | ); 46 | ``` 47 | 48 | The component also takes in the following props (all optional): 49 | 50 | | Prop | Type | Description | 51 | |------------------|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 52 | | supportedMethods | Array | An array of the WebLN methods that the component will support. Defaults to all methods in the WebLNMethod enum. | 53 | | methodComponents | { [key: WebLNMethod]: React.Component } | An object keyed by WebLN method to component. Each component receives the WebLN method parameters as its arguments. See our components for example. | 54 | | i18nextLng | `i18next.Language` | [ISO-639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) language code. Defaults to using [i18next-browser-langaugedetector](https://www.npmjs.com/package/i18next-browser-languagedetector) for language auto-detection | 55 | | overrideWebLN | boolean | Forcefully user-provided WebLN client. Not recommended in production, mainly for demoing & testing. | 56 | 57 | ## Installation & usage (Standalone UMD) 58 | 59 | If you're not using React and / or don't have a build system setup, you can use the standalone versions of each style. Simply include the script for the style you want, call init with an element to mount to, and you're good to go. 60 | 61 | ```html 62 | 63 | 64 | 65 | 71 |
72 | ``` 73 | 74 | The standalone version bundles React with it to allow for use even with non-React applications. If you are already using React and don't want it bundled twice, you should use the node module (instructions above) instead of the UMD script. 75 | 76 | 77 | ## Event Handling & Methods 78 | 79 | The SendPayment component doesn't hook into your node, so it doesn't automatically know when a payment has been made. This means that your app will need to alert the library when that happens. Two functions have been provided for you to handle this: 80 | 81 | ### paymentComplete(preimage: string) 82 | 83 | Displays a success message to the user, and auto-close the modal shortly after. This only happens if SendPayment is open, otherwise it's a no-op (With a console warning in development, if that happens.) You should pass the preimage string, so that your `webln.sendPayment` callback receives it too. 84 | 85 | ```tsx 86 | // Module Style 87 | import { paymentComplete } from 'react-webln-fallback-[style]'; 88 | paymentComplete(response.preimage); 89 | 90 | // UMD Style 91 | ReactWebLNFallback.closePrompt(response.preimage); 92 | ``` 93 | 94 | ### closePrompt() 95 | 96 | If something bad happens with the payment, or you otherwise just want to return the user to your app immediately, calling this will close _any_ prompt, not just SendPayment. It's safe to call even if there are no prompts open, so just fire-and-forget as needed. 97 | 98 | ```tsx 99 | // Module Style 100 | import { closePrompt } from 'react-webln-fallback-[style]'; 101 | closePrompt(); 102 | 103 | // UMD Style 104 | ReactWebLNFallback.closePrompt(); 105 | ``` 106 | 107 | ## Localization 108 | 109 | React WebLN Fallback supports the following languages: 110 | 111 | * English (en) 112 | 113 | If you'd like to add support for your language, simply add a `[lang].json` file to `packages/react-webln-fallback-core/src/i18n/` and add the following code to `index.ts` in that folder: 114 | 115 | ```diff 116 | import en from './en.json'; 117 | +import fr from './fr.json'; 118 | 119 | const resources = { 120 | en: { translation: en }, 121 | + fr: { translation: fr }, 122 | }; 123 | ``` 124 | 125 | ## Development 126 | 127 | ### Requirements 128 | 129 | * Node 8+ 130 | * Yarn 1+ (many commands use this, so npm will not be enough) 131 | 132 | ### Development 133 | 134 | This project uses Lerna to coordinate dependencies. 135 | 136 | * Install dependencies and setup local links with `yarn && yarn bootstrap` 137 | * Run `yarn dev` to simultaneously run all package's dev commands and start the demo webserver at `localhost:8080` 138 | 139 | ### Building 140 | 141 | Simply run `yarn build` in the root directory to build production versions of each package 142 | 143 | 144 | ### Publishing 145 | 146 | See https://github.com/lerna/lerna for more 147 | -------------------------------------------------------------------------------- /config/rollup.style.config.js: -------------------------------------------------------------------------------- 1 | const typescript = require('rollup-plugin-typescript2'); 2 | const peerDepsExternal = require('rollup-plugin-peer-deps-external'); 3 | const localResolve = require('rollup-plugin-local-resolve'); 4 | const nodeResolve = require('rollup-plugin-node-resolve'); 5 | const commonjs = require('rollup-plugin-commonjs'); 6 | const json = require('rollup-plugin-json'); 7 | const terser = require('rollup-plugin-terser'); 8 | const pkg = require('./package.json'); 9 | 10 | const isDev = process.env.NODE_ENV !== 'production'; 11 | 12 | const makePlugins = (opts) => { 13 | return [ 14 | typescript({ 15 | tsconfigOverride: { 16 | compilerOptions: { 17 | declaration: !!opts.declarations, 18 | }, 19 | }, 20 | }), 21 | opts.externals && peerDepsExternal(), 22 | localResolve(), 23 | nodeResolve(), 24 | commonjs(opts.externals ? undefined : { 25 | include: [ 26 | 'node_modules/**' 27 | ], 28 | namedExports: { 29 | 'node_modules/react/react.js': ['Children', 'Component', 'PropTypes', 'createElement'], 30 | 'node_modules/react-dom/index.js': ['render'], 31 | 'node_modules/webln/lib/errors.js': ['UnsupportedMethodError', 'RejectionError'], 32 | }, 33 | }), 34 | json(), 35 | opts.minify && terser({ 36 | compress: { 37 | pure_getters: true, 38 | unsafe: true, 39 | unsafe_comps: true, 40 | warnings: false, 41 | }, 42 | }), 43 | ].filter(p => !!p); 44 | }; 45 | 46 | module.exports = [{ 47 | // CommonJS and ESModule 48 | input: 'src/index.tsx', 49 | output: [{ 50 | file: pkg.main, 51 | format: 'cjs', 52 | name: 'React WebLN Fallback', 53 | }, { 54 | file: pkg.module, 55 | format: 'es', 56 | }], 57 | plugins: makePlugins({ declarations: true, externals: true }), 58 | }, { 59 | // UMD (Development) 60 | input: 'src/umd.tsx', 61 | output: [{ 62 | file: 'umd/react-webln-fallback.js', 63 | format: 'umd', 64 | name: 'ReactWebLNFallback', 65 | indent: false, 66 | }], 67 | plugins: makePlugins({ declarations: false }), 68 | }, !isDev && { 69 | // UMD (Production) 70 | input: 'src/umd.tsx', 71 | output: [{ 72 | file: 'umd/react-webln-fallback.min.js', 73 | format: 'umd', 74 | name: 'ReactWebLNFallback', 75 | indent: false, 76 | }], 77 | plugins: makePlugins({ declarations: false, minify: true }), 78 | }].filter(c => !!c); 79 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # React WebLN Fallback Demo 2 | 3 | This is a little demo based off of the [WebLN demos](https://webln.dev/) that shows off how React WebLN Fallback works. 4 | 5 | ## Development 6 | 7 | 1. Run `yarn` to get dependencies 8 | 2. Go up to the parent directory 9 | 3. Run `yarn build` to build the `react-webln-fallback` component 10 | 4. Run `yarn link` to provide a link for `react-webln-fallback` 11 | 5. Return to this directory 12 | 6. Run `yarn dev 13 | 14 | Changes to the library will be reflected in the demo as you make changes. 15 | 16 | ## Building / Publishing 17 | 18 | 1. Publish the latest version of `react-webln-fallback` 19 | 2. Run `yarn add react-webln-fallback@[new-version-here]` to upgrade to the latest version 20 | 3. Run `yarn unlink react-webln-fallback` to make sure you're using the npm package and not the local one 21 | 4. Run `yarn publish` to update the gh-pages branch, or `yarn build` to just make a build without pushing 22 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-webln-fallback-demo", 3 | "private": true, 4 | "version": "0.2.1", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "webpack serve", 9 | "build": "rm -rf dist/ && NODE_ENV=production webpack", 10 | "ghpages": "npm run build && gh-pages -d dist" 11 | }, 12 | "dependencies": { 13 | "react-webln-fallback-antd": "file:../packages/react-webln-fallback-antd", 14 | "react-webln-fallback-bootstrap": "file:../packages/react-webln-fallback-bootstrap", 15 | "react-webln-fallback-core": "file:../packages/react-webln-fallback-core", 16 | "react-webln-fallback-material-ui": "file:../packages/react-webln-fallback-material-ui", 17 | "react-webln-fallback-reactstrap": "file:../packages/react-webln-fallback-reactstrap", 18 | "react-webln-fallback-semantic-ui": "file:../packages/react-webln-fallback-semantic-ui" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /demo/src/antd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React WebLN Fallback - Ant Design 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/antd.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ReactWebLNFallback, { paymentComplete } from 'react-webln-fallback-antd'; 4 | import App from './components/App'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') as HTMLElement, 9 | ); 10 | -------------------------------------------------------------------------------- /demo/src/bootstrap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React WebLN Fallback - Bootstrap 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/bootstrap.tsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import ReactWebLNFallback, { paymentComplete } from 'react-webln-fallback-bootstrap'; 5 | import App from './components/App'; 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') as HTMLElement, 10 | ); 11 | -------------------------------------------------------------------------------- /demo/src/components/App.less: -------------------------------------------------------------------------------- 1 | // App styles 2 | .App { 3 | max-width: 980px; 4 | margin: 0 auto; 5 | padding: 0 2rem; 6 | 7 | &-header { 8 | display: flex; 9 | justify-content: space-between; 10 | align-items: center; 11 | padding: 2rem 0 2rem; 12 | 13 | &-left { 14 | &-docs { 15 | font-size: 0.8rem; 16 | margin-top: -0.75rem; 17 | opacity: 0.8; 18 | } 19 | } 20 | } 21 | } 22 | 23 | // Antd overrides 24 | .ant-form-item { 25 | margin-bottom: 0.5rem; 26 | } 27 | 28 | .ant-checkbox-wrapper { 29 | margin-bottom: 0.5rem; 30 | } 31 | 32 | .ant-tabs-nav::before { 33 | width: 100%; 34 | } 35 | 36 | // Overrides from other libraries 37 | .ant-btn code { 38 | color: inherit; 39 | font-size: 0.9em; 40 | padding-left: 0.25rem; 41 | } 42 | -------------------------------------------------------------------------------- /demo/src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tabs, Dropdown, Menu, Button } from 'antd'; 3 | import MakeInvoice from './MakeInvoice'; 4 | import SendPayment from './SendPayment'; 5 | import SignMessage from './SignMessage'; 6 | import VerifyMessage from './VerifyMessage'; 7 | import 'antd/dist/antd.css'; 8 | import './App.less'; 9 | 10 | const STYLES = { 11 | antd: { 12 | name: 'Ant Design', 13 | link: 'https://ant.design', 14 | }, 15 | bootstrap: { 16 | name: 'Bootstrap (react-bootstrap)', 17 | link: 'https://react-bootstrap.github.io/', 18 | }, 19 | reactstrap: { 20 | name: 'Bootstrap (reactstrap)', 21 | link: 'https://reactstrap.github.io/', 22 | }, 23 | 'semantic-ui': { 24 | name: 'Semantic UI', 25 | link: 'https://react.semantic-ui.com', 26 | }, 27 | 'material-ui': { 28 | name: 'Material UI', 29 | link: 'https://material-ui.com/', 30 | }, 31 | }; 32 | type Style = keyof typeof STYLES; 33 | 34 | interface Props { 35 | style: Style; 36 | WebLNFallbackComponent: React.ComponentType; 37 | paymentComplete(preImage: string): void; 38 | } 39 | 40 | export default class App extends React.Component { 41 | render() { 42 | const { style, WebLNFallbackComponent, paymentComplete } = this.props; 43 | const tabs = [{ 44 | key: 'sendPayment', 45 | tab: 'Send Payment', 46 | content: , 47 | }, { 48 | key: 'makeInvoice', 49 | tab: 'Make Invoice', 50 | content: , 51 | }, { 52 | key: 'signMessage', 53 | tab: 'Sign Message', 54 | content: , 55 | }, { 56 | key: 'verifyMessage', 57 | tab: 'Verify Message', 58 | content: , 59 | }]; 60 | 61 | const styleMenu = ( 62 | 63 | {Object.entries(STYLES).map(([s, info]) => ( 64 | this.goToStyle(s)}> 65 | {info.name} 66 | 67 | ))} 68 | 69 | ) 70 | 71 | return ( 72 |
73 |
74 |
75 |

76 | React WebLN Fallback Demo 77 |

78 |
79 | For more information, check out the{' '} 80 | 81 | documentation on GitHub 82 | . 83 |
84 |
85 |
86 | 87 | 90 | 91 |
92 |
93 |
94 | 95 | {tabs.map(t => ( 96 | 97 | {t.content} 98 | 99 | ))} 100 | 101 |
102 | 103 |
104 | ) 105 | } 106 | 107 | private goToStyle = (s: string) => { 108 | window.location.pathname = `/${s}`; 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /demo/src/components/MakeInvoice.less: -------------------------------------------------------------------------------- 1 | .MakeInvoice { 2 | &-form { 3 | &.ant-form { 4 | margin-bottom: 2rem; 5 | } 6 | 7 | &-amounts { 8 | position: relative; 9 | padding: 1.5rem 1rem 0.5rem; 10 | margin: 1rem 0 0.5rem; 11 | border: 1px solid rgba(#000, 0.1); 12 | 13 | &-toggle { 14 | position: absolute; 15 | left: 50%; 16 | top: 0; 17 | width: 100%; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | transform: translate(-50%, -50%); 22 | } 23 | } 24 | } 25 | 26 | &-pr { 27 | padding-top: 10px; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /demo/src/components/MakeInvoice.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row, Col, Form, Input, Radio, Button, message } from 'antd'; 3 | import { RadioChangeEvent } from 'antd/lib/radio'; 4 | import { requestProvider } from 'webln'; 5 | import './MakeInvoice.less'; 6 | 7 | export interface FormState { 8 | amount: string; 9 | defaultAmount: string; 10 | minimumAmount: string; 11 | maximumAmount: string; 12 | defaultMemo: string; 13 | } 14 | 15 | interface State { 16 | form: FormState; 17 | amountType: string; 18 | paymentRequest: string; 19 | } 20 | 21 | export default class MakeInvoice extends React.PureComponent<{}, State> { 22 | state: State = { 23 | form: { 24 | amount: '', 25 | defaultAmount: '', 26 | minimumAmount: '100', 27 | maximumAmount: '1000', 28 | defaultMemo: 'React WebLN Fallback is cool', 29 | }, 30 | amountType: 'dynamic', 31 | paymentRequest: '', 32 | }; 33 | 34 | render() { 35 | const { amountType, form, paymentRequest } = this.state; 36 | 37 | let amountInputs; 38 | if (amountType === 'fixed') { 39 | amountInputs = ( 40 | 41 | 47 | 48 | ); 49 | } else { 50 | amountInputs = ( 51 | <> 52 | 53 | 59 | 60 | 61 | 67 | 68 | 69 | 75 | 76 | 77 | ); 78 | } 79 | 80 | return ( 81 | 82 | 83 |
84 |
85 |
86 | 90 | Dynamic values 91 | Fixed value 92 | 93 |
94 | {amountInputs} 95 |
96 | 97 | 102 | 103 | 104 | 112 |
113 | 114 | 115 |
116 | 117 | 118 | 119 |
120 | 121 |
122 | ); 123 | } 124 | 125 | private handleInput = (ev: React.ChangeEvent) => { 126 | const form = { 127 | ...this.state.form, 128 | [ev.target.name]: ev.target.value, 129 | }; 130 | this.setState({ form }); 131 | }; 132 | 133 | private handleChangeAmountType = (ev: RadioChangeEvent) => { 134 | const { value } = ev.target; 135 | const form = { ...this.state.form }; 136 | if (value === 'fixed') { 137 | form.defaultAmount = ''; 138 | form.minimumAmount = ''; 139 | form.maximumAmount = ''; 140 | } else { 141 | form.amount = ''; 142 | } 143 | this.setState({ 144 | form, 145 | amountType: value, 146 | }); 147 | }; 148 | 149 | private handleSubmit = async (ev: React.FormEvent) => { 150 | ev.preventDefault(); 151 | this.setState({ paymentRequest: '' }); 152 | try { 153 | const webln = await requestProvider(); 154 | const res = await webln.makeInvoice(this.state.form); 155 | this.setState({ paymentRequest: res.paymentRequest }); 156 | message.success('Received invoice!'); 157 | } catch(err) { 158 | message.error(err.message, 3); 159 | } 160 | }; 161 | } 162 | -------------------------------------------------------------------------------- /demo/src/components/SendPayment.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Form, Button, Input, message, Checkbox } from "antd"; 3 | import { requestProvider } from "webln"; 4 | import { CheckboxChangeEvent } from "antd/lib/checkbox"; 5 | 6 | interface Props { 7 | paymentComplete(preimage: string): void; 8 | } 9 | 10 | interface State { 11 | paymentRequest: string; 12 | callPaymentComplete: boolean; 13 | } 14 | 15 | export default class SendPayment extends React.PureComponent { 16 | state: State = { 17 | paymentRequest: "", 18 | callPaymentComplete: true, 19 | }; 20 | 21 | render() { 22 | const { paymentRequest, callPaymentComplete } = this.state; 23 | 24 | return ( 25 |
26 | 27 | 32 | 33 | 34 | Automatically call paymentComplete after 3s 35 | 36 | 45 |
46 | ); 47 | } 48 | 49 | private handlePaymentRequestChange = ( 50 | ev: React.ChangeEvent 51 | ) => { 52 | this.setState({ paymentRequest: ev.target.value }); 53 | }; 54 | 55 | private handleCheckboxChange = (ev: CheckboxChangeEvent) => { 56 | this.setState({ callPaymentComplete: ev.target.checked }); 57 | }; 58 | 59 | private handleSubmit = async (ev: React.FormEvent) => { 60 | ev.preventDefault(); 61 | try { 62 | const webln = await requestProvider(); 63 | if (this.state.callPaymentComplete) { 64 | setTimeout(() => this.props.paymentComplete('123'), 3000); 65 | } 66 | await webln.sendPayment(this.state.paymentRequest); 67 | message.success("Payment succeeded!"); 68 | } catch (err) { 69 | message.error(err.message, 3); 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /demo/src/components/SignMessage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Button, Input, Row, Col, message } from 'antd'; 3 | import { requestProvider } from 'webln'; 4 | 5 | interface State { 6 | message: string; 7 | signature: string; 8 | } 9 | 10 | export default class SignMessage extends React.PureComponent<{}, State> { 11 | state: State = { 12 | message: '', 13 | signature: '', 14 | }; 15 | 16 | render() { 17 | const { message, signature } = this.state; 18 | 19 | return ( 20 |
21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 46 |
47 | ); 48 | } 49 | 50 | private handleChange = (ev: React.ChangeEvent) => { 51 | this.setState({ message: ev.target.value }); 52 | }; 53 | 54 | private handleSubmit = async (ev: React.FormEvent) => { 55 | ev.preventDefault(); 56 | try { 57 | const webln = await requestProvider(); 58 | const res = await webln.signMessage(this.state.message); 59 | this.setState(res); 60 | message.success('Signature succeeded!'); 61 | } catch(err) { 62 | message.error(err.message, 3); 63 | } 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /demo/src/components/VerifyMessage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Form, Button, Input, Row, Col, message } from 'antd'; 3 | import { requestProvider } from 'webln'; 4 | 5 | interface State { 6 | message: string; 7 | signature: string; 8 | } 9 | 10 | export default class VerifyMessage extends React.PureComponent<{}, State> { 11 | state: State = { 12 | message: '', 13 | signature: '', 14 | }; 15 | 16 | render() { 17 | const { message, signature } = this.state; 18 | 19 | return ( 20 |
21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | 45 | 48 |
49 | ); 50 | } 51 | 52 | private handleChange = (ev: React.ChangeEvent) => { 53 | this.setState({ [ev.target.name]: ev.target.value } as any); 54 | }; 55 | 56 | private handleSubmit = async (ev: React.FormEvent) => { 57 | ev.preventDefault(); 58 | try { 59 | const webln = await requestProvider(); 60 | await webln.verifyMessage(this.state.signature, this.state.message); 61 | } catch(err) { 62 | message.error(err.message, 3); 63 | } 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React WebLN Fallback 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ReactWebLNFallback, { paymentComplete } from 'react-webln-fallback-antd'; 4 | import App from './components/App'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') as HTMLElement, 9 | ); 10 | -------------------------------------------------------------------------------- /demo/src/material-ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React WebLN Fallback - Material UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /demo/src/material-ui.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ReactWebLNFallback, { paymentComplete } from 'react-webln-fallback-material-ui'; 4 | import App from './components/App'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') as HTMLElement, 9 | ); 10 | -------------------------------------------------------------------------------- /demo/src/reactstrap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React WebLN Fallback - Bootstrap (reactstrap) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/reactstrap.tsx: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.min.css'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import ReactWebLNFallback, { paymentComplete } from 'react-webln-fallback-reactstrap'; 5 | import App from './components/App'; 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') as HTMLElement, 10 | ); 11 | -------------------------------------------------------------------------------- /demo/src/semantic-ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React WebLN Fallback - Semantic UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /demo/src/semantic-ui.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ReactWebLNFallback, { paymentComplete } from 'react-webln-fallback-semantic-ui'; 4 | import App from './components/App'; 5 | import 'semantic-ui-css/semantic.min.css'; 6 | 7 | ReactDOM.render( 8 | , 9 | document.getElementById('root') as HTMLElement, 10 | ); 11 | -------------------------------------------------------------------------------- /demo/static/CNAME: -------------------------------------------------------------------------------- 1 | react-fallback.webln.dev -------------------------------------------------------------------------------- /demo/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joule-labs/react-webln-fallback/10ef71f6d100d1c70814e94b205239b01e9e12b8/demo/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /demo/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joule-labs/react-webln-fallback/10ef71f6d100d1c70814e94b205239b01e9e12b8/demo/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /demo/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joule-labs/react-webln-fallback/10ef71f6d100d1c70814e94b205239b01e9e12b8/demo/static/apple-touch-icon.png -------------------------------------------------------------------------------- /demo/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joule-labs/react-webln-fallback/10ef71f6d100d1c70814e94b205239b01e9e12b8/demo/static/favicon-16x16.png -------------------------------------------------------------------------------- /demo/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joule-labs/react-webln-fallback/10ef71f6d100d1c70814e94b205239b01e9e12b8/demo/static/favicon-32x32.png -------------------------------------------------------------------------------- /demo/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joule-labs/react-webln-fallback/10ef71f6d100d1c70814e94b205239b01e9e12b8/demo/static/favicon.ico -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "es5", 5 | "jsx": "preserve", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "strict": true, 9 | "noEmitOnError": true, 10 | "preserveConstEnums": true, 11 | "skipLibCheck": true, 12 | "allowSyntheticDefaultImports": true, 13 | "lib": ["dom", "es2017"], 14 | "baseUrl": "." 15 | }, 16 | "include": ["src"] 17 | } 18 | -------------------------------------------------------------------------------- /demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CopyPlugin = require('copy-webpack-plugin'); 5 | 6 | const isDev = process.env.NODE_ENV !== 'production'; 7 | const publicPath = '/'; 8 | 9 | const src = path.join(__dirname, 'src'); 10 | const dist = path.join(__dirname, 'dist'); 11 | const static = path.join(__dirname, 'static'); 12 | 13 | const bundles = [ 14 | 'index', 15 | 'antd', 16 | 'bootstrap', 17 | 'reactstrap', 18 | 'material-ui', 19 | 'semantic-ui', 20 | ]; 21 | 22 | // Construct some things dynamically based on bundles array 23 | const entry = bundles.reduce((prev, bundle) => { 24 | prev[bundle] = path.join(src, `${bundle}.tsx`); 25 | return prev; 26 | }, {}) 27 | 28 | const plugins = [ 29 | new MiniCssExtractPlugin({ 30 | filename: isDev ? '[name].css' : '[name].[hash:8].css', 31 | }), 32 | ...bundles.map(b => ( 33 | new HtmlWebpackPlugin({ 34 | template: path.join(src, `${b}.html`), 35 | chunks: [b], 36 | filename: `${b}.html`, 37 | inject: true, 38 | }) 39 | )), 40 | new CopyPlugin({ 41 | patterns: [{ 42 | from: static, 43 | to: dist, 44 | }], 45 | }), 46 | ]; 47 | 48 | // Loaders 49 | const typescriptLoader = { 50 | test: /\.tsx?$/, 51 | use: [ 52 | { 53 | loader: 'babel-loader', 54 | options: { 55 | plugins: [ 56 | '@babel/plugin-proposal-object-rest-spread', 57 | '@babel/plugin-proposal-class-properties', 58 | ], 59 | presets: [ 60 | '@babel/react', 61 | ['@babel/env', { 62 | useBuiltIns: 'entry', 63 | corejs: 3, 64 | }], 65 | ], 66 | }, 67 | }, 68 | { 69 | loader: 'ts-loader', 70 | options: { transpileOnly: isDev }, 71 | }, 72 | ], 73 | }; 74 | const cssLoader = { 75 | test: /\.css$/, 76 | use: [ 77 | isDev && 'style-loader', 78 | !isDev && MiniCssExtractPlugin.loader, 79 | 'css-loader', 80 | ].filter(Boolean), 81 | }; 82 | const lessLoader = { 83 | test: /\.less$/, 84 | use: [ 85 | ...cssLoader.use, 86 | { 87 | loader: 'less-loader', 88 | options: { 89 | lessOptions: { javascriptEnabled: true }, 90 | }, 91 | }, 92 | ], 93 | }; 94 | const fileLoader = { 95 | test: /\.(png|woff|woff2|eot|ttf|svg)$/, 96 | use: [{ 97 | loader: 'file-loader', 98 | options: { 99 | publicPath, 100 | name: '[folder]/[name].[ext]', 101 | }, 102 | }], 103 | }; 104 | 105 | // Full config 106 | module.exports = { 107 | mode: isDev ? 'development' : 'production', 108 | name: 'main', 109 | target: 'web', 110 | devtool: isDev ? 'inline-cheap-module-source-map' : 'source-map', 111 | entry, 112 | plugins, 113 | output: { 114 | path: dist, 115 | publicPath, 116 | chunkFilename: isDev ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js', 117 | }, 118 | module: { 119 | rules: [ 120 | typescriptLoader, 121 | lessLoader, 122 | cssLoader, 123 | fileLoader, 124 | ], 125 | }, 126 | resolve: { 127 | extensions: ['.ts', '.tsx', '.js', '.mjs', '.json'], 128 | }, 129 | devServer: { 130 | contentBase: dist, 131 | staticOptions: { 132 | extensions: ['html'], 133 | }, 134 | hot: true, 135 | compress: true, 136 | }, 137 | }; 138 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1", 3 | "npmClient": "yarn", 4 | "useWorkspaces": true, 5 | "packages": [ 6 | "packages/*", 7 | "demo" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "author": "Will O'Beirne ", 4 | "license": "MIT", 5 | "workspaces": [ 6 | "packages/*", 7 | "demo" 8 | ], 9 | "scripts": { 10 | "bootstrap": "lerna bootstrap", 11 | "dev": "lerna run dev --stream --parallel", 12 | "build": "lerna run build --stream" 13 | }, 14 | "devDependencies": { 15 | "@babel/core": "^7.14.3", 16 | "@babel/plugin-proposal-class-properties": "^7.13.0", 17 | "@babel/plugin-proposal-object-rest-spread": "^7.14.4", 18 | "@babel/preset-env": "^7.14.4", 19 | "@babel/preset-react": "^7.13.13", 20 | "@material-ui/core": "^4.11.4", 21 | "@material-ui/icons": "^4.11.2", 22 | "@types/qrcode.react": "^1.0.1", 23 | "@types/react": "17.0.9", 24 | "@types/react-dom": "17.0.6", 25 | "@types/reactstrap": "^8.7.2", 26 | "antd": "^4.16.1", 27 | "babel-loader": "^8.2.2", 28 | "bootstrap": "^5.0.1", 29 | "copy-webpack-plugin": "9.0.0", 30 | "core-js": "^3.14.0", 31 | "css-loader": "^5.2.6", 32 | "file-loader": "^6.2.0", 33 | "gh-pages": "3.2.0", 34 | "html-webpack-plugin": "^5.3.1", 35 | "lerna": "^4.0.0", 36 | "less": "^4.1.1", 37 | "less-loader": "^9.0.0", 38 | "mini-css-extract-plugin": "^1.6.0", 39 | "react": "^17.0.2", 40 | "react-bootstrap": "^1.6.0", 41 | "react-dom": "^17.0.2", 42 | "reactstrap": "^8.9.0", 43 | "rollup": "^2.50.5", 44 | "rollup-plugin-babel": "^4.4.0", 45 | "rollup-plugin-commonjs": "^10.1.0", 46 | "rollup-plugin-json": "^4.0.0", 47 | "rollup-plugin-local-resolve": "^1.0.7", 48 | "rollup-plugin-node-resolve": "^5.2.0", 49 | "rollup-plugin-peer-deps-external": "^2.2.4", 50 | "rollup-plugin-typescript2": "^0.30.0", 51 | "semantic-ui-css": "^2.4.1", 52 | "semantic-ui-react": "^2.0.3", 53 | "style-loader": "^2.0.0", 54 | "ts-loader": "^9.2.2", 55 | "typescript": "^4.3.2", 56 | "webln": "^0.2.2", 57 | "webpack": "^5.38.1", 58 | "webpack-cli": "^4.7.0", 59 | "webpack-dev-server": "^3.11.2" 60 | }, 61 | "dependencies": { 62 | "react-webln-fallback-antd": "file:packages/react-webln-fallback-antd", 63 | "react-webln-fallback-bootstrap": "file:packages/react-webln-fallback-bootstrap", 64 | "react-webln-fallback-core": "file:packages/react-webln-fallback-core", 65 | "react-webln-fallback-demo": "file:demo", 66 | "react-webln-fallback-material-ui": "file:packages/react-webln-fallback-material-ui", 67 | "react-webln-fallback-reactstrap": "file:packages/react-webln-fallback-reactstrap", 68 | "react-webln-fallback-semantic-ui": "file:packages/react-webln-fallback-semantic-ui" 69 | }, 70 | "name": "react-webln-fallback" 71 | } 72 | -------------------------------------------------------------------------------- /packages/react-webln-fallback-antd/README.md: -------------------------------------------------------------------------------- 1 | # React WebLN Fallback: Ant Design 2 | 3 | * Full documentation here: https://github.com/joule-labs/react-webln-fallback 4 | * Demo available here: https://react-fallback.webln.dev/antd -------------------------------------------------------------------------------- /packages/react-webln-fallback-antd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-webln-fallback-antd", 3 | "version": "0.2.1", 4 | "main": "cjs/index.js", 5 | "module": "esm/index.js", 6 | "unpkg": "umd/react-webln-fallback.min.js", 7 | "repository": "https://github.com/joule-labs/react-webln-fallback", 8 | "license": "MIT", 9 | "files": [ 10 | "cjs", 11 | "esm", 12 | "umd" 13 | ], 14 | "scripts": { 15 | "dev": "rollup -c -w", 16 | "build": "rollup -c && NODE_ENV=development webpack && NODE_ENV=production webpack", 17 | "prepublishOnly": "yarn build" 18 | }, 19 | "dependencies": { 20 | "qrcode.react": "0.9.3", 21 | "react-webln-fallback-core": "file:../react-webln-fallback-core" 22 | }, 23 | "peerDependencies": { 24 | "antd": "^4.0.0", 25 | "react": ">=16", 26 | "react-dom": ">=16", 27 | "webln": "^0.2.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/react-webln-fallback-antd/rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from 'rollup-plugin-typescript2'; 2 | import peerDepsExternal from 'rollup-plugin-peer-deps-external'; 3 | import localResolve from 'rollup-plugin-local-resolve'; 4 | import nodeResolve from 'rollup-plugin-node-resolve'; 5 | import commonjs from 'rollup-plugin-commonjs'; 6 | import json from 'rollup-plugin-json'; 7 | import pkg from './package.json'; 8 | 9 | export default [{ 10 | // CommonJS and ESModule 11 | input: 'src/index.tsx', 12 | output: [{ 13 | file: pkg.main, 14 | format: 'cjs', 15 | name: 'React WebLN Fallback', 16 | }, { 17 | file: pkg.module, 18 | format: 'es', 19 | }], 20 | plugins: [ 21 | typescript(), 22 | peerDepsExternal(), 23 | localResolve(), 24 | nodeResolve(), 25 | commonjs(), 26 | json(), 27 | ], 28 | }]; 29 | -------------------------------------------------------------------------------- /packages/react-webln-fallback-antd/src/CLIHelp.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Input from 'antd/lib/input'; 3 | import TextArea from 'antd/lib/input/TextArea'; 4 | import Radio, { RadioChangeEvent } from 'antd/lib/radio'; 5 | import { 6 | WebLNMethod, 7 | NodeType, 8 | MethodComponentProps, 9 | nodeInfo, 10 | getCliCommand, 11 | } from 'react-webln-fallback-core'; 12 | 13 | interface Props { 14 | method: WebLNMethod; 15 | args: any; 16 | t: MethodComponentProps['t']; 17 | } 18 | 19 | interface State { 20 | nodeType: NodeType; 21 | } 22 | 23 | export default class CLIHelp extends React.PureComponent { 24 | state: State = { 25 | nodeType: NodeType.LND, 26 | }; 27 | 28 | render() { 29 | const { t } = this.props; 30 | const { nodeType } = this.state; 31 | 32 | return ( 33 |
34 |
35 | 36 | {t('react-webln-fallback.cli.prefix')} 37 | 38 | 43 | {Object.entries(nodeInfo).map(([key, info]) => ( 44 | 45 | {info.name} 46 | 47 | ))} 48 | 49 |
50 | {this.renderCommandInput()} 51 |
52 | ); 53 | } 54 | 55 | private handleTypeChange = (ev: RadioChangeEvent) => { 56 | this.setState({ nodeType: ev.target.value }); 57 | }; 58 | 59 | private renderCommandInput = () => { 60 | const { method, args, t } = this.props; 61 | const { nodeType } = this.state; 62 | const cmd = getCliCommand(nodeType, method, args); 63 | if (cmd) { 64 | if (cmd.includes('\n')) { 65 | return