chore: πŸ”§ remove client (#111)

* chore: πŸ”§ remove client

* chore: πŸ”§ change cookie name

* chore: πŸ”§ remove auth param

* chore: πŸ”§ remove auth components

* chore: πŸ”§ add getaccount query

* fix: πŸ› tests

* chore: πŸ”§ get account

* chore: πŸ”§ status check

* chore: πŸ”§ remove log

* chore: πŸ”§ update apollo client

* refactor: ♻️ server side props

* chore: πŸ”§ ssr queries

* chore: πŸ”§ more ssr queries

* chore: πŸ”§ type check

* chore: πŸ”§ increase nodeinfo limit

Co-authored-by: apotdevin <apotdevincab@gmail.com>
This commit is contained in:
Anthony Potdevin 2020-08-03 16:31:20 +02:00 committed by Anthony Potdevin
parent bc4415cde7
commit 581185e6b0
No known key found for this signature in database
GPG Key ID: 4403F1DFBE779457
274 changed files with 2811 additions and 5797 deletions

View File

@ -1,36 +1,29 @@
/* eslint @typescript-eslint/no-var-requires: 0 */
import * as React from 'react';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { IncomingMessage, ServerResponse } from 'http';
import { useMemo } from 'react';
import {
ApolloClient,
InMemoryCache,
IntrospectionFragmentMatcher,
NormalizedCacheObject,
} from 'apollo-cache-inmemory';
} from '@apollo/client';
import getConfig from 'next/config';
import introspectionQueryResultData from 'src/graphql/fragmentTypes.json';
import { SchemaLink } from 'apollo-link-schema';
import { NextPage } from 'next';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
let globalApolloClient: ReturnType<typeof createApolloClient> | null = null;
const { publicRuntimeConfig } = getConfig();
const { apiUrl: uri } = publicRuntimeConfig;
type Context = SchemaLink.ResolverContextFunction | Record<string, any>;
let apolloClient: ReturnType<typeof createApolloClient> | null = null;
function createIsomorphLink(ctx: Context) {
function createIsomorphLink(req?: IncomingMessage, res?: ServerResponse) {
if (typeof window === 'undefined') {
const schema = require('server/schema');
return new SchemaLink({ schema, context: ctx });
const { SchemaLink } = require('@apollo/client/link/schema');
const { schema } = require('server/schema');
const { getContext } = require('server/schema/context');
return new SchemaLink({
schema,
context: req && res ? getContext(req, res) : {},
});
} else {
const { HttpLink } = require('apollo-link-http');
const { HttpLink } = require('@apollo/client/link/http');
return new HttpLink({
uri,
credentials: 'same-origin',
@ -38,147 +31,38 @@ function createIsomorphLink(ctx: Context) {
}
}
/**
* Creates and configures the ApolloClient
*/
function createApolloClient(
ctx: Context = {},
initialState: NormalizedCacheObject = {}
) {
const ssrMode = typeof window === 'undefined';
const cache = new InMemoryCache({ fragmentMatcher }).restore(initialState);
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
function createApolloClient(req?: IncomingMessage, res?: ServerResponse) {
return new ApolloClient({
ssrMode,
link: createIsomorphLink(ctx),
cache,
credentials: 'same-origin',
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(req, res),
cache: new InMemoryCache({
possibleTypes: { Transaction: ['InvoiceType', 'PaymentType'] },
}),
});
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
*/
function initApolloClient(ctx?: Context, initialState?: NormalizedCacheObject) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(ctx, initialState);
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(ctx, initialState);
}
return globalApolloClient;
}
interface WithApolloProps {
apolloClient?: ApolloClient<NormalizedCacheObject>;
apolloState?: NormalizedCacheObject;
}
interface WithApolloOptions {
ssr?: boolean;
}
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
*/
export function withApollo(
PageComponent: NextPage,
{ ssr }: WithApolloOptions = { ssr: true }
export function initializeApollo(
initialState: NormalizedCacheObject | null = null,
req?: IncomingMessage,
res?: ServerResponse
) {
const WithApollo: NextPage<WithApolloProps> = ({
apolloClient,
apolloState,
...pageProps
}) => {
const client = apolloClient || initApolloClient(undefined, apolloState);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
const _apolloClient = apolloClient ?? createApolloClient(req, res);
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName =
PageComponent.displayName || PageComponent.name || 'Component';
if (displayName === 'App') {
console.warn('This withApollo HOC only works with PageComponents.');
}
WithApollo.displayName = `withApollo(${displayName})`;
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx): Promise<WithApolloProps> => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = initApolloClient({
res: ctx.res,
req: ctx.req,
});
(ctx as any).apolloClient = apolloClient;
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps as WithApolloProps;
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr');
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
return _apolloClient;
}
export function useApollo(initialState: NormalizedCacheObject | null) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}

View File

@ -28,7 +28,6 @@ module.exports = withBundleAnalyzer({
fetchFees: process.env.FETCH_FEES === 'false' ? false : true,
hodlhodl: process.env.HODL_HODL === 'false' ? false : true,
disableLinks: process.env.DISABLE_LINKS === 'true' ? true : false,
noClient: process.env.NO_CLIENT_ACCOUNTS === 'true' ? true : false,
noVersionCheck: process.env.NO_VERSION_CHECK === 'true' ? true : false,
},
});

184
package-lock.json generated
View File

@ -143,6 +143,56 @@
"cross-fetch": "3.0.4"
}
},
"@apollo/client": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.1.1.tgz",
"integrity": "sha512-c5DxrU81p0B5BsyBXm+5uPJqLCX2epnBsd87PXfRwzDLbp/NiqnWp6a6c5vT5EV2LwJuCq1movmKthoy0gFb0w==",
"requires": {
"@types/zen-observable": "^0.8.0",
"@wry/context": "^0.5.2",
"@wry/equality": "^0.2.0",
"fast-json-stable-stringify": "^2.0.0",
"graphql-tag": "^2.11.0",
"hoist-non-react-statics": "^3.3.2",
"optimism": "^0.12.1",
"prop-types": "^15.7.2",
"symbol-observable": "^1.2.0",
"ts-invariant": "^0.4.4",
"tslib": "^1.10.0",
"zen-observable": "^0.8.14"
},
"dependencies": {
"@wry/context": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz",
"integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==",
"requires": {
"tslib": "^1.9.3"
}
},
"@wry/equality": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.2.0.tgz",
"integrity": "sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==",
"requires": {
"tslib": "^1.9.3"
}
},
"graphql-tag": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz",
"integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA=="
},
"optimism": {
"version": "0.12.1",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.12.1.tgz",
"integrity": "sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==",
"requires": {
"@wry/context": "^0.5.2"
}
}
}
},
"@apollo/protobufjs": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz",
@ -170,36 +220,6 @@
}
}
},
"@apollo/react-common": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@apollo/react-common/-/react-common-3.1.4.tgz",
"integrity": "sha512-X5Kyro73bthWSCBJUC5XYQqMnG0dLWuDZmVkzog9dynovhfiVCV4kPSdgSIkqnb++cwCzOVuQ4rDKVwo2XRzQA==",
"requires": {
"ts-invariant": "^0.4.4",
"tslib": "^1.10.0"
}
},
"@apollo/react-hooks": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@apollo/react-hooks/-/react-hooks-3.1.5.tgz",
"integrity": "sha512-y0CJ393DLxIIkksRup4nt+vSjxalbZBXnnXxYbviq/woj+zKa431zy0yT4LqyRKpFy9ahMIwxBnBwfwIoupqLQ==",
"requires": {
"@apollo/react-common": "^3.1.4",
"@wry/equality": "^0.1.9",
"ts-invariant": "^0.4.4",
"tslib": "^1.10.0"
}
},
"@apollo/react-ssr": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@apollo/react-ssr/-/react-ssr-3.1.5.tgz",
"integrity": "sha512-wuLPkKlctNn3u8EU8rlECyktpOUCeekFfb0KhIKknpGY6Lza2Qu0bThx7D9MIbVEzhKadNNrzLcpk0Y8/5UuWg==",
"requires": {
"@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^3.1.5",
"tslib": "^1.10.0"
}
},
"@apollographql/apollo-tools": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.4.8.tgz",
@ -4834,12 +4854,6 @@
"@types/express": "*"
}
},
"@types/crypto-js": {
"version": "3.1.47",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-3.1.47.tgz",
"integrity": "sha512-eI6gvpcGHLk3dAuHYnRCAjX+41gMv1nz/VP55wAe5HtmAKDOoPSfr3f6vkMc08ov1S0NsjvUBxDtHHxqQY1LGA==",
"dev": true
},
"@types/eslint-visitor-keys": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
@ -5303,12 +5317,6 @@
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg=="
},
"@types/zxcvbn": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.0.tgz",
"integrity": "sha512-GQLOT+SN20a+AI51y3fAimhyTF4Y0RG+YP3gf91OibIZ7CJmPFgoZi+ZR5a+vRbS01LbQosITWum4ATmJ1Z6Pg==",
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.0.tgz",
@ -5583,15 +5591,6 @@
"@xtuc/long": "4.2.2"
}
},
"@wry/context": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.4.4.tgz",
"integrity": "sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==",
"requires": {
"@types/node": ">=6",
"tslib": "^1.9.3"
}
},
"@wry/equality": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz",
@ -5897,15 +5896,6 @@
}
}
},
"apollo-cache": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.5.tgz",
"integrity": "sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA==",
"requires": {
"apollo-utilities": "^1.3.4",
"tslib": "^1.10.0"
}
},
"apollo-cache-control": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.1.tgz",
@ -5916,33 +5906,6 @@
"apollo-server-plugin-base": "^0.9.1"
}
},
"apollo-cache-inmemory": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz",
"integrity": "sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A==",
"requires": {
"apollo-cache": "^1.3.5",
"apollo-utilities": "^1.3.4",
"optimism": "^0.10.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
}
},
"apollo-client": {
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.10.tgz",
"integrity": "sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA==",
"requires": {
"@types/zen-observable": "^0.8.0",
"apollo-cache": "1.3.5",
"apollo-link": "^1.0.0",
"apollo-utilities": "1.3.4",
"symbol-observable": "^1.0.2",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0",
"zen-observable": "^0.8.0"
}
},
"apollo-datasource": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.2.tgz",
@ -6010,35 +5973,6 @@
"zen-observable-ts": "^0.8.21"
}
},
"apollo-link-http": {
"version": "1.5.17",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.17.tgz",
"integrity": "sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg==",
"requires": {
"apollo-link": "^1.2.14",
"apollo-link-http-common": "^0.2.16",
"tslib": "^1.9.3"
}
},
"apollo-link-http-common": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz",
"integrity": "sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg==",
"requires": {
"apollo-link": "^1.2.14",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3"
}
},
"apollo-link-schema": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/apollo-link-schema/-/apollo-link-schema-1.2.5.tgz",
"integrity": "sha512-7XUS8fOsObJt9rzp8CUuZ/a9TNUBoChWwEDmdVmYxTlzgGcyUXxkLXkMS9CHUb0cx04jiiWjWQc41C4iakSmzA==",
"requires": {
"apollo-link": "^1.2.14",
"tslib": "^1.9.3"
}
},
"apollo-server": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-2.15.1.tgz",
@ -7603,11 +7537,6 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"base64url": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
"integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A=="
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
@ -20530,14 +20459,6 @@
"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
"dev": true
},
"optimism": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.10.3.tgz",
"integrity": "sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw==",
"requires": {
"@wry/context": "^0.4.0"
}
},
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@ -26674,11 +26595,6 @@
"tslib": "^1.9.3",
"zen-observable": "^0.8.0"
}
},
"zxcvbn": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz",
"integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA="
}
}
}

View File

@ -5,22 +5,22 @@
"main": "index.js",
"scripts": {
"bs": "yarn build && yarn start",
"dev": "cross-env NODE_OPTIONS='--insecure-http-parser' next",
"dev": "next",
"dev:compatible": "next",
"build": "next build",
"start": "cross-env NODE_OPTIONS='--insecure-http-parser' next start",
"start:two": "cross-env NODE_OPTIONS='--insecure-http-parser' next start -p 3001",
"start": "next start",
"start:two": "next start -p 3001",
"start:compatible": "next start",
"start:compatible:two": "next start -p 3001",
"start:cookie": "sh ./scripts/initCookie.sh",
"lint": "eslint */**/*.{js,ts,tsx} --fix",
"prettier": "prettier --write **/*.{ts,tsx,js,css,html}",
"lint": "eslint . --ext ts --ext tsx --ext js",
"format": "prettier --write \"**/*.{js,ts,tsx}\"",
"release": "standard-version --sign",
"release:push": "standard-version --sign && git push --follow-tags origin master",
"release:test": "standard-version --sign --dry-run",
"release:minor": "standard-version --sign --release-as minor && git push --follow-tags origin master",
"analyze": "cross-env ANALYZE=true next build",
"generate": "graphql-codegen --config codegen.yml && yarn lint",
"generate": "graphql-codegen --config codegen.yml && yarn format",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
@ -37,25 +37,15 @@
"author": "",
"license": "MIT",
"dependencies": {
"@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^3.1.5",
"@apollo/react-ssr": "^3.1.5",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"apollo-link-http": "^1.5.17",
"apollo-link-schema": "^1.2.5",
"@apollo/client": "^3.1.1",
"apollo-server-micro": "^2.15.1",
"apollo-utilities": "^1.3.4",
"balanceofsatoshis": "^5.40.2",
"base64url": "^3.0.1",
"bcryptjs": "^2.4.3",
"cookie": "^0.4.1",
"crypto-js": "^4.0.0",
"date-fns": "^2.14.0",
"graphql": "^15.3.0",
"graphql-iso-date": "^3.6.1",
"graphql-rate-limit": "^2.0.1",
"graphql-tag": "^2.10.4",
"intersection-observer": "^0.11.0",
"js-cookie": "^2.2.1",
"js-yaml": "^3.14.0",
@ -84,8 +74,7 @@
"underscore": "^1.10.2",
"uuid": "^8.2.0",
"victory": "^35.0.3",
"winston": "^3.3.3",
"zxcvbn": "^4.4.2"
"winston": "^3.3.3"
},
"devDependencies": {
"@babel/core": "^7.10.4",
@ -104,7 +93,6 @@
"@testing-library/react": "^10.4.5",
"@types/bcryptjs": "^2.4.2",
"@types/cookie": "^0.4.0",
"@types/crypto-js": "^3.1.47",
"@types/graphql-iso-date": "^3.4.0",
"@types/js-cookie": "^2.2.6",
"@types/js-yaml": "^3.12.5",
@ -123,7 +111,6 @@
"@types/styled-theming": "^2.2.4",
"@types/underscore": "^1.10.9",
"@types/uuid": "^8.0.0",
"@types/zxcvbn": "^4.4.0",
"@typescript-eslint/eslint-plugin": "^3.6.0",
"@typescript-eslint/parser": "^3.6.0",
"apollo-server": "^2.15.1",
@ -143,7 +130,6 @@
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^4.0.7",
"fast-diff": "^1.2.0",
"husky": "^4.2.5",
"jest": "^26.1.0",
"jest-fetch-mock": "^3.0.3",

View File

@ -4,8 +4,9 @@ import { ModalProvider, BaseModalBackground } from 'styled-react-modal';
import { useRouter } from 'next/router';
import Head from 'next/head';
import { StyledToastContainer } from 'src/components/toastContainer/ToastContainer';
import { NextPage } from 'next';
import { AppProps } from 'next/app';
import { ApolloProvider } from '@apollo/client';
import { useApollo } from 'config/client';
import { ContextProvider } from '../src/context/ContextProvider';
import { useConfigState, ConfigProvider } from '../src/context/ConfigContext';
import { GlobalStyles } from '../src/styles/GlobalStyle';
@ -13,7 +14,6 @@ import { Header } from '../src/layouts/header/Header';
import { Footer } from '../src/layouts/footer/Footer';
import 'react-toastify/dist/ReactToastify.min.css';
import { PageWrapper, HeaderBodyWrapper } from '../src/layouts/Layout.styled';
import { parseCookies } from '../src/utils/cookies';
import 'react-circular-progressbar/dist/styles.css';
const Wrapper: React.FC = ({ children }) => {
@ -38,46 +38,21 @@ const Wrapper: React.FC = ({ children }) => {
);
};
type InitialProps = { initialConfig: string };
type MyAppProps = InitialProps & AppProps;
const App: NextPage<MyAppProps, InitialProps> = ({
Component,
pageProps,
initialConfig,
}) => (
<>
<Head>
<title>ThunderHub - Lightning Node Manager</title>
</Head>
<ConfigProvider initialConfig={initialConfig}>
<ContextProvider>
<Wrapper>
<Component {...pageProps} />
</Wrapper>
</ContextProvider>
</ConfigProvider>
<StyledToastContainer />
</>
);
/*
* Props should be NextPageContext but something wierd
* happens and the context object received is not this
* type.
*/
App.getInitialProps = async ({ ctx }: any) => {
const cookies = parseCookies(ctx?.req);
if (!cookies?.theme) {
return { initialConfig: 'dark' };
}
try {
const initialConfig = cookies.theme || 'dark';
return { initialConfig };
} catch (error) {
return { initialConfig: 'dark' };
}
};
export default App;
export default function App({ Component, pageProps }: AppProps) {
const apolloClient = useApollo(pageProps.initialApolloState);
return (
<ApolloProvider client={apolloClient}>
<Head>
<title>ThunderHub - Lightning Node Manager</title>
</Head>
<ConfigProvider initialConfig={pageProps.initialConfig}>
<ContextProvider>
<Wrapper>
<Component {...pageProps} />
</Wrapper>
</ContextProvider>
</ConfigProvider>
<StyledToastContainer />
</ApolloProvider>
);
}

View File

@ -1,98 +1,24 @@
import crypto from 'crypto';
import { ApolloServer } from 'apollo-server-micro';
import { getIp } from 'server/helpers/helpers';
import getConfig from 'next/config';
import jwt from 'jsonwebtoken';
import { logger } from 'server/helpers/logger';
import {
readMacaroons,
readFile,
readCookie,
getAccounts,
} from 'server/helpers/fileHelpers';
import { ContextType, SSOType } from 'server/types/apiTypes';
import cookie from 'cookie';
import schema from 'server/schema';
import { readCookie } from 'server/helpers/fileHelpers';
import { schema } from 'server/schema';
import { getContext } from 'server/schema/context';
const { publicRuntimeConfig, serverRuntimeConfig } = getConfig();
const { apiBaseUrl, nodeEnv } = publicRuntimeConfig;
const {
cookiePath,
macaroonPath,
lnCertPath,
lnServerUrl,
accountConfigPath,
} = serverRuntimeConfig;
const { cookiePath } = serverRuntimeConfig;
const secret =
export const secret =
nodeEnv === 'development'
? '123456789'
: crypto.randomBytes(64).toString('hex');
const ssoMacaroon = readMacaroons(macaroonPath);
const ssoCert = readFile(lnCertPath);
const accountConfig = getAccounts(accountConfigPath);
let ssoAccount: SSOType | null = null;
if (ssoMacaroon && lnServerUrl) {
ssoAccount = {
macaroon: ssoMacaroon,
host: lnServerUrl,
cert: ssoCert,
};
}
readCookie(cookiePath);
const apolloServer = new ApolloServer({
schema,
context: ({ req, res }) => {
const ip = getIp(req);
const { AccountAuth, SSOAuth } = cookie.parse(req.headers.cookie ?? '');
let ssoVerified = false;
if (SSOAuth) {
logger.silly('SSOAuth cookie found in request');
if (nodeEnv === 'development') {
ssoVerified = true;
}
try {
jwt.verify(SSOAuth, secret);
ssoVerified = true;
} catch (error) {
logger.silly('SSO authentication cookie failed');
}
}
let account = '';
if (AccountAuth) {
logger.silly('AccountAuth cookie found in request');
try {
const cookieAccount = jwt.verify(AccountAuth, secret);
if (typeof cookieAccount === 'object') {
account = (cookieAccount as { id?: string })['id'] ?? '';
} else {
account = cookieAccount;
}
} catch (error) {
logger.silly('Account authentication cookie failed');
}
}
const context: ContextType = {
ip,
secret,
ssoVerified,
account,
sso: ssoVerified ? ssoAccount : null,
accounts: accountConfig,
res,
};
return context;
},
context: ({ req, res }) => getContext(req, res),
});
export const config = {

View File

@ -1,7 +1,9 @@
import React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_CHAIN_TRANSACTIONS } from 'src/graphql/queries/getChainTransactions';
import { GET_UTXOS } from 'src/graphql/queries/getUtxos';
import { ChainTransactions } from '../src/views/chain/transactions/ChainTransactions';
import { ChainUtxos } from '../src/views/chain/utxos/ChainUtxos';
@ -20,4 +22,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_CHAIN_TRANSACTIONS, GET_UTXOS]);
}

View File

@ -1,12 +1,13 @@
import React, { useState, useEffect } from 'react';
import { useAccountState } from 'src/context/AccountContext';
import { useGetChannelAmountInfoQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated';
import styled from 'styled-components';
import { Settings } from 'react-feather';
import { IconCursor } from 'src/views/channels/channels/Channel.style';
import { ChannelManage } from 'src/views/channels/channels/ChannelManage';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { NextPageContext } from 'next';
import { GET_CHANNELS } from 'src/graphql/queries/getChannels';
import { getProps } from 'src/utils/ssr';
import { Channels } from '../src/views/channels/channels/Channels';
import { PendingChannels } from '../src/views/channels/pendingChannels/PendingChannels';
import { ClosedChannels } from '../src/views/channels/closedChannels/ClosedChannels';
@ -41,12 +42,7 @@ const ChannelView = () => {
closed: 0,
});
const { auth } = useAccountState();
const { data } = useGetChannelAmountInfoQuery({
skip: !auth,
variables: { auth },
});
const { data } = useGetChannelAmountInfoQuery();
useEffect(() => {
if (data && data.getNodeInfo) {
@ -119,4 +115,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_CHANNELS]);
}

View File

@ -2,9 +2,11 @@ import * as React from 'react';
import styled from 'styled-components';
import { Users } from 'react-feather';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { ChatInit } from 'src/components/chat/ChatInit';
import { ChatFetcher } from 'src/components/chat/ChatFetcher';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_MESSAGES } from 'src/graphql/queries/getMessages';
import { useChatState } from '../src/context/ChatContext';
import { separateBySender, getSenders } from '../src/utils/chat';
import {
@ -126,4 +128,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_MESSAGES]);
}

View File

@ -1,15 +1,17 @@
import React, { useState } from 'react';
import { toast } from 'react-toastify';
import { ChevronRight, ChevronUp, ChevronDown } from 'react-feather';
import { useAccountState } from 'src/context/AccountContext';
import { useChannelFeesQuery } from 'src/graphql/queries/__generated__/getChannelFees.generated';
import { useUpdateFeesMutation } from 'src/graphql/mutations/__generated__/updateFees.generated';
import { InputWithDeco } from 'src/components/input/InputWithDeco';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import styled from 'styled-components';
import { useStatusState } from 'src/context/StatusContext';
import { ChannelFeeType } from 'src/graphql/types';
import { ColorButton } from 'src/components/buttons/colorButton/ColorButton';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { CHANNEL_FEES } from 'src/graphql/queries/getChannelFees';
import {
Card,
CardWithTitle,
@ -22,8 +24,6 @@ import {
import { getErrorContent } from '../src/utils/error';
import { LoadingCard } from '../src/components/loading/LoadingCard';
import { FeeCard } from '../src/views/fees/FeeCard';
import { SecureButton } from '../src/components/buttons/secureButton/SecureButton';
import { AdminSwitch } from '../src/components/adminSwitch/AdminSwitch';
const WithPointer = styled.div`
cursor: pointer;
@ -43,11 +43,7 @@ const FeesView = () => {
const [max, setMax] = useState(0);
const [min, setMin] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useChannelFeesQuery({
skip: !auth,
variables: { auth },
onError: error => toast.error(getErrorContent(error)),
});
@ -73,95 +69,101 @@ const FeesView = () => {
return (
<>
<AdminSwitch>
<Card>
<WithPointer>
<SingleLine onClick={() => setIsEdit(prev => !prev)}>
<Sub4Title>Update All Channel Details</Sub4Title>
{isEdit ? <ChevronUp /> : <ChevronDown />}
</SingleLine>
</WithPointer>
{isEdit && (
<>
<Separation />
<Card>
<WithPointer>
<SingleLine onClick={() => setIsEdit(prev => !prev)}>
<Sub4Title>Update All Channel Details</Sub4Title>
{isEdit ? <ChevronUp /> : <ChevronDown />}
</SingleLine>
</WithPointer>
{isEdit && (
<>
<Separation />
<InputWithDeco
title={'BaseFee'}
value={baseFee}
placeholder={'sats'}
amount={baseFee}
override={'sat'}
inputType={'number'}
inputCallback={value => setBaseFee(Number(value))}
/>
<InputWithDeco
title={'Fee Rate'}
value={feeRate}
placeholder={'ppm'}
amount={feeRate}
override={'ppm'}
inputType={'number'}
inputCallback={value => setFeeRate(Number(value))}
/>
<InputWithDeco
title={'CLTV Delta'}
value={cltv}
placeholder={'cltv delta'}
customAmount={cltv ? cltv.toString() : ''}
inputType={'number'}
inputCallback={value => setCLTV(Number(value))}
/>
{canMax && (
<InputWithDeco
title={'BaseFee'}
value={baseFee}
title={'Max HTLC'}
value={max}
placeholder={'sats'}
amount={baseFee}
amount={max}
override={'sat'}
inputType={'number'}
inputCallback={value => setBaseFee(Number(value))}
inputCallback={value => setMax(Number(value))}
/>
)}
{canMin && (
<InputWithDeco
title={'Fee Rate'}
value={feeRate}
placeholder={'ppm'}
amount={feeRate}
override={'ppm'}
title={'Min HTLC'}
value={min}
placeholder={'sats'}
amount={min}
override={'sat'}
inputType={'number'}
inputCallback={value => setFeeRate(Number(value))}
inputCallback={value => setMin(Number(value))}
/>
<InputWithDeco
title={'CLTV Delta'}
value={cltv}
placeholder={'cltv delta'}
customAmount={cltv ? cltv.toString() : ''}
inputType={'number'}
inputCallback={value => setCLTV(Number(value))}
/>
{canMax && (
<InputWithDeco
title={'Max HTLC'}
value={max}
placeholder={'sats'}
amount={max}
override={'sat'}
inputType={'number'}
inputCallback={value => setMax(Number(value))}
/>
)}
{canMin && (
<InputWithDeco
title={'Min HTLC'}
value={min}
placeholder={'sats'}
amount={min}
override={'sat'}
inputType={'number'}
inputCallback={value => setMin(Number(value))}
/>
)}
<RightAlign>
<SecureButton
callback={updateFees}
variables={{
...(baseFee !== 0 && { base_fee_tokens: baseFee }),
...(feeRate !== 0 && { fee_rate: feeRate }),
...(cltv !== 0 && { cltv_delta: cltv }),
...(max !== 0 &&
canMax && { max_htlc_mtokens: (max * 1000).toString() }),
...(min !== 0 &&
canMin && { min_htlc_mtokens: (min * 1000).toString() }),
}}
disabled={
baseFee === 0 &&
feeRate === 0 &&
cltv === 0 &&
max === 0 &&
min === 0
}
fullWidth={true}
withMargin={'16px 0 0'}
>
Update Fees
<ChevronRight size={18} />
</SecureButton>
</RightAlign>
</>
)}
</Card>
</AdminSwitch>
)}
<RightAlign>
<ColorButton
onClick={() =>
updateFees({
variables: {
...(baseFee !== 0 && { base_fee_tokens: baseFee }),
...(feeRate !== 0 && { fee_rate: feeRate }),
...(cltv !== 0 && { cltv_delta: cltv }),
...(max !== 0 &&
canMax && {
max_htlc_mtokens: (max * 1000).toString(),
}),
...(min !== 0 &&
canMin && {
min_htlc_mtokens: (min * 1000).toString(),
}),
},
})
}
disabled={
baseFee === 0 &&
feeRate === 0 &&
cltv === 0 &&
max === 0 &&
min === 0
}
fullWidth={true}
withMargin={'16px 0 0'}
>
Update Fees
<ChevronRight size={18} />
</ColorButton>
</RightAlign>
</>
)}
</Card>
<CardWithTitle>
<SubTitle>Channel Details</SubTitle>
<Card mobileCardPadding={'0'} mobileNoBackground={true}>
@ -186,4 +188,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [CHANNEL_FEES]);
}

View File

@ -1,10 +1,11 @@
import React, { useState } from 'react';
import { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { useGetForwardsQuery } from 'src/graphql/queries/__generated__/getForwards.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { ForwardType } from 'src/graphql/types';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_FORWARDS } from 'src/graphql/queries/getForwards';
import {
SubTitle,
Card,
@ -29,11 +30,8 @@ const ForwardsView = () => {
const [time, setTime] = useState('week');
const [indexOpen, setIndexOpen] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useGetForwardsQuery({
skip: !auth,
variables: { auth, time },
variables: { time },
onError: error => toast.error(getErrorContent(error)),
});
@ -89,4 +87,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_FORWARDS]);
}

View File

@ -1,7 +1,9 @@
import React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { Version } from 'src/components/version/Version';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_NODE_INFO } from 'src/graphql/queries/getNodeInfo';
import { NetworkInfo } from '../src/views/home/networkInfo/NetworkInfo';
import { AccountInfo } from '../src/views/home/account/AccountInfo';
import { QuickActions } from '../src/views/home/quickActions/QuickActions';
@ -9,14 +11,12 @@ import { FlowBox } from '../src/views/home/reports/flow';
import { ForwardBox } from '../src/views/home/reports/forwardReport';
import { LiquidReport } from '../src/views/home/reports/liquidReport/LiquidReport';
import { ConnectCard } from '../src/views/home/connect/Connect';
import { NodeBar } from '../src/components/nodeInfo/NodeBar';
const HomeView = () => {
return (
<>
<Version />
<AccountInfo />
<NodeBar />
<ConnectCard />
<QuickActions />
<FlowBox />
@ -33,4 +33,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_NODE_INFO]);
}

View File

@ -1,44 +1,22 @@
import * as React from 'react';
import { Spacer } from 'src/components/spacer/Spacer';
import { withApollo } from 'config/client';
import { ServerAccounts } from 'src/components/accounts/ServerAccounts';
import { useAccountState } from 'src/context/AccountContext';
import getConfig from 'next/config';
import { ThunderStorm } from 'src/views/homepage/HomePage.styled';
import { appendBasePath } from 'src/utils/basePath';
import { SessionLogin } from '../src/views/login/SessionLogin';
import { NextPageContext } from 'next';
import { GET_SERVER_ACCOUNTS } from 'src/graphql/queries/getServerAccounts';
import { getProps } from 'src/utils/ssr';
import { TopSection } from '../src/views/homepage/Top';
import { LoginBox } from '../src/views/homepage/LoginBox';
import { Accounts } from '../src/views/homepage/Accounts';
import { LoadingCard } from '../src/components/loading/LoadingCard';
import { Section } from '../src/components/section/Section';
const { publicRuntimeConfig } = getConfig();
const { noClient } = publicRuntimeConfig;
const ContextApp = () => {
const { finishedFetch } = useAccountState();
return (
<>
<ThunderStorm alt={''} src={appendBasePath('/static/thunderstorm.gif')} />
<TopSection />
{!finishedFetch && (
<Section color={'transparent'}>
<LoadingCard loadingHeight={'160px'} />
</Section>
)}
{finishedFetch && (
<>
<SessionLogin />
<Accounts />
{!noClient && <LoginBox />}
</>
)}
<Spacer />
</>
);
};
const ContextApp = () => (
<>
<ThunderStorm alt={''} src={appendBasePath('/static/thunderstorm.gif')} />
<TopSection />
<Accounts />
<Spacer />
</>
);
const Wrapped = () => (
<>
@ -47,4 +25,8 @@ const Wrapped = () => (
</>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_SERVER_ACCOUNTS]);
}

View File

@ -1,9 +1,10 @@
import React, { useState } from 'react';
import { useAccountState } from 'src/context/AccountContext';
import { useGetPeersQuery } from 'src/graphql/queries/__generated__/getPeers.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { PeerType } from 'src/graphql/types';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_PEERS } from 'src/graphql/queries/getPeers';
import {
CardWithTitle,
SubTitle,
@ -15,12 +16,8 @@ import { AddPeer } from '../src/views/peers/AddPeer';
const PeersView = () => {
const [indexOpen, setIndexOpen] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useGetPeersQuery({
skip: !auth,
variables: { auth },
});
const { loading, data } = useGetPeersQuery();
if (loading || !data?.getPeers) {
return <LoadingCard title={'Peers'} />;
@ -53,4 +50,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_PEERS]);
}

View File

@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { SimpleBalance } from 'src/views/balance/SimpleBalance';
import {
CardWithTitle,
@ -11,6 +10,9 @@ import {
} from 'src/components/generic/Styled';
import { Text } from 'src/components/typography/Styled';
import { AdvancedBalance } from 'src/views/balance/AdvancedBalance';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_CHANNELS } from 'src/graphql/queries/getChannels';
import { useStatusState } from '../src/context/StatusContext';
const BalanceView = () => {
@ -64,4 +66,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_CHANNELS]);
}

View File

@ -1,12 +1,12 @@
import React from 'react';
import styled from 'styled-components';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { SingleLine } from '../src/components/generic/Styled';
import { InterfaceSettings } from '../src/views/settings/Interface';
import { AccountSettings } from '../src/views/settings/Account';
import { DangerView } from '../src/views/settings/Danger';
import { CurrentSettings } from '../src/views/settings/Current';
import { ChatSettings } from '../src/views/settings/Chat';
import { PrivacySettings } from '../src/views/settings/Privacy';
@ -25,7 +25,6 @@ const SettingsView = () => {
<InterfaceSettings />
<PrivacySettings />
<ChatSettings />
<CurrentSettings />
<AccountSettings />
<DangerView />
</>
@ -38,4 +37,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View File

@ -1,12 +1,16 @@
import React from 'react';
import styled from 'styled-components';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { VolumeStats } from 'src/views/stats/FlowStats';
import { TimeStats } from 'src/views/stats/TimeStats';
import { FeeStats } from 'src/views/stats/FeeStats';
import { StatResume } from 'src/views/stats/StatResume';
import { StatsProvider } from 'src/views/stats/context';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_FEE_HEALTH } from 'src/graphql/queries/getFeeHealth';
import { GET_VOLUME_HEALTH } from 'src/graphql/queries/getVolumeHealth';
import { GET_TIME_HEALTH } from 'src/graphql/queries/getTimeHealth';
import { SingleLine } from '../src/components/generic/Styled';
export const ButtonRow = styled.div`
@ -37,4 +41,12 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [
GET_FEE_HEALTH,
GET_VOLUME_HEALTH,
GET_TIME_HEALTH,
]);
}

View File

@ -1,8 +1,10 @@
import React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { Bakery } from 'src/views/tools/bakery/Bakery';
import { Accounting } from 'src/views/tools/accounting/Accounting';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_WALLET_INFO } from 'src/graphql/queries/getWalletInfo';
import { BackupsView } from '../src/views/tools/backups/Backups';
import { MessagesView } from '../src/views/tools/messages/Messages';
import { WalletVersion } from '../src/views/tools/WalletVersion';
@ -23,4 +25,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_WALLET_INFO]);
}

View File

@ -3,9 +3,10 @@ import { toast } from 'react-toastify';
import { useRouter } from 'next/router';
import { useGetOffersQuery } from 'src/graphql/hodlhodl/__generated__/query.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import getConfig from 'next/config';
import { HodlOfferType } from 'src/graphql/types';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import {
CardWithTitle,
SubTitle,
@ -169,4 +170,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context);
}

View File

@ -1,13 +1,16 @@
import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { InvoiceCard } from 'src/views/transactions/InvoiceCard';
import {
useGetResumeQuery,
GetResumeQuery,
} from 'src/graphql/queries/__generated__/getResume.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_RESUME } from 'src/graphql/queries/getResume';
import { GET_IN_OUT } from 'src/graphql/queries/getInOut';
import {
Card,
CardWithTitle,
@ -23,11 +26,8 @@ const TransactionsView = () => {
const [indexOpen, setIndexOpen] = useState(0);
const [token, setToken] = useState('');
const { auth } = useAccountState();
const { loading, data, fetchMore } = useGetResumeQuery({
skip: !auth,
variables: { auth, token: '' },
variables: { token: '' },
onError: error => toast.error(getErrorContent(error)),
});
@ -82,7 +82,7 @@ const TransactionsView = () => {
withMargin={'16px 0 0'}
onClick={() => {
fetchMore({
variables: { auth, token },
variables: { token },
updateQuery: (
prev,
{ fetchMoreResult }: { fetchMoreResult?: GetResumeQuery }
@ -123,4 +123,11 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [
GET_RESUME,
{ document: GET_IN_OUT, variables: { time: 'month' } },
]);
}

60
server/helpers/auth.ts Normal file
View File

@ -0,0 +1,60 @@
import { authenticatedLndGrpc } from 'ln-service';
import { SSOType, AccountType } from 'server/types/apiTypes';
import { LndObject } from 'server/types/ln-service.types';
import { v5 as uuidv5 } from 'uuid';
import { logger } from './logger';
type LndAuthType = {
cert: string | null;
macaroon: string;
socket: string;
};
const THUNDERHUB_NAMESPACE = '00000000-0000-0000-0000-000000000000';
export const getUUID = (text: string): string =>
uuidv5(text, THUNDERHUB_NAMESPACE);
export const getAuthLnd = (
id: string,
sso: SSOType | null,
accounts: AccountType[]
): LndObject | null => {
if (!id) {
logger.silly('Account not authenticated');
return null;
}
let authDetails: LndAuthType | null = null;
if (id === 'test') {
authDetails = {
socket: process.env.TEST_HOST || '',
macaroon: process.env.TEST_MACAROON || '',
cert: process.env.TEST_CERT || '',
};
}
if (id === 'sso' && !sso) {
logger.debug('SSO Account is not verified');
throw new Error('AccountNotAuthenticated');
}
if (id === 'sso' && sso) {
authDetails = sso;
}
if (!authDetails) {
const verifiedAccount = accounts.find(a => a.id === id) || null;
if (!verifiedAccount) {
logger.debug('Account not found in config file');
throw new Error('AccountNotAuthenticated');
}
authDetails = verifiedAccount;
}
const { lnd } = authenticatedLndGrpc(authDetails);
return lnd;
};

View File

@ -4,9 +4,9 @@ import path from 'path';
import os from 'os';
import { logger } from 'server/helpers/logger';
import yaml from 'js-yaml';
import { getUUID } from 'src/utils/auth';
import bcrypt from 'bcryptjs';
import { AccountType as ContextAccountType } from 'server/types/apiTypes';
import { getUUID } from './auth';
type EncodingType = 'hex' | 'utf-8';
type BitcoinNetwork = 'mainnet' | 'regtest' | 'testnet';
@ -18,14 +18,14 @@ type AccountType = {
network?: BitcoinNetwork;
macaroonPath?: string;
certificatePath?: string;
password?: string;
password?: string | null;
macaroon?: string;
certificate?: string;
};
type ParsedAccount = {
name: string;
id: string;
host: string;
socket: string;
macaroon: string;
cert: string;
password: string;
@ -117,7 +117,7 @@ export const hashPasswords = (
const cloned = { ...config };
let hashedMasterPassword = config?.masterPassword || '';
let hashedMasterPassword = config?.masterPassword;
if (
hashedMasterPassword &&
@ -283,7 +283,7 @@ export const getParsedAccount = (
return {
name: name || '',
id,
host: serverUrl || '',
socket: serverUrl || '',
macaroon,
cert: cert || '',
password: password || masterPassword || '',

View File

@ -15,7 +15,7 @@ const errorNode = {
export const getNodeFromChannel = async (
id: string,
publicKey: string,
lnd: LndObject
lnd: LndObject | null
) => {
const [channelInfo, channelError] = await toWithError(
getChannel({

View File

@ -1,23 +1,7 @@
import { authenticatedLndGrpc } from 'ln-service';
import getConfig from 'next/config';
import {
SSO_ACCOUNT,
SERVER_ACCOUNT,
AuthType,
CLIENT_ACCOUNT,
} from 'src/context/AccountContext';
import { ContextType } from 'server/types/apiTypes';
import { logger } from './logger';
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig() || {};
const { serverRuntimeConfig } = getConfig() || {};
const { nodeEnv } = serverRuntimeConfig || {};
const { noClient } = publicRuntimeConfig || {};
type LndAuthType = {
cert: string | null;
macaroon: string;
host: string;
};
export const getIp = (req: any) => {
if (!req || !req.headers) {
@ -31,78 +15,6 @@ export const getIp = (req: any) => {
return ip;
};
export const getCorrectAuth = (
auth: AuthType,
context: ContextType
): LndAuthType => {
if (auth.type === 'test' && nodeEnv !== 'production') {
return {
host: process.env.TEST_HOST || '',
macaroon: process.env.TEST_MACAROON || '',
cert: process.env.TEST_CERT || '',
};
}
if (auth.type === SERVER_ACCOUNT) {
const { account, accounts } = context;
if (!account) {
logger.debug('Account not available in request');
throw new Error('AccountNotAuthenticated');
}
if (account !== auth.id) {
logger.debug(
`Account (${account}) in cookie different to requested account (${auth.id})`
);
throw new Error('AccountNotAuthenticated');
}
const verifiedAccount = accounts.find(a => a.id === account) || null;
if (!verifiedAccount) {
logger.debug('Account not found in config file');
throw new Error('AccountNotAuthenticated');
}
return verifiedAccount;
}
if (auth.type === SSO_ACCOUNT) {
if (!context.ssoVerified || !context.sso) {
logger.debug('SSO Account is not verified');
throw new Error('AccountNotAuthenticated');
}
return { ...context.sso };
}
if (auth.type === CLIENT_ACCOUNT && !noClient) {
const { host, macaroon, cert } = auth;
return { host, macaroon, cert };
}
if (auth.type === CLIENT_ACCOUNT && noClient) {
logger.info(`Client accounts are disabled from the server.`);
throw new Error('AccountTypeDoesNotExist');
}
logger.info(`No authentication for account type '${auth.type}' found`);
throw new Error('AccountTypeDoesNotExist');
};
export const getAuthLnd = (auth: LndAuthType) => {
const cert = auth.cert || '';
const macaroon = auth.macaroon || '';
const socket = auth.host || '';
const params = {
macaroon,
socket,
...(cert !== '' ? { cert } : {}),
};
const { lnd } = authenticatedLndGrpc(params);
return lnd;
};
export const getLnd = (auth: AuthType, context: ContextType) =>
getAuthLnd(getCorrectAuth(auth, context));
export const getErrorMsg = (error: any[] | string): string => {
if (typeof error === 'string') {
return error;

View File

@ -10,6 +10,7 @@ interface RateConfigProps {
export const RateConfig: RateConfigProps = {
getMessages: { max: 10, window: '5s' },
nodeInfo: { max: 10, window: '5s' },
};
const rateLimiter = getGraphQLRateLimiter({

View File

@ -6,16 +6,10 @@ Object {
"getServerAccounts": Array [
Object {
"id": "accountID",
"loggedIn": true,
"loggedIn": false,
"name": "account",
"type": "server",
},
Object {
"id": "sso",
"loggedIn": true,
"name": "SSO Account",
"type": "sso",
},
],
},
"errors": undefined,
@ -34,7 +28,7 @@ Object {
"getServerAccounts": Array [
Object {
"id": "accountID",
"loggedIn": true,
"loggedIn": false,
"name": "account",
"type": "server",
},
@ -56,7 +50,7 @@ Object {
"getServerAccounts": Array [
Object {
"id": "accountID",
"loggedIn": true,
"loggedIn": false,
"name": "account",
"type": "server",
},

View File

@ -11,7 +11,6 @@ describe('Account Resolvers', () => {
const res = await query({
query: GET_SERVER_ACCOUNTS,
variables: { auth: { type: 'test' } },
});
expect(res.errors).toBe(undefined);
@ -22,7 +21,6 @@ describe('Account Resolvers', () => {
const res = await query({
query: GET_SERVER_ACCOUNTS,
variables: { auth: { type: 'test' } },
});
expect(res.errors).toBe(undefined);
@ -33,7 +31,6 @@ describe('Account Resolvers', () => {
const res = await query({
query: GET_SERVER_ACCOUNTS,
variables: { auth: { type: 'test' } },
});
expect(res.errors).toBe(undefined);

View File

@ -1,42 +1,68 @@
import { ContextType } from 'server/types/apiTypes';
import { SSO_ACCOUNT, SERVER_ACCOUNT } from 'src/context/AccountContext';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
export const accountResolvers = {
Query: {
getAccount: async (_: undefined, __: undefined, context: ContextType) => {
const { ip, accounts, id } = context;
await requestLimiter(ip, 'getAccount');
if (!id) {
logger.error(`Not authenticated`);
throw new Error('NotAuthenticated');
}
if (id === 'sso') {
return {
name: 'SSO Account',
id: 'sso',
loggedIn: true,
type: 'sso',
};
}
const currentAccount = accounts.find(a => a.id === id);
if (!currentAccount) {
logger.error(`No account found for id ${id}`);
throw new Error('NoAccountFound');
}
return { ...currentAccount, type: 'server', loggedIn: true };
},
getServerAccounts: async (
_: undefined,
__: undefined,
context: ContextType
) => {
const { ip, accounts, account, sso, ssoVerified } = context;
const { ip, accounts, id, sso } = context;
await requestLimiter(ip, 'getServerAccounts');
let ssoAccount = null;
if (ssoVerified && sso) {
const { cert, host } = sso;
if (id === 'sso' && sso) {
const { cert, socket } = sso;
logger.debug(
`Macaroon${
cert ? ', certificate' : ''
} and host (${host}) found for SSO.`
} and host (${socket}) found for SSO.`
);
ssoAccount = {
name: 'SSO Account',
id: SSO_ACCOUNT,
id: 'sso',
loggedIn: true,
type: SSO_ACCOUNT,
type: 'sso',
};
}
const withStatus =
accounts?.map(a => ({
...a,
loggedIn: a.id === account,
type: SERVER_ACCOUNT,
loggedIn: a.id === id,
type: 'server',
})) || [];
return ssoAccount ? [...withStatus, ssoAccount] : withStatus;
return ssoAccount ? [ssoAccount, ...withStatus] : withStatus;
},
},
};

View File

@ -6,11 +6,11 @@ import {
PRE_PASS_STRING,
} from 'server/helpers/fileHelpers';
import { ContextType } from 'server/types/apiTypes';
import { SSO_ACCOUNT, SERVER_ACCOUNT } from 'src/context/AccountContext';
import { logger } from 'server/helpers/logger';
import cookie from 'cookie';
import { requestLimiter } from 'server/helpers/rateLimiter';
import bcrypt from 'bcryptjs';
import { appConstants } from 'server/utils/appConstants';
const { serverRuntimeConfig } = getConfig() || {};
const { cookiePath, nodeEnv } = serverRuntimeConfig || {};
@ -26,7 +26,7 @@ export const authResolvers = {
return null;
}
if (!sso.host || !sso.macaroon) {
if (!sso.socket || !sso.macaroon) {
logger.warn('Host and macaroon are required for SSO');
return null;
}
@ -47,11 +47,15 @@ export const authResolvers = {
nodeEnv === 'development'
) {
refreshCookie(cookiePath);
const token = jwt.sign({ user: SSO_ACCOUNT }, secret);
const token = jwt.sign({ id: 'sso' }, secret);
res.setHeader(
'Set-Cookie',
cookie.serialize('SSOAuth', token, { httpOnly: true, sameSite: true })
cookie.serialize(appConstants.cookieName, token, {
httpOnly: true,
sameSite: true,
path: '/',
})
);
return true;
}
@ -83,17 +87,13 @@ export const authResolvers = {
}
logger.debug(`Correct password for account ${params.id}`);
const token = jwt.sign(
{
id: params.id,
},
secret
);
const token = jwt.sign({ id: params.id }, secret);
res.setHeader(
'Set-Cookie',
cookie.serialize('AccountAuth', token, {
cookie.serialize(appConstants.cookieName, token, {
httpOnly: true,
sameSite: true,
path: '/',
})
);
return true;
@ -104,20 +104,10 @@ export const authResolvers = {
const { ip, res } = context;
await requestLimiter(ip, 'logout');
if (params.type === SSO_ACCOUNT) {
res.setHeader(
'Set-Cookie',
cookie.serialize('SSOAuth', '', { maxAge: 1 })
);
return true;
}
if (params.type === SERVER_ACCOUNT) {
res.setHeader(
'Set-Cookie',
cookie.serialize('AccountAuth', '', { maxAge: 1 })
);
return true;
}
res.setHeader(
'Set-Cookie',
cookie.serialize(appConstants.cookieName, '', { maxAge: 1 })
);
return true;
},
},

View File

@ -1,16 +1,12 @@
import { ContextType } from 'server/types/apiTypes';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { logger } from 'server/helpers/logger';
import { AuthType } from 'src/context/AccountContext';
import { rebalance } from 'balanceofsatoshis/swaps';
import { getAccountingReport } from 'balanceofsatoshis/balances';
import request from '@alexbosworth/request';
import { RebalanceResponseType } from 'server/types/balanceofsatoshis.types';
type RebalanceType = {
auth: AuthType;
avoid?: String[];
in_through?: String;
is_avoiding_high_inbound?: Boolean;
@ -24,7 +20,6 @@ type RebalanceType = {
};
type AccountingType = {
auth: AuthType;
category?: String;
currency?: String;
fiat?: String;
@ -39,8 +34,7 @@ export const bosResolvers = {
params: AccountingType,
context: ContextType
) => {
const { auth, ...settings } = params;
const lnd = getLnd(auth, context);
const { lnd } = context;
const response = await to(
getAccountingReport({
@ -48,7 +42,7 @@ export const bosResolvers = {
logger,
request,
is_csv: true,
...settings,
...params,
})
);
@ -62,7 +56,6 @@ export const bosResolvers = {
context: ContextType
) => {
const {
auth,
avoid,
in_through,
is_avoiding_high_inbound,
@ -74,7 +67,7 @@ export const bosResolvers = {
out_through,
target,
} = params;
const lnd = getLnd(auth, context);
const { lnd } = context;
const filteredParams = {
avoid,

View File

@ -1,34 +1,31 @@
import { AuthMock } from 'server/tests/testMocks';
import testServer from 'server/tests/testServer';
import gql from 'graphql-tag';
import { gql } from '@apollo/client';
jest.mock('ln-service');
describe('Chain Resolvers', () => {
test('getChainBalance', async () => {
const getChainBalance = gql`
query($auth: authType!) {
getChainBalance(auth: $auth)
query {
getChainBalance
}
`;
const { query } = testServer();
const res = await query({
query: getChainBalance,
variables: AuthMock,
});
expect(res.errors).toBe(undefined);
expect(res).toMatchSnapshot();
});
test('getPendingChainBalance', async () => {
const getPendingChainBalance = gql`
query($auth: authType!) {
getPendingChainBalance(auth: $auth)
query {
getPendingChainBalance
}
`;
const { query } = testServer();
const res = await query({
query: getPendingChainBalance,
variables: AuthMock,
});
expect(res.errors).toBe(undefined);
expect(res).toMatchSnapshot();

View File

@ -9,11 +9,7 @@ import {
import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
} from 'server/helpers/helpers';
import { getErrorMsg } from 'server/helpers/helpers';
import { sortBy } from 'underscore';
import { to } from 'server/helpers/async';
import {
@ -41,8 +37,7 @@ export const chainResolvers = {
) => {
await requestLimiter(context.ip, 'chainBalance');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const value: ChainBalanceProps = await to<GetChainBalanceType>(
getChainBalance({
@ -58,8 +53,7 @@ export const chainResolvers = {
) => {
await requestLimiter(context.ip, 'pendingChainBalance');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const pendingValue: PendingChainBalanceProps = await to<
GetPendingChainBalanceType
@ -77,8 +71,7 @@ export const chainResolvers = {
) => {
await requestLimiter(context.ip, 'chainTransactions');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const transactionList = await to<GetChainTransactionsType>(
getChainTransactions({
@ -95,8 +88,7 @@ export const chainResolvers = {
getUtxos: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getUtxos');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const info = await to<GetUtxosType>(getUtxos({ lnd }));
@ -107,8 +99,7 @@ export const chainResolvers = {
createAddress: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getAddress');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const format = params.nested ? 'np2wpkh' : 'p2wpkh';
@ -128,8 +119,7 @@ export const chainResolvers = {
sendToAddress: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'sendToAddress');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const props = params.fee
? { fee_tokens_per_vbyte: params.fee }

View File

@ -2,11 +2,7 @@ import { closeChannel as lnCloseChannel } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
} from 'server/helpers/helpers';
import { getErrorMsg } from 'server/helpers/helpers';
export const closeChannel = async (
_: undefined,
@ -15,8 +11,7 @@ export const closeChannel = async (
) => {
await requestLimiter(context.ip, 'closeChannel');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
try {
const info = await lnCloseChannel({

View File

@ -2,11 +2,7 @@ import { openChannel as lnOpenChannel } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
} from 'server/helpers/helpers';
import { getErrorMsg } from 'server/helpers/helpers';
export const openChannel = async (
_: undefined,
@ -15,8 +11,7 @@ export const openChannel = async (
) => {
await requestLimiter(context.ip, 'openChannel');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const openParams = {
is_private: params.isPrivate,

View File

@ -1,7 +1,6 @@
import { updateRoutingFees } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
export const updateFees = async (
@ -21,7 +20,7 @@ export const updateFees = async (
min_htlc_mtokens,
} = params;
const lnd = getLnd(params.auth, context);
const { lnd } = context;
if (
!base_fee_tokens &&

View File

@ -1,7 +1,7 @@
import { getChannelBalance as getLnChannelBalance } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
interface ChannelBalanceProps {
@ -16,8 +16,7 @@ export const getChannelBalance = async (
) => {
await requestLimiter(context.ip, 'channelBalance');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const channelBalance: ChannelBalanceProps = await to(
getLnChannelBalance({

View File

@ -1,7 +1,6 @@
import { getChannels, getWalletInfo } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
interface GetChannelsProps {
@ -37,7 +36,7 @@ export const getChannelFees = async (
) => {
await requestLimiter(context.ip, 'channelFees');
const lnd = getLnd(params.auth, context);
const { lnd } = context;
const { public_key } = await to(getWalletInfo({ lnd }));
const { channels }: GetChannelsProps = await to(getChannels({ lnd }));

View File

@ -2,7 +2,7 @@ import { getChannels as getLnChannels, getWalletInfo } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { to } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { getChannelAge } from 'server/schema/health/helpers';
import { GetChannelsType } from 'server/types/ln-service.types';
@ -13,8 +13,7 @@ export const getChannels = async (
) => {
await requestLimiter(context.ip, 'channels');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const { public_key, current_block_height } = await to(getWalletInfo({ lnd }));

View File

@ -2,7 +2,6 @@ import { getClosedChannels as getLnClosedChannels } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { to } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
interface ChannelListProps {
channels: ChannelProps[];
@ -32,8 +31,7 @@ export const getClosedChannels = async (
) => {
await requestLimiter(context.ip, 'closedChannels');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const { channels }: ChannelListProps = await to(getLnClosedChannels({ lnd }));

View File

@ -2,7 +2,6 @@ import { getPendingChannels as getLnPendingChannels } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { to } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
interface PendingChannelListProps {
pending_channels: PendingChannelProps[];
@ -32,8 +31,7 @@ export const getPendingChannels = async (
) => {
await requestLimiter(context.ip, 'pendingChannels');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const { pending_channels }: PendingChannelListProps = await to(
getLnPendingChannels({ lnd })

View File

@ -10,7 +10,7 @@ import {
import { ContextType } from 'server/types/apiTypes';
import { to, toWithError } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import {
createCustomRecords,
decodeMessage,
@ -26,8 +26,7 @@ export const chatResolvers = {
getMessages: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getMessages');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const invoiceList = await to<GetInvoicesType>(
getInvoices({
@ -105,8 +104,7 @@ export const chatResolvers = {
sendMessage: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'sendMessage');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
if (params.maxFee) {
const tokens = Math.max(params.tokens || 100, 100);

72
server/schema/context.ts Normal file
View File

@ -0,0 +1,72 @@
import { IncomingMessage, ServerResponse } from 'http';
import { getIp } from 'server/helpers/helpers';
import jwt from 'jsonwebtoken';
import { logger } from 'server/helpers/logger';
import {
readMacaroons,
readFile,
getAccounts,
} from 'server/helpers/fileHelpers';
import getConfig from 'next/config';
import { ContextType, SSOType } from 'server/types/apiTypes';
import cookie from 'cookie';
import { LndObject } from 'server/types/ln-service.types';
import { getAuthLnd } from 'server/helpers/auth';
import { appConstants } from 'server/utils/appConstants';
import { secret } from 'pages/api/v1';
const { serverRuntimeConfig } = getConfig();
const {
macaroonPath,
lnCertPath,
lnServerUrl,
accountConfigPath,
} = serverRuntimeConfig;
const ssoMacaroon = readMacaroons(macaroonPath);
const ssoCert = readFile(lnCertPath);
const accountConfig = getAccounts(accountConfigPath);
let sso: SSOType | null = null;
if (ssoMacaroon && lnServerUrl) {
sso = {
macaroon: ssoMacaroon,
socket: lnServerUrl,
cert: ssoCert,
};
}
export const getContext = (req: IncomingMessage, res: ServerResponse) => {
const ip = getIp(req);
const cookies = cookie.parse(req.headers.cookie ?? '') || {};
const auth = cookies[appConstants.cookieName];
let lnd: LndObject | null = null;
let id: string | null = null;
if (auth) {
try {
const data = jwt.verify(auth, secret) as { id: string };
if (data && data.id) {
lnd = getAuthLnd(data.id, sso, accountConfig);
id = data.id;
}
} catch (error) {
logger.silly('Authentication cookie failed');
}
}
const context: ContextType = {
ip,
lnd,
secret,
id,
sso,
accounts: accountConfig,
res,
};
return context;
};

View File

@ -1,5 +1,4 @@
import { getChannels, getChannel, getWalletInfo } from 'ln-service';
import { getCorrectAuth, getAuthLnd } from 'server/helpers/helpers';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { to, toWithError } from 'server/helpers/async';
import { logger } from 'server/helpers/logger';
@ -19,8 +18,7 @@ type ChannelFeesType = {
export default async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getFeeHealth');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const { public_key } = await to(getWalletInfo({ lnd }));
const { channels } = await to<GetChannelsType>(getChannels({ lnd }));

View File

@ -1,5 +1,4 @@
import { getChannels } from 'ln-service';
import { getCorrectAuth, getAuthLnd } from 'server/helpers/helpers';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { to } from 'server/helpers/async';
import { ContextType } from 'server/types/apiTypes';
@ -8,11 +7,10 @@ import { getAverage } from '../helpers';
const halfMonthInMilliSeconds = 1296000000;
export default async (_: undefined, params: any, context: ContextType) => {
export default async (_: undefined, __: any, context: ContextType) => {
await requestLimiter(context.ip, 'getTimeHealth');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const { channels } = await to<GetChannelsType>(getChannels({ lnd }));

View File

@ -1,6 +1,5 @@
import { getForwards, getChannels, getWalletInfo } from 'ln-service';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getCorrectAuth, getAuthLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { subMonths } from 'date-fns';
import { ContextType } from 'server/types/apiTypes';
@ -15,8 +14,7 @@ const monthInBlocks = 4380;
export default async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getVolumeHealth');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const before = new Date().toISOString();
const after = subMonths(new Date(), 1).toISOString();

View File

@ -1,5 +1,5 @@
import merge from 'lodash.merge';
import { makeExecutableSchema } from 'apollo-server-micro';
import { makeExecutableSchema } from 'graphql-tools';
import { nodeTypes } from './node/types';
import { nodeResolvers } from './node/resolvers';
import { authResolvers } from './auth/resolvers';
@ -93,4 +93,4 @@ const resolvers = merge(
tbaseResolvers
);
export default makeExecutableSchema({ typeDefs, resolvers });
export const schema = makeExecutableSchema({ typeDefs, resolvers });

View File

@ -9,12 +9,7 @@ import {
import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
getLnd,
} from 'server/helpers/helpers';
import { getErrorMsg } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { DecodedType } from 'server/types/ln-service.types';
@ -25,7 +20,7 @@ export const invoiceResolvers = {
decodeRequest: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'decode');
const lnd = getLnd(params.auth, context);
const { lnd } = context;
const decoded = await to<DecodedType>(
decodePaymentRequest({
@ -49,8 +44,7 @@ export const invoiceResolvers = {
createInvoice: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'createInvoice');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
return await to(
createInvoiceRequest({
@ -62,8 +56,8 @@ export const invoiceResolvers = {
keysend: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'keysend');
const { auth, destination, tokens } = params;
const lnd = getLnd(auth, context);
const { destination, tokens } = params;
const { lnd } = context;
const preimage = randomBytes(32);
const secret = preimage.toString('hex');
@ -91,8 +85,7 @@ export const invoiceResolvers = {
) => {
await requestLimiter(context.ip, 'circularRebalance');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
let route;
try {
@ -121,8 +114,8 @@ export const invoiceResolvers = {
payViaRoute: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'payViaRoute');
const { auth, route: routeJSON, id } = params;
const lnd = getLnd(auth, context);
const { route: routeJSON, id } = params;
const { lnd } = context;
let route;
try {

View File

@ -1,9 +1,7 @@
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { grantAccess } from 'ln-service';
import { AuthType } from 'src/context/AccountContext';
import { logger } from 'server/helpers/logger';
export type PermissionsType = {
@ -27,7 +25,6 @@ export type PermissionsType = {
};
type ParamsType = {
auth: AuthType;
permissions: PermissionsType;
};
@ -40,9 +37,8 @@ export const macaroonResolvers = {
) => {
await requestLimiter(context.ip, 'createMacaroon');
const { auth, permissions } = params;
const lnd = getLnd(auth, context);
const { permissions } = params;
const { lnd } = context;
const { macaroon, permissions: permissionList } = await to(
grantAccess({ lnd, ...permissions })

View File

@ -1,7 +1,6 @@
import { getNetworkInfo } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
interface NetworkInfoProps {
@ -20,7 +19,7 @@ export const networkResolvers = {
getNetworkInfo: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'networkInfo');
const lnd = getLnd(params.auth, context);
const { lnd } = context;
const info: NetworkInfoProps = await to(getNetworkInfo({ lnd }));

View File

@ -7,7 +7,6 @@ import {
GetWalletInfoType,
GetNodeType,
} from 'server/types/ln-service.types';
import { getAuthLnd, getCorrectAuth, getLnd } from '../../helpers/helpers';
import { ContextType } from '../../types/apiTypes';
import { logger } from '../../helpers/logger';
@ -24,16 +23,15 @@ export const nodeResolvers = {
getNode: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'closedChannels');
const { auth, withoutChannels = true, publicKey } = params;
const lnd = getLnd(auth, context);
const { withoutChannels = true, publicKey } = params;
const { lnd } = context;
return { lnd, publicKey, withChannels: !withoutChannels };
},
getNodeInfo: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'nodeInfo');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const info = await to<GetWalletInfoType>(
getWalletInfo({

View File

@ -2,11 +2,7 @@ import { getPeers, removePeer, addPeer } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
} from 'server/helpers/helpers';
import { getErrorMsg } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
interface PeerProps {
@ -26,8 +22,7 @@ export const peerResolvers = {
getPeers: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getPeers');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const { peers }: { peers: PeerProps[] } = await to(
getPeers({
@ -67,8 +62,7 @@ export const peerResolvers = {
peerSocket = parts[1];
}
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
try {
const success: boolean = await addPeer({
@ -86,8 +80,7 @@ export const peerResolvers = {
removePeer: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'removePeer');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
try {
const success: boolean = await removePeer({

View File

@ -6,7 +6,6 @@ import {
import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { toWithError, to } from 'server/helpers/async';
import { LndObject, ProbeForRouteType } from 'server/types/ln-service.types';
@ -21,7 +20,7 @@ export const routeResolvers = {
getRoutes: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getRoutes');
const lnd = getLnd(params.auth, context);
const { lnd } = context;
const { public_key } = await getWalletInfo({ lnd });

View File

@ -9,12 +9,7 @@ import {
import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
getLnd,
} from 'server/helpers/helpers';
import { getErrorMsg } from 'server/helpers/helpers';
import { toWithError } from 'server/helpers/async';
import { ChannelType } from 'server/types/ln-service.types';
@ -23,8 +18,7 @@ export const toolsResolvers = {
verifyBackups: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'verifyBackups');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
let backupObj = { backup: '', channels: [] as ChannelType[] };
try {
@ -51,8 +45,7 @@ export const toolsResolvers = {
recoverFunds: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'recoverFunds');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
let backupObj = { backup: '' };
try {
@ -78,8 +71,7 @@ export const toolsResolvers = {
getBackups: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getBackups');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
try {
const backups = await getBackups({
@ -94,7 +86,7 @@ export const toolsResolvers = {
adminCheck: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'adminCheck');
const lnd = getLnd(params.auth, context);
const { lnd } = context;
const [, error] = await toWithError(
pay({
@ -123,8 +115,7 @@ export const toolsResolvers = {
verifyMessage: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'verifyMessage');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
try {
const message: { signed_by: string } = await verifyMessage({
@ -142,8 +133,7 @@ export const toolsResolvers = {
signMessage: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'signMessage');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
try {
const message: { signature: string } = await signMessage({

View File

@ -8,7 +8,6 @@ import { compareDesc, subHours, subDays, subMonths, subYears } from 'date-fns';
import { sortBy } from 'underscore';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import {
GetInvoicesType,
@ -26,8 +25,7 @@ export const transactionResolvers = {
getResume: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'payments');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const invoiceProps = params.token
? { token: params.token }
@ -105,8 +103,7 @@ export const transactionResolvers = {
getForwards: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'forwards');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
let startDate = new Date();
const endDate = new Date();

View File

@ -1,14 +1,6 @@
import { gql } from 'apollo-server-micro';
export const generalTypes = gql`
input authType {
type: String!
id: String
host: String
macaroon: String
cert: String
}
input permissionsType {
is_ok_to_adjust_peers: Boolean
is_ok_to_create_chain_addresses: Boolean
@ -38,66 +30,54 @@ export const queryTypes = gql`
type Query {
getBaseNodes: [baseNodesType]!
getAccountingReport(
auth: authType!
category: String
currency: String
fiat: String
month: String
year: String
): String!
getVolumeHealth(auth: authType!): channelsHealth
getTimeHealth(auth: authType!): channelsTimeHealth
getFeeHealth(auth: authType!): channelsFeeHealth
getChannelBalance(auth: authType!): channelBalanceType
getChannels(auth: authType!, active: Boolean): [channelType]!
getClosedChannels(auth: authType!, type: String): [closedChannelType]
getPendingChannels(auth: authType!): [pendingChannelType]
getChannelFees(auth: authType!): [channelFeeType]
getChannelReport(auth: authType!): channelReportType
getNetworkInfo(auth: authType!): networkInfoType
getNodeInfo(auth: authType!): nodeInfoType
adminCheck(auth: authType!): Boolean
getNode(
auth: authType!
publicKey: String!
withoutChannels: Boolean
): Node!
decodeRequest(auth: authType!, request: String!): decodeType
getWalletInfo(auth: authType!): walletInfoType
getResume(auth: authType!, token: String): getResumeType
getForwards(auth: authType!, time: String): getForwardType
getVolumeHealth: channelsHealth
getTimeHealth: channelsTimeHealth
getFeeHealth: channelsFeeHealth
getChannelBalance: channelBalanceType
getChannels(active: Boolean): [channelType]!
getClosedChannels(type: String): [closedChannelType]
getPendingChannels: [pendingChannelType]
getChannelFees: [channelFeeType]
getChannelReport: channelReportType
getNetworkInfo: networkInfoType
getNodeInfo: nodeInfoType
adminCheck: Boolean
getNode(publicKey: String!, withoutChannels: Boolean): Node!
decodeRequest(request: String!): decodeType
getWalletInfo: walletInfoType
getResume(token: String): getResumeType
getForwards(time: String): getForwardType
getBitcoinPrice(logger: Boolean, currency: String): String
getBitcoinFees(logger: Boolean): bitcoinFeeType
getForwardReport(auth: authType!, time: String): String
getForwardChannelsReport(
auth: authType!
time: String
order: String
type: String
): String
getInOut(auth: authType!, time: String): InOutType
getBackups(auth: authType!): String
verifyBackups(auth: authType!, backup: String!): Boolean
recoverFunds(auth: authType!, backup: String!): Boolean
getForwardReport(time: String): String
getForwardChannelsReport(time: String, order: String, type: String): String
getInOut(time: String): InOutType
getBackups: String
verifyBackups(backup: String!): Boolean
recoverFunds(backup: String!): Boolean
getRoutes(
auth: authType!
outgoing: String!
incoming: String!
tokens: Int!
maxFee: Int
): GetRouteType
getPeers(auth: authType!): [peerType]
signMessage(auth: authType!, message: String!): String
verifyMessage(auth: authType!, message: String!, signature: String!): String
getChainBalance(auth: authType!): Int
getPendingChainBalance(auth: authType!): Int
getChainTransactions(auth: authType!): [getTransactionsType]
getUtxos(auth: authType!): [getUtxosType]
getPeers: [peerType]
signMessage(message: String!): String
verifyMessage(message: String!, signature: String!): String
getChainBalance: Int
getPendingChainBalance: Int
getChainTransactions: [getTransactionsType]
getUtxos: [getUtxosType]
getOffers(filter: String): [hodlOfferType]
getCountries: [hodlCountryType]
getCurrencies: [hodlCurrencyType]
getMessages(
auth: authType!
token: String
initialize: Boolean
lastMessage: String
@ -105,6 +85,7 @@ export const queryTypes = gql`
getAuthToken(cookie: String): Boolean
getSessionToken(id: String, password: String): Boolean
getServerAccounts: [serverAccountType]
getAccount: serverAccountType
getLnPayInfo: lnPayInfoType
getLnPay(amount: Int): String
getLatestVersion: String
@ -114,21 +95,18 @@ export const queryTypes = gql`
export const mutationTypes = gql`
type Mutation {
closeChannel(
auth: authType!
id: String!
forceClose: Boolean
targetConfirmations: Int
tokensPerVByte: Int
): closeChannelType
openChannel(
auth: authType!
amount: Int!
partnerPublicKey: String!
tokensPerVByte: Int
isPrivate: Boolean
): openChannelType
updateFees(
auth: authType!
transaction_id: String
transaction_vout: Int
base_fee_tokens: Float
@ -137,11 +115,10 @@ export const mutationTypes = gql`
max_htlc_mtokens: String
min_htlc_mtokens: String
): Boolean
keysend(auth: authType!, destination: String!, tokens: Int!): payType
createInvoice(auth: authType!, amount: Int!): newInvoiceType
circularRebalance(auth: authType!, route: String!): Boolean
keysend(destination: String!, tokens: Int!): payType
createInvoice(amount: Int!): newInvoiceType
circularRebalance(route: String!): Boolean
bosRebalance(
auth: authType!
avoid: [String]
in_through: String
is_avoiding_high_inbound: Boolean
@ -153,10 +130,9 @@ export const mutationTypes = gql`
out_through: String
target: Int
): bosRebalanceResultType
payViaRoute(auth: authType!, route: String!, id: String!): Boolean
createAddress(auth: authType!, nested: Boolean): String
payViaRoute(route: String!, id: String!): Boolean
createAddress(nested: Boolean): String
sendToAddress(
auth: authType!
address: String!
tokens: Int
fee: Int
@ -164,15 +140,13 @@ export const mutationTypes = gql`
sendAll: Boolean
): sendToType
addPeer(
auth: authType!
url: String
publicKey: String
socket: String
isTemporary: Boolean
): Boolean
removePeer(auth: authType!, publicKey: String!): Boolean
removePeer(publicKey: String!): Boolean
sendMessage(
auth: authType!
publicKey: String!
message: String!
messageType: String
@ -180,6 +154,6 @@ export const mutationTypes = gql`
maxFee: Int
): Int
logout(type: String!): Boolean
createMacaroon(auth: authType!, permissions: permissionsType!): String
createMacaroon(permissions: permissionsType!): String
}
`;

View File

@ -2,15 +2,13 @@ import { getWalletVersion } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { to } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
export const walletResolvers = {
Query: {
getWalletInfo: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getWalletInfo');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
return await to(
getWalletVersion({

View File

@ -1,7 +1,6 @@
import { getChannels } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { GetChannelsType } from 'server/types/ln-service.types';
@ -12,7 +11,7 @@ export const getChannelReport = async (
) => {
await requestLimiter(context.ip, 'channelReport');
const lnd = getLnd(params.auth, context);
const { lnd } = context;
const info = await to<GetChannelsType>(getChannels({ lnd }));

View File

@ -4,7 +4,7 @@ import { sortBy } from 'underscore';
import { ContextType } from 'server/types/apiTypes';
import { getNodeFromChannel } from 'server/helpers/getNodeFromChannel';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import {
GetForwardsType,
@ -19,8 +19,7 @@ export const getForwardChannelsReport = async (
) => {
await requestLimiter(context.ip, 'forwardChannels');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
let startDate = new Date();
const endDate = new Date();

View File

@ -8,7 +8,7 @@ import {
} from 'date-fns';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { GetForwardsType } from 'server/types/ln-service.types';
import { reduceForwardArray } from './helpers';
@ -20,8 +20,7 @@ export const getForwardReport = async (
) => {
await requestLimiter(context.ip, 'forwardReport');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
let startDate = new Date();
const endDate = new Date();

View File

@ -3,7 +3,7 @@ import { differenceInHours, differenceInCalendarDays } from 'date-fns';
import { groupBy } from 'underscore';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import {
GetInvoicesType,
@ -18,8 +18,7 @@ export const getInOut = async (
) => {
await requestLimiter(context.ip, 'getInOut');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const endDate = new Date();
let periods = 7;

View File

@ -2,6 +2,14 @@ import * as res from '../lnServiceResponse';
export const authenticatedLndGrpc = jest.fn().mockReturnValue({});
export const probeForRoute = jest
.fn()
.mockReturnValue(Promise.resolve(res.probeForRouteResponse));
export const getNode = jest
.fn()
.mockReturnValue(Promise.resolve(res.getNodeResponse));
export const getNetworkInfo = jest
.fn()
.mockReturnValue(Promise.resolve(res.getNetworkInfoResponse));

View File

@ -342,6 +342,39 @@ export const getNodeResponse = {
updated_at: '2011-10-05T14:48:00.000Z',
};
export const probeForRouteResponse = {
route: {
confidence: 1000,
fee: 1000,
fee_mtokens: 'string',
hops: [
{
channel: 'string',
channel_capacity: 1000,
fee: 1000,
fee_mtokens: 'string',
forward: 1000,
forward_mtokens: 'string',
public_key: 'string',
timeout: 1000,
},
],
messages: [
{
type: 'string',
value: 'string',
},
],
mtokens: 'string',
payment: 'string',
safe_fee: 1000,
safe_tokens: 1000,
timeout: 1000,
tokens: 1000,
total_mtokens: 'string',
},
};
export const getChannelResponse = {
capacity: 1000,
id: '100x1x1',

View File

@ -1,27 +1,21 @@
import { ServerResponse } from 'http';
import { ContextType } from 'server/types/apiTypes';
export const AuthMock = {
auth: {
type: 'test',
},
};
export const ContextMock: ContextType = {
lnd: {},
id: 'test',
ip: '1.2.3.4',
secret: '123456789',
ssoVerified: true,
account: 'accountID',
sso: {
macaroon: 'macaroon',
cert: 'cert',
host: 'host',
socket: 'host',
},
accounts: [
{
name: 'account',
id: 'accountID',
host: 'host',
socket: 'host',
macaroon: 'macaroon',
cert: 'cert',
password: 'password',
@ -31,30 +25,30 @@ export const ContextMock: ContextType = {
};
export const ContextMockNoAccounts: ContextType = {
lnd: {},
id: 'test',
ip: '1.2.3.4',
secret: '123456789',
ssoVerified: true,
account: 'accountID',
sso: {
macaroon: 'macaroon',
cert: 'cert',
host: 'host',
socket: 'host',
},
accounts: [],
res: {} as ServerResponse,
};
export const ContextMockNoSSO: ContextType = {
lnd: {},
id: 'test',
ip: '1.2.3.4',
secret: '123456789',
ssoVerified: true,
account: 'accountID',
sso: null,
accounts: [
{
name: 'account',
id: 'accountID',
host: 'host',
socket: 'host',
macaroon: 'macaroon',
cert: 'cert',
password: 'password',

View File

@ -3,7 +3,7 @@ import {
ApolloServerTestClient,
} from 'apollo-server-testing';
import { ApolloServer } from 'apollo-server';
import schema from 'server/schema';
import { schema } from 'server/schema';
import { ContextMock } from 'server/tests/testMocks';
export default function testServer(context?: any): ApolloServerTestClient {

View File

@ -1,15 +1,16 @@
import { ServerResponse } from 'http';
import { LndObject } from './ln-service.types';
export type SSOType = {
macaroon: string;
cert: string | null;
host: string;
socket: string;
};
export type AccountType = {
name: string;
id: string;
host: string;
socket: string;
macaroon: string;
cert: string | null;
password: string;
@ -17,9 +18,9 @@ export type AccountType = {
export type ContextType = {
ip: string;
lnd: LndObject | null;
secret: string;
ssoVerified: boolean;
account: string | null;
id: string | null;
sso: SSOType | null;
accounts: AccountType[];
res: ServerResponse;

View File

@ -0,0 +1,3 @@
export const appConstants = {
cookieName: 'Thub-Auth',
};

View File

@ -1,24 +1,11 @@
import * as React from 'react';
import {
useAccountDispatch,
useAccountState,
CompleteAccount,
AccountProps,
} from 'src/context/AccountContext';
import { addIdAndTypeToAccount } from 'src/context/helpers/context';
import { useGetServerAccountsQuery } from 'src/graphql/queries/__generated__/getServerAccounts.generated';
import { toast } from 'react-toastify';
import { useRouter } from 'next/router';
import { appendBasePath } from 'src/utils/basePath';
import { getUrlParam } from 'src/utils/url';
import { useGetAuthTokenQuery } from 'src/graphql/queries/__generated__/getAuthToken.generated';
export const ServerAccounts: React.FC = () => {
const { hasAccount } = useAccountState();
const dispatch = useAccountDispatch();
const { push, pathname, query } = useRouter();
const { data, loading, refetch } = useGetServerAccountsQuery();
const { push, query } = useRouter();
const cookieParam = getUrlParam(query?.token);
@ -30,42 +17,9 @@ export const ServerAccounts: React.FC = () => {
React.useEffect(() => {
if (cookieParam && authData && authData.getAuthToken) {
refetch();
push(appendBasePath('/'));
}
}, [push, authData, cookieParam, refetch]);
React.useEffect(() => {
if (hasAccount === 'error' && pathname !== '/') {
toast.error('No account found');
dispatch({ type: 'resetFetch' });
push(appendBasePath('/'));
}
}, [hasAccount, push, dispatch, pathname]);
React.useEffect(() => {
const session = sessionStorage.getItem('session') || null;
const changeId = localStorage.getItem('active') || null;
const savedAccounts = JSON.parse(localStorage.getItem('accounts') || '[]');
const accountsToAdd = savedAccounts.map((a: AccountProps) =>
addIdAndTypeToAccount(a)
);
dispatch({
type: 'initialize',
accountsToAdd,
changeId,
session,
});
}, [dispatch]);
React.useEffect(() => {
if (!loading && data && data.getServerAccounts) {
dispatch({
type: 'addServerAccounts',
accountsToAdd: data.getServerAccounts as CompleteAccount[],
});
}
}, [loading, data, dispatch]);
}, [push, authData, cookieParam]);
return null;
};

View File

@ -1,14 +0,0 @@
import React from 'react';
import { useAccountState, CLIENT_ACCOUNT } from 'src/context/AccountContext';
export const AdminSwitch: React.FC = ({ children }) => {
const { account, session } = useAccountState();
if (account?.type === CLIENT_ACCOUNT) {
if (!account.admin && !session) {
return null;
}
}
return <>{children}</>;
};

View File

@ -1,43 +0,0 @@
import styled from 'styled-components';
import { Sub4Title } from '../generic/Styled';
import { fontColors, textColor } from '../../styles/Themes';
export const Line = styled.div`
margin: 16px 0;
`;
export const StyledTitle = styled(Sub4Title)`
text-align: left;
width: 100%;
margin-bottom: 0px;
`;
export const CheckboxText = styled.div`
font-size: 13px;
color: ${fontColors.grey7};
text-align: justify;
`;
export const StyledContainer = styled.div`
color: ${textColor};
display: flex;
justify-content: flex-start;
align-items: center;
padding-right: 32px;
margin: 32px 0 8px;
`;
export const FixedWidth = styled.div`
height: 18px;
width: 18px;
margin: 0px;
margin-right: 8px;
`;
export const QRTextWrapper = styled.div`
display: flex;
margin: 16px 0;
flex-direction: column;
align-items: center;
justify-content: center;
`;

View File

@ -1,50 +0,0 @@
import React from 'react';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { X, Check } from 'react-feather';
import { getAuthObj } from 'src/utils/auth';
import { useGetCanAdminQuery } from 'src/graphql/queries/__generated__/adminCheck.generated';
import { SingleLine, Sub4Title } from '../../generic/Styled';
import { themeColors } from '../../../styles/Themes';
type AdminProps = {
host: string;
admin: string;
cert?: string;
setChecked: (state: boolean) => void;
};
export const AdminCheck: React.FC<AdminProps> = ({
host,
admin,
cert,
setChecked,
}) => {
const { data, loading } = useGetCanAdminQuery({
fetchPolicy: 'network-only',
skip: !admin,
variables: { auth: getAuthObj(host, undefined, admin, cert) },
onError: () => {
setChecked(false);
},
onCompleted: () => {
setChecked(true);
},
});
const content = () => {
if (loading) {
return <ScaleLoader height={20} color={themeColors.blue3} />;
}
if (data?.adminCheck) {
return <Check size={18} />;
}
return <X size={18} />;
};
return (
<SingleLine>
<Sub4Title>Admin Macaroon</Sub4Title>
{content()}
</SingleLine>
);
};

View File

@ -1,155 +0,0 @@
import React, { useState, useEffect } from 'react';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { Check, X } from 'react-feather';
import { getAuthObj } from 'src/utils/auth';
import { useGetCanConnectQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated';
import { themeColors } from '../../../styles/Themes';
import { SingleLine, Sub4Title, Separation } from '../../generic/Styled';
import { ColorButton } from '../../buttons/colorButton/ColorButton';
import { Text } from '../../typography/Styled';
import { AdminCheck } from './AdminCheck';
type ViewProps = {
host: string;
admin?: string;
viewOnly?: string;
cert?: string;
adminChecked: boolean;
callback: () => void;
setAdminChecked: (state: boolean) => void;
handleConnect: () => void;
setName: (name: string) => void;
};
export const ViewCheck = ({
host,
admin,
viewOnly,
cert,
adminChecked,
setAdminChecked,
handleConnect,
callback,
setName,
}: ViewProps) => {
const [confirmed, setConfirmed] = useState(false);
const { data, loading } = useGetCanConnectQuery({
fetchPolicy: 'network-only',
variables: {
auth: getAuthObj(host, viewOnly, admin, cert),
},
onCompleted: () => setConfirmed(true),
onError: () => setConfirmed(false),
});
useEffect(() => {
if (!loading && data && data.getNodeInfo) {
setName(data.getNodeInfo.alias);
}
}, [loading, data, setName]);
const content = () => {
if (loading) {
return <ScaleLoader height={20} color={themeColors.blue3} />;
}
if (data?.getNodeInfo?.alias) {
return <Check size={18} />;
}
return <X size={18} />;
};
const renderInfo = () => {
if (!loading && data && data.getNodeInfo) {
return (
<>
<SingleLine>
<Sub4Title>Alias</Sub4Title>
<Sub4Title>{data.getNodeInfo.alias}</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Synced To Chain</Sub4Title>
<Sub4Title>
{data.getNodeInfo.is_synced_to_chain ? 'Yes' : 'No'}
</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Version</Sub4Title>
<Sub4Title>{data.getNodeInfo.version.split(' ')[0]}</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Active Channels</Sub4Title>
<Sub4Title>{data.getNodeInfo.active_channels_count}</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Pending Channels</Sub4Title>
<Sub4Title>{data.getNodeInfo.pending_channels_count}</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Closed Channels</Sub4Title>
<Sub4Title>{data.getNodeInfo.closed_channels_count}</Sub4Title>
</SingleLine>
<Separation />
</>
);
}
return null;
};
const renderTitle = () => {
if (!confirmed) {
return 'Go Back';
}
if (adminChecked && !viewOnly && admin) {
return 'Connect (Admin-Only)';
}
if (!adminChecked && viewOnly) {
return 'Connect (View-Only)';
}
if (!adminChecked && admin) {
return 'Connect (View-Only)';
}
return 'Connect';
};
const renderText = () => (
<Text>
Failed to connect to node. Please verify the information provided.
</Text>
);
return (
<>
{renderInfo()}
{!confirmed && !loading && renderText()}
<SingleLine>
<Sub4Title>{viewOnly ? 'View-Only Macaroon' : 'Connected'}</Sub4Title>
{content()}
</SingleLine>
{admin && confirmed && (
<AdminCheck
host={host}
admin={admin}
cert={cert}
setChecked={setAdminChecked}
/>
)}
<ColorButton
fullWidth={true}
withMargin={'16px 0 0'}
disabled={loading}
loading={loading}
arrow={confirmed}
onClick={() => {
if (confirmed) {
handleConnect();
} else {
callback();
}
}}
>
{renderTitle()}
</ColorButton>
</>
);
};

View File

@ -1,169 +0,0 @@
import React, { useState } from 'react';
import dynamic from 'next/dynamic';
import AES from 'crypto-js/aes';
import { useRouter } from 'next/router';
import { toast } from 'react-toastify';
import {
useAccountState,
useAccountDispatch,
} from 'src/context/AccountContext';
import { getAccountId } from '../../utils/auth';
import { useStatusDispatch } from '../../context/StatusContext';
import { LoadingCard } from '../loading/LoadingCard';
import { appendBasePath } from '../../utils/basePath';
import { useChatDispatch } from '../../context/ChatContext';
import { ViewCheck } from './checks/ViewCheck';
import { BTCLoginForm } from './views/BTCLogin';
import { ConnectLoginForm } from './views/ConnectLogin';
import { LoginForm } from './views/NormalLogin';
const PasswordInput = dynamic(() => import('./views/Password'), {
ssr: false,
loading: function Loading() {
return <LoadingCard noCard={true} />;
},
});
type AuthProps = {
type: string;
status: string;
callback: () => void;
setStatus: (state: string) => void;
};
export const Auth = ({ type, status, callback, setStatus }: AuthProps) => {
const { accounts } = useAccountState();
const { push } = useRouter();
const dispatch = useStatusDispatch();
const dispatchChat = useChatDispatch();
const dispatchAccount = useAccountDispatch();
const [name, setName] = useState<string>();
const [host, setHost] = useState<string>();
const [admin, setAdmin] = useState<string>();
const [viewOnly, setViewOnly] = useState<string>();
const [cert, setCert] = useState<string>();
const [password, setPassword] = useState<string>();
const [adminChecked, setAdminChecked] = useState(false);
const handleSet = ({
host,
admin,
viewOnly,
cert,
}: {
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
}) => {
const id = getAccountId(
host ?? '',
viewOnly ?? '',
admin ?? '',
cert ?? ''
);
const accountExists = accounts
? accounts.filter(account => account.id === id).length > 0
: false;
if (accountExists) {
toast.error('Account already exists.');
} else if (!host) {
toast.error('A host url is needed to connect.');
} else if (!admin && !viewOnly) {
toast.error('View-Only or Admin macaroon are needed to connect.');
} else {
host && setHost(host);
admin && setAdmin(admin);
viewOnly && setViewOnly(viewOnly);
cert && setCert(cert);
setStatus('confirmNode');
}
};
const handleSave = () => {
if (!host) {
toast.error('A host url is needed to connect.');
} else if (!admin && !viewOnly) {
toast.error('View-Only or Admin macaroon are needed to connect.');
} else {
let correctViewOnly = viewOnly || null;
if (!viewOnly && admin && !password) {
correctViewOnly = admin;
}
const encryptedAdmin =
admin && password ? AES.encrypt(admin, password).toString() : null;
dispatch({ type: 'disconnected' });
dispatchChat({ type: 'disconnected' });
dispatchAccount({
type: 'addAccountAndSave',
accountToAdd: {
name: name || '',
host,
admin: encryptedAdmin || '',
viewOnly: correctViewOnly || '',
cert: cert || '',
},
...(!correctViewOnly && { session: admin }),
});
push(appendBasePath('/home'));
}
};
const handleConnect = () => {
if (adminChecked) {
setStatus('password');
} else {
handleSave();
}
};
const renderView = () => {
switch (type) {
case 'login':
return <LoginForm handleSet={handleSet} />;
case 'connect':
return <ConnectLoginForm handleSet={handleSet} />;
default:
return <BTCLoginForm handleSet={handleSet} />;
}
};
return (
<>
{status === 'none' && renderView()}
{status === 'confirmNode' && host && (
<ViewCheck
host={host}
admin={admin}
viewOnly={viewOnly}
cert={cert}
adminChecked={adminChecked}
setAdminChecked={setAdminChecked}
handleConnect={handleConnect}
callback={callback}
setName={setName}
/>
)}
{status === 'password' && (
<PasswordInput
isPass={password}
setPass={setPassword}
callback={() => {
handleSave();
setStatus('none');
}}
loading={false}
/>
)}
</>
);
};

View File

@ -1,51 +0,0 @@
import React, { useState } from 'react';
import { toast } from 'react-toastify';
import { getConfigLnd } from '../../../utils/auth';
import { Input } from '../../input/Input';
import { Line, StyledTitle } from '../Auth.styled';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps {
handleSet: ({
host,
admin,
viewOnly,
cert,
}: {
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
}) => void;
}
export const BTCLoginForm = ({ handleSet }: AuthProps) => {
const [json, setJson] = useState('');
const [checked, setChecked] = useState(false);
const handleClick = () => {
try {
JSON.parse(json);
const { cert, admin, viewOnly, host } = getConfigLnd(json);
handleSet({ host, admin, viewOnly, cert });
} catch (error) {
toast.error('Invalid JSON');
}
};
const canConnect = json !== '' && checked;
return (
<>
<Line>
<StyledTitle>BTCPayServer Connect JSON:</StyledTitle>
<Input onChange={e => setJson(e.target.value)} />
</Line>
<RiskCheckboxAndConfirm
disabled={!canConnect}
handleClick={handleClick}
checked={checked}
onChange={setChecked}
/>
</>
);
};

View File

@ -1,35 +0,0 @@
import React from 'react';
import { Checkbox } from '../../checkbox/Checkbox';
import { CheckboxText } from '../Auth.styled';
import { ColorButton } from '../../buttons/colorButton/ColorButton';
type CheckboxProps = {
handleClick: () => void;
disabled: boolean;
checked: boolean;
onChange: (state: boolean) => void;
};
export const RiskCheckboxAndConfirm = ({
handleClick,
disabled,
checked,
onChange,
}: CheckboxProps) => (
<>
<Checkbox checked={checked} onChange={onChange}>
<CheckboxText>
{`I'm feeling reckless. Lightning, LND and ThunderHub are under constant development and I understand that there is always a risk of losing funds.`}
</CheckboxText>
</Checkbox>
<ColorButton
disabled={disabled}
onClick={handleClick}
withMargin={'32px 0 0'}
fullWidth={true}
arrow={true}
>
Connect
</ColorButton>
</>
);

View File

@ -1,52 +0,0 @@
import React, { useState } from 'react';
import { getAuthLnd, getBase64CertfromDerFormat } from '../../../utils/auth';
import { Input } from '../../input/Input';
import { Line, StyledTitle } from '../Auth.styled';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps {
handleSet: ({
host,
admin,
viewOnly,
cert,
}: {
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
}) => void;
}
export const ConnectLoginForm = ({ handleSet }: AuthProps) => {
const [url, setUrl] = useState('');
const [checked, setChecked] = useState(false);
const handleClick = () => {
const { cert, macaroon, socket } = getAuthLnd(url);
const base64Cert = getBase64CertfromDerFormat(cert) || '';
handleSet({
host: socket,
admin: macaroon,
cert: base64Cert,
});
};
const canConnect = url !== '' && checked;
return (
<>
<Line>
<StyledTitle>LND Connect Url:</StyledTitle>
<Input onChange={e => setUrl(e.target.value)} />
</Line>
<RiskCheckboxAndConfirm
disabled={!canConnect}
handleClick={handleClick}
checked={checked}
onChange={setChecked}
/>
</>
);
};

View File

@ -1,99 +0,0 @@
import React, { useState } from 'react';
import { Input } from '../../input/Input';
import { Line, StyledTitle } from '../Auth.styled';
import { SingleLine, Sub4Title } from '../../generic/Styled';
import {
MultiButton,
SingleButton,
} from '../../buttons/multiButton/MultiButton';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps {
handleSet: ({
host,
admin,
viewOnly,
cert,
}: {
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
}) => void;
}
export const LoginForm = ({ handleSet }: AuthProps) => {
const [isViewOnly, setIsViewOnly] = useState(true);
const [checked, setChecked] = useState(false);
const [host, setHost] = useState('');
const [admin, setAdmin] = useState('');
const [viewOnly, setRead] = useState('');
const [cert, setCert] = useState('');
const handleClick = () => {
handleSet({ host, admin, viewOnly, cert });
};
const canConnect =
host !== '' && (admin !== '' || viewOnly !== '') && checked;
return (
<>
<SingleLine>
<Sub4Title>Type of Account:</Sub4Title>
<MultiButton>
<SingleButton
selected={isViewOnly}
onClick={() => setIsViewOnly(true)}
>
ViewOnly
</SingleButton>
<SingleButton
selected={!isViewOnly}
onClick={() => setIsViewOnly(false)}
>
Admin
</SingleButton>
</MultiButton>
</SingleLine>
<Line>
<StyledTitle>{'Url (host:grpc port):'}</StyledTitle>
<Input
placeholder={'Host and port (e.g.: www.node.com:443)'}
onChange={e => setHost(e.target.value)}
/>
</Line>
{!isViewOnly && (
<Line>
<StyledTitle>Admin:</StyledTitle>
<Input
placeholder={'Base64 or HEX Admin macaroon'}
onChange={e => setAdmin(e.target.value)}
/>
</Line>
)}
<Line>
<StyledTitle>
{!isViewOnly ? 'Readonly (optional):' : 'Readonly:'}
</StyledTitle>
<Input
placeholder={'Base64 or HEX Readonly macaroon'}
onChange={e => setRead(e.target.value)}
/>
</Line>
<Line>
<StyledTitle>Certificate:</StyledTitle>
<Input
placeholder={'Base64 or HEX TLS Certificate'}
onChange={e => setCert(e.target.value)}
/>
</Line>
<RiskCheckboxAndConfirm
disabled={!canConnect}
handleClick={handleClick}
checked={checked}
onChange={setChecked}
/>
</>
);
};

View File

@ -1,66 +0,0 @@
import React, { useState } from 'react';
import zxcvbn from 'zxcvbn';
import getConfig from 'next/config';
import { Sub4Title, SubTitle, DarkSubTitle } from '../../generic/Styled';
import { ColorButton } from '../../buttons/colorButton/ColorButton';
import { Input } from '../../input/Input';
import { Line, CheckboxText } from '../Auth.styled';
import { LoadingBar } from '../../loadingBar/LoadingBar';
import { Checkbox } from '../../checkbox/Checkbox';
interface PasswordProps {
isPass?: string;
setPass: (pass: string) => void;
callback: () => void;
loading: boolean;
}
const { publicRuntimeConfig } = getConfig();
const { nodeEnv } = publicRuntimeConfig;
const PasswordInput = ({
isPass = '',
setPass,
callback,
loading = false,
}: PasswordProps) => {
const [checked, setChecked] = useState(false);
const strength = (100 * Math.min(zxcvbn(isPass).guesses_log10, 40)) / 40;
const needed = nodeEnv === 'development' ? 1 : checked ? 10 : 20;
return (
<>
<SubTitle>Please Input a Password</SubTitle>
<DarkSubTitle>
This password will be used to encrypt your admin macaroon.
</DarkSubTitle>
<Line>
<Sub4Title>Password:</Sub4Title>
<Input onChange={e => setPass(e.target.value)} />
</Line>
<Line>
<Sub4Title>Strength:</Sub4Title>
<LoadingBar percent={strength} />
</Line>
<Line>
<Checkbox checked={checked} onChange={setChecked}>
<CheckboxText>
{'Disable Strong Password Check (Not Recommended)'}
</CheckboxText>
</Checkbox>
</Line>
<ColorButton
disabled={strength < needed}
onClick={callback}
withMargin={'32px 0 0'}
fullWidth={true}
arrow={true}
loading={loading}
>
Connect
</ColorButton>
</>
);
};
export default PasswordInput;

View File

@ -1,104 +0,0 @@
import React, { useState } from 'react';
import CryptoJS from 'crypto-js';
import { toast } from 'react-toastify';
import { ChevronRight } from 'react-feather';
import {
useAccountState,
useAccountDispatch,
} from 'src/context/AccountContext';
import { getAuthFromAccount } from 'src/context/helpers/context';
import {
Sub4Title,
NoWrapTitle,
SubTitle,
ResponsiveLine,
} from '../../generic/Styled';
import { ColorButton } from '../colorButton/ColorButton';
import { Input } from '../../input/Input';
import { MultiButton, SingleButton } from '../multiButton/MultiButton';
interface LoginProps {
macaroon: string;
color?: string;
callback: (variables: {}) => void;
variables: {};
setModalOpen: (value: boolean) => void;
}
export const LoginModal = ({
macaroon,
color,
setModalOpen,
callback,
variables,
}: LoginProps) => {
const [pass, setPass] = useState<string>('');
const [storeSession, setStoreSession] = useState<boolean>(false);
const { account } = useAccountState();
const dispatch = useAccountDispatch();
const handleClick = () => {
try {
const bytes = CryptoJS.AES.decrypt(macaroon, pass);
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
if (storeSession) {
dispatch({ type: 'addSession', session: decrypted });
}
callback({
variables: {
...variables,
auth: getAuthFromAccount(account, decrypted),
},
});
setModalOpen(false);
} catch (error) {
toast.error('Wrong Password');
}
};
const renderButton = (
onClick: () => void,
text: string,
selected: boolean
) => (
<SingleButton selected={selected} color={color} onClick={onClick}>
{text}
</SingleButton>
);
return (
<>
<SubTitle>Unlock your Account</SubTitle>
<ResponsiveLine>
<Sub4Title>Password:</Sub4Title>
<Input
withMargin={'0 0 0 16px'}
mobileMargin={'0'}
type={'password'}
onChange={e => setPass(e.target.value)}
/>
</ResponsiveLine>
<ResponsiveLine>
<NoWrapTitle>{`Don't ask me again this session:`}</NoWrapTitle>
<MultiButton>
{renderButton(() => setStoreSession(true), 'Yes', storeSession)}
{renderButton(() => setStoreSession(false), 'No', !storeSession)}
</MultiButton>
</ResponsiveLine>
<ColorButton
disabled={pass === ''}
onClick={handleClick}
color={color}
fullWidth={true}
withMargin={'16px 0 0'}
>
Unlock
<ChevronRight size={18} />
</ColorButton>
</>
);
};

View File

@ -1,75 +0,0 @@
import React, { useState } from 'react';
import { useAccountState, CLIENT_ACCOUNT } from 'src/context/AccountContext';
import { getAuthFromAccount } from 'src/context/helpers/context';
import Modal from '../../modal/ReactModal';
import { ColorButton, ColorButtonProps } from '../colorButton/ColorButton';
import { LoginModal } from './LoginModal';
interface SecureButtonProps extends ColorButtonProps {
callback: (variables: {}) => void;
disabled: boolean;
variables: {};
color?: string;
withMargin?: string;
mobileMargin?: string;
arrow?: boolean;
}
export const SecureButton: React.FC<SecureButtonProps> = ({
callback,
color,
disabled,
children,
variables,
...props
}) => {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const { session, account } = useAccountState();
if (!account) {
return null;
}
if (
account &&
account.type === CLIENT_ACCOUNT &&
!account.admin &&
!session
) {
return null;
}
const auth = getAuthFromAccount(account, session);
const handleClick = () => setModalOpen(true);
const onClick =
session || account.type !== CLIENT_ACCOUNT
? () => callback({ variables: { ...variables, auth } })
: handleClick;
return (
<>
<ColorButton
color={color}
disabled={disabled}
onClick={onClick}
{...props}
>
{children}
</ColorButton>
{account.type === CLIENT_ACCOUNT && (
<Modal isOpen={modalOpen} closeCallback={() => setModalOpen(false)}>
<LoginModal
color={color}
macaroon={account.admin}
setModalOpen={setModalOpen}
callback={callback}
variables={variables}
/>
</Modal>
)}
</>
);
};

View File

@ -1,58 +0,0 @@
import React, { useState } from 'react';
import { useAccountState, CLIENT_ACCOUNT } from 'src/context/AccountContext';
import { getAuthFromAccount } from 'src/context/helpers/context';
import Modal from '../../modal/ReactModal';
import { LoginModal } from './LoginModal';
interface SecureButtonProps {
callback: (variables: {}) => void;
variables: {};
color?: string;
}
export const SecureWrapper: React.FC<SecureButtonProps> = ({
callback,
children,
variables,
color,
}) => {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const { account, session } = useAccountState();
if (!account) {
return null;
}
if (account.type === CLIENT_ACCOUNT && !account.admin && !session) {
return null;
}
const auth = getAuthFromAccount(account, session);
const handleClick = () => setModalOpen(true);
const onClick =
session || account.type !== CLIENT_ACCOUNT
? () => callback({ variables: { ...variables, auth } })
: handleClick;
return (
<>
<div role={'button'} onClick={onClick} onKeyDown={onClick} tabIndex={0}>
{children}
</div>
{account.type === CLIENT_ACCOUNT && (
<Modal isOpen={modalOpen} closeCallback={() => setModalOpen(false)}>
<LoginModal
color={color}
macaroon={account.admin}
setModalOpen={setModalOpen}
callback={callback}
variables={variables}
/>
</Modal>
)}
</>
);
};

View File

@ -1,10 +1,9 @@
import * as React from 'react';
import { toast } from 'react-toastify';
import { useRouter } from 'next/router';
import { useAccountState } from 'src/context/AccountContext';
import { useGetMessagesQuery } from 'src/graphql/queries/__generated__/getMessages.generated';
import { useStatusState } from 'src/context/StatusContext';
import { MessagesType } from 'src/graphql/types';
import { useAccount } from 'src/hooks/UseAccount';
import { useChatState, useChatDispatch } from '../../context/ChatContext';
import { getErrorContent } from '../../utils/error';
import { useConfigState } from '../../context/ConfigContext';
@ -14,8 +13,7 @@ export const ChatFetcher: React.FC = () => {
const { chatPollingSpeed } = useConfigState();
const { connected } = useStatusState();
const { auth } = useAccountState();
const account = useAccount();
const { pathname } = useRouter();
const { lastChat, chats, sentChats, initialized } = useChatState();
const dispatch = useChatDispatch();
@ -24,10 +22,10 @@ export const ChatFetcher: React.FC = () => {
const { data, loading, error } = useGetMessagesQuery({
ssr: false,
skip: !auth || initialized || noChatsAvailable || !connected,
skip: initialized || noChatsAvailable || !account,
pollInterval: chatPollingSpeed,
fetchPolicy: 'network-only',
variables: { auth, initialize: !noChatsAvailable },
variables: { initialize: !noChatsAvailable },
onError: error => toast.error(getErrorContent(error)),
});

View File

@ -1,23 +1,24 @@
import * as React from 'react';
import { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { useGetMessagesLazyQuery } from 'src/graphql/queries/__generated__/getMessages.generated';
import { MessagesType } from 'src/graphql/types';
import { useAccount } from 'src/hooks/UseAccount';
import { useChatDispatch } from '../../context/ChatContext';
import { getErrorContent } from '../../utils/error';
export const ChatInit: React.FC = () => {
const { auth, account } = useAccountState();
const dispatch = useChatDispatch();
const [
getMessages,
{ data: initData, loading: initLoading, error: initError },
] = useGetMessagesLazyQuery({
variables: { auth, initialize: true },
variables: { initialize: true },
onError: error => toast.error(getErrorContent(error)),
});
const account = useAccount();
React.useEffect(() => {
if (account) {
const storageChats =

View File

@ -2,12 +2,10 @@ import React from 'react';
import styled, { css } from 'styled-components';
import { BitcoinFees } from 'src/components/bitcoinInfo/BitcoinFees';
import { BitcoinPrice } from 'src/components/bitcoinInfo/BitcoinPrice';
import { useAccountState } from 'src/context/AccountContext';
import { mediaWidths } from '../../styles/Themes';
import { Section } from '../section/Section';
import { Navigation } from '../../layouts/navigation/Navigation';
import { StatusCheck } from '../statusCheck/StatusCheck';
import { LoadingCard } from '../loading/LoadingCard';
import { ServerAccounts } from '../accounts/ServerAccounts';
type GridProps = {
@ -38,24 +36,15 @@ const ContentStyle = styled.div`
export const GridWrapper: React.FC<GridProps> = ({
children,
noNavigation,
}) => {
const { hasAccount, auth } = useAccountState();
const renderContent = () => {
if (hasAccount === 'false') {
return <LoadingCard loadingHeight={'50vh'} noCard={true} />;
}
return children;
};
return (
<Section padding={'16px 0 32px'}>
<Container noNavigation={noNavigation}>
<ServerAccounts />
<BitcoinPrice />
<BitcoinFees />
{auth && <StatusCheck />}
{!noNavigation && <Navigation />}
<ContentStyle>{renderContent()}</ContentStyle>
</Container>
</Section>
);
};
}) => (
<Section padding={'16px 0 32px'}>
<Container noNavigation={noNavigation}>
<ServerAccounts />
<BitcoinPrice />
<BitcoinFees />
<StatusCheck />
{!noNavigation && <Navigation />}
<ContentStyle>{children}</ContentStyle>
</Container>
</Section>
);

View File

@ -10,7 +10,6 @@ import {
Sub4Title,
} from '../../generic/Styled';
import { getErrorContent } from '../../../utils/error';
import { SecureButton } from '../../buttons/secureButton/SecureButton';
import { ColorButton } from '../../buttons/colorButton/ColorButton';
import {
MultiButton,
@ -48,7 +47,7 @@ export const CloseChannel = ({
const [amount, setAmount] = useState<number>(0);
const [isConfirmed, setIsConfirmed] = useState<boolean>(false);
const [closeChannel] = useCloseChannelMutation({
const [closeChannel, { loading }] = useCloseChannelMutation({
onCompleted: data => {
if (data.closeChannel) {
toast.success('Channel Closed');
@ -79,23 +78,27 @@ export const CloseChannel = ({
<WarningCard>
<AlertTriangle size={32} color={'red'} />
<SubTitle>Are you sure you want to close the channel?</SubTitle>
<SecureButton
callback={closeChannel}
variables={{
id: channelId,
forceClose: isForce,
...(isType !== 'none'
? isType === 'fee'
? { tokens: amount }
: { target: amount }
: {}),
}}
color={'red'}
disabled={false}
<ColorButton
disabled={loading}
loading={loading}
withMargin={'4px'}
color={'red'}
onClick={() =>
closeChannel({
variables: {
id: channelId,
forceClose: isForce,
...(isType !== 'none'
? isType === 'fee'
? { tokens: amount }
: { target: amount }
: {}),
},
})
}
>
{`Close Channel [ ${channelName}/${channelId} ]`}
</SecureButton>
</ColorButton>
<ColorButton withMargin={'4px'} onClick={handleOnlyClose}>
Cancel
</ColorButton>

View File

@ -5,7 +5,6 @@ import { toast } from 'react-toastify';
import { useRemovePeerMutation } from 'src/graphql/mutations/__generated__/removePeer.generated';
import { SubTitle } from '../../generic/Styled';
import { getErrorContent } from '../../../utils/error';
import { SecureButton } from '../../buttons/secureButton/SecureButton';
import { ColorButton } from '../../buttons/colorButton/ColorButton';
interface RemovePeerProps {
@ -42,17 +41,17 @@ export const RemovePeerModal = ({
<WarningCard>
<AlertTriangle size={32} color={'red'} />
<SubTitle>Are you sure you want to remove this peer?</SubTitle>
<SecureButton
callback={removePeer}
variables={{
publicKey,
<ColorButton
onClick={() => {
removePeer({ variables: { publicKey } });
}}
color={'red'}
disabled={loading}
loading={loading}
withMargin={'4px'}
>
{`Remove Peer [${peerAlias || publicKey?.substring(0, 6)}]`}
</SecureButton>
</ColorButton>
<ColorButton withMargin={'4px'} onClick={handleOnlyClose}>
Cancel
</ColorButton>

View File

@ -1,93 +0,0 @@
import * as React from 'react';
import { HelpCircle } from 'react-feather';
import styled from 'styled-components';
import ReactTooltip from 'react-tooltip';
import {
useAccountState,
CLIENT_ACCOUNT,
AccountType,
} from 'src/context/AccountContext';
import { CardWithTitle, SubTitle } from '../generic/Styled';
import { useConfigState } from '../../context/ConfigContext';
import {
ArrowLeft,
ArrowRight,
StyledNodeBar,
NodeBarContainer,
} from './NodeInfo.styled';
import { NodeCard } from './NodeCard';
const StyledQuestion = styled(HelpCircle)`
margin-left: 8px;
`;
export const NodeBar = () => {
const { accounts } = useAccountState();
const { multiNodeInfo } = useConfigState();
const slider = React.useRef<HTMLDivElement>(null);
const viewOnlyAccounts = accounts.filter(
account => account.type === CLIENT_ACCOUNT && account.viewOnly !== ''
) as AccountType[];
const handleScroll = (decrease?: boolean) => {
if (slider.current !== null) {
if (decrease) {
slider.current.scrollLeft -= 240;
} else {
slider.current.scrollLeft += 240;
}
}
};
if (viewOnlyAccounts.length <= 1 || !multiNodeInfo) {
return null;
}
return (
<CardWithTitle>
<SubTitle>
Your Nodes
<span data-tip data-for="node_info_question">
<StyledQuestion size={14} />
</span>
</SubTitle>
<NodeBarContainer>
<div
role={'button'}
onClick={() => {
handleScroll(true);
}}
onKeyDown={() => {
handleScroll(true);
}}
tabIndex={0}
>
<ArrowLeft size={18} />
</div>
<div
role={'button'}
onClick={() => {
handleScroll();
}}
onKeyDown={() => {
handleScroll();
}}
tabIndex={-1}
>
<ArrowRight size={18} />
</div>
<StyledNodeBar ref={slider}>
{viewOnlyAccounts.map(account => (
<React.Fragment key={account.id}>
<NodeCard account={account} accountId={account.id} />
</React.Fragment>
))}
</StyledNodeBar>
</NodeBarContainer>
<ReactTooltip id={'node_info_question'} effect={'solid'} place={'right'}>
Only accounts with a view-only macaroon will appear here.
</ReactTooltip>
</CardWithTitle>
);
};

View File

@ -1,131 +0,0 @@
import React, { useState } from 'react';
import { useInView } from 'react-intersection-observer';
import 'intersection-observer'; // Polyfill
import ScaleLoader from 'react-spinners/ScaleLoader';
import { useGetNodeInfoQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated';
import { AccountType } from 'src/context/AccountContext';
import { SingleLine, DarkSubTitle, ResponsiveLine } from '../generic/Styled';
import { themeColors } from '../../styles/Themes';
import { Price } from '../price/Price';
import Modal from '../modal/ReactModal';
import { getAuthObj } from '../../utils/auth';
import { StatusDot, StatusLine, QuickCard } from './NodeInfo.styled';
import { NodeInfoModal } from './NodeInfoModal';
export const getStatusDot = (status: boolean) => {
return status ? <StatusDot color="#95de64" /> : <StatusDot color="#ff4d4f" />;
};
interface NodeCardProps {
account: AccountType;
accountId: string;
}
export const NodeCard = ({ account, accountId }: NodeCardProps) => {
const [isOpen, setIsOpen] = useState(false);
const { host, viewOnly, cert } = account;
const [ref, inView] = useInView({
threshold: 0,
triggerOnce: true,
});
const auth = getAuthObj(host, viewOnly, undefined, cert);
const { data, loading, error } = useGetNodeInfoQuery({
ssr: false,
skip: !inView || !auth,
variables: { auth },
pollInterval: 10000,
});
if (error) {
return null;
}
const renderContent = () => {
if (!inView) {
return (
<>
<StatusLine>{getStatusDot(false)}</StatusLine>
<div>-</div>
<SingleLine>
<DarkSubTitle>Lightning</DarkSubTitle>
<div>-</div>
</SingleLine>
<SingleLine>
<DarkSubTitle>Bitcoin</DarkSubTitle>
<div>-</div>
</SingleLine>
<SingleLine>
<DarkSubTitle>Channels</DarkSubTitle>
<div>-</div>
</SingleLine>
</>
);
}
if (
loading ||
!data?.getNodeInfo ||
!data?.getChannelBalance ||
!data?.getChainBalance ||
!data?.getPendingChainBalance
) {
return <ScaleLoader height={20} color={themeColors.blue3} />;
}
const {
active_channels_count: active,
closed_channels_count: closed,
alias,
pending_channels_count: pending,
is_synced_to_chain,
} = data.getNodeInfo;
const { confirmedBalance, pendingBalance } = data.getChannelBalance;
const chainBalance = data.getChainBalance;
const pendingChainBalance = data.getPendingChainBalance;
return (
<>
<StatusLine>{getStatusDot(is_synced_to_chain)}</StatusLine>
<div>{alias}</div>
<ResponsiveLine>
<DarkSubTitle>Lightning</DarkSubTitle>
<Price amount={confirmedBalance + pendingBalance} />
</ResponsiveLine>
<ResponsiveLine>
<DarkSubTitle>Bitcoin</DarkSubTitle>
<Price amount={chainBalance + pendingChainBalance} />
</ResponsiveLine>
<ResponsiveLine>
<DarkSubTitle>Channels</DarkSubTitle>
<div>{`${active} / ${pending} / ${closed}`}</div>
</ResponsiveLine>
</>
);
};
return (
<>
<QuickCard
onClick={() => {
setIsOpen(true);
}}
ref={ref}
key={account.id}
>
{renderContent()}
</QuickCard>
<Modal
isOpen={isOpen}
closeCallback={() => {
setIsOpen(false);
}}
>
<NodeInfoModal account={data} accountId={accountId} />
</Modal>
</>
);
};

View File

@ -1,105 +0,0 @@
import styled, { css } from 'styled-components';
import { ChevronLeft, ChevronRight } from 'react-feather';
import { Card } from '../generic/Styled';
import {
inverseTextColor,
buttonBorderColor,
textColor,
mediaWidths,
} from '../../styles/Themes';
const arrowCSS = css`
background-color: ${inverseTextColor};
height: 32px;
width: 32px;
position: absolute;
z-index: 2;
top: 50%;
display: none;
border-radius: 4px;
box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
border: 1px solid ${buttonBorderColor};
cursor: pointer;
&:hover {
border: 1px solid ${textColor};
}
`;
export const ArrowLeft = styled(ChevronLeft)`
${arrowCSS}
transform: translate(-30%, -50%);
`;
export const ArrowRight = styled(ChevronRight)`
${arrowCSS}
transform: translate(30%, -50%);
right: 0;
`;
export const NodeBarContainer = styled.div`
position: relative;
margin-bottom: 24px;
&:hover {
${ArrowLeft} {
display: inline-block;
}
${ArrowRight} {
display: inline-block;
}
}
`;
export const StyledNodeBar = styled.div`
display: flex;
overflow-x: scroll;
-ms-overflow-style: none;
cursor: pointer;
::-webkit-scrollbar {
display: none;
}
`;
const sectionColor = '#69c0ff';
export const QuickCard = styled(Card)`
height: 120px;
width: 240px;
min-width: 240px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: stretch;
margin-bottom: 0px;
padding: 10px;
margin-right: 10px;
cursor: pointer;
@media (${mediaWidths.mobile}) {
height: unset;
width: 160px;
min-width: 160px;
}
&:hover {
border: 1px solid ${sectionColor};
}
`;
export const StatusLine = styled.div`
width: 100%;
position: relative;
right: -8px;
top: -8px;
display: flex;
justify-content: flex-end;
margin: 0 0 -8px 0;
`;
export const StatusDot = styled.div`
margin: 0 2px;
height: 8px;
width: 8px;
border-radius: 100%;
background-color: ${({ color }: { color: string }) => color};
`;

View File

@ -1,108 +0,0 @@
import React from 'react';
import { useAccountDispatch } from 'src/context/AccountContext';
import { GetNodeInfoQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated';
import { NodeInfoType, ChannelBalanceType } from 'src/graphql/types';
import {
SubTitle,
SingleLine,
DarkSubTitle,
Sub4Title,
Separation,
} from '../generic/Styled';
import { Price } from '../price/Price';
import { ColorButton } from '../buttons/colorButton/ColorButton';
import { useStatusDispatch } from '../../context/StatusContext';
interface NodeInfoModalProps {
account: GetNodeInfoQuery | null | undefined;
accountId: string;
}
export const NodeInfoModal = ({ account, accountId }: NodeInfoModalProps) => {
const dispatch = useStatusDispatch();
const dispatchAccount = useAccountDispatch();
if (!account) {
return null;
}
const {
active_channels_count,
closed_channels_count,
alias,
pending_channels_count,
is_synced_to_chain,
peers_count,
version,
} = account.getNodeInfo as NodeInfoType;
const {
confirmedBalance,
pendingBalance,
} = account.getChannelBalance as ChannelBalanceType;
const chainBalance = account.getChainBalance;
const pendingChainBalance = account.getPendingChainBalance;
return (
<>
<SubTitle>{alias}</SubTitle>
<Separation />
<SingleLine>
<DarkSubTitle>Version:</DarkSubTitle>
<div>{version.split(' ')[0]}</div>
</SingleLine>
<SingleLine>
<DarkSubTitle>Is Synced:</DarkSubTitle>
<div>{is_synced_to_chain ? 'True' : 'False'}</div>
</SingleLine>
<SingleLine>
<DarkSubTitle>Peer Count:</DarkSubTitle>
<div>{peers_count}</div>
</SingleLine>
<SingleLine>
<DarkSubTitle>Active Channels:</DarkSubTitle>
<div>{active_channels_count}</div>
</SingleLine>
<SingleLine>
<DarkSubTitle>Pending Channels:</DarkSubTitle>
<div>{pending_channels_count}</div>
</SingleLine>
<SingleLine>
<DarkSubTitle>Closed Channels:</DarkSubTitle>
<div>{closed_channels_count}</div>
</SingleLine>
<Sub4Title>Lightning</Sub4Title>
<SingleLine>
<DarkSubTitle>Balance:</DarkSubTitle>
<Price amount={confirmedBalance} />
</SingleLine>
<SingleLine>
<DarkSubTitle>Pending:</DarkSubTitle>
<Price amount={pendingBalance} />
</SingleLine>
<Sub4Title>Bitcoin</Sub4Title>
<SingleLine>
<DarkSubTitle>Balance:</DarkSubTitle>
<Price amount={chainBalance} />
</SingleLine>
<SingleLine>
<DarkSubTitle>Pending:</DarkSubTitle>
<Price amount={pendingChainBalance} />
</SingleLine>
<ColorButton
withMargin={'16px 0 0'}
fullWidth={true}
onClick={() => {
dispatch({
type: 'disconnected',
});
dispatchAccount({ type: 'changeAccount', changeId: accountId });
}}
>
Change to this Account
</ColorButton>
</>
);
};

View File

@ -1,66 +0,0 @@
import React from 'react';
import ScaleLoader from 'react-spinners/ScaleLoader';
import styled from 'styled-components';
import { SubTitle, Card } from '../generic/Styled';
import { themeColors, mediaWidths, fontColors } from '../../styles/Themes';
import { SectionTitle } from '../typography/Styled';
import { CurrentSettings } from '../../views/settings/Current';
import { AccountSettings } from '../../views/settings/Account';
import { DangerView } from '../../views/settings/Danger';
const FullDiv = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 100%;
min-height: 400px;
@media (${mediaWidths.mobile}) {
justify-content: center;
}
`;
export const LoadingView = () => (
<FullDiv>
<SubTitle>Connecting to your Node</SubTitle>
<ScaleLoader height={20} color={themeColors.blue3} />
</FullDiv>
);
const StyledTitle = styled(SectionTitle)`
text-align: center;
margin: 16px 0 32px;
`;
const StyledSubtitle = styled(SubTitle)`
text-align: center;
margin: 48px 0 4px;
`;
const StyledParagraph = styled.p`
color: ${fontColors.grey7};
text-align: center;
margin: 4px 0;
`;
export const ErrorView = () => (
<>
<Card>
<StyledTitle textColor={fontColors.blue3}>Connection Error</StyledTitle>
<StyledParagraph>
ThunderHub was unable to connect to your node.
</StyledParagraph>
<StyledParagraph>
{`Please make sure it's online and that the connection details are correct.`}
</StyledParagraph>
<StyledSubtitle>
If the problem persists please contact us.
</StyledSubtitle>
</Card>
<CurrentSettings />
<AccountSettings />
<DangerView />
</>
);

View File

@ -1,7 +1,6 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { useGetNodeInfoQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated';
import { NodeInfoType, ChannelBalanceType } from 'src/graphql/types';
import { useStatusDispatch, StatusState } from '../../context/StatusContext';
@ -11,18 +10,16 @@ export const StatusCheck: React.FC = () => {
const dispatch = useStatusDispatch();
const { push } = useRouter();
const { account, auth } = useAccountState();
const { data, loading, error, stopPolling } = useGetNodeInfoQuery({
ssr: false,
skip: !auth,
fetchPolicy: 'network-only',
variables: { auth },
pollInterval: 10000,
});
useEffect(() => {
if (error) {
account && toast.error(`Unable to connect to ${account.name}`);
toast.error(`Unable to connect to node`);
stopPolling();
dispatch({ type: 'disconnected' });
push(appendBasePath('/'));
@ -74,7 +71,7 @@ export const StatusCheck: React.FC = () => {
dispatch({ type: 'connected', state });
}
}, [data, dispatch, error, loading, push, account, stopPolling]);
}, [data, dispatch, error, loading, push, stopPolling]);
return null;
};

View File

@ -1,317 +0,0 @@
import * as React from 'react';
import Cookies from 'js-cookie';
import {
getAccountById,
deleteAccountById,
addIdAndTypeToAccount,
getAuthFromAccount,
} from './helpers/context';
export type SERVER_ACCOUNT_TYPE = 'sso' | 'server' | 'test';
export type ACCOUNT_TYPE = 'client';
export const CLIENT_ACCOUNT: ACCOUNT_TYPE = 'client';
export const SSO_ACCOUNT: SERVER_ACCOUNT_TYPE = 'sso';
export const SERVER_ACCOUNT: SERVER_ACCOUNT_TYPE = 'server';
type HasAccountType = 'fetched' | 'false' | 'error';
export type AuthType =
| {
type: ACCOUNT_TYPE;
host: string;
macaroon: string;
cert: string | null;
}
| {
type: SERVER_ACCOUNT_TYPE;
id: string;
};
export type AccountProps = {
name: string;
host: string;
admin: string;
viewOnly: string;
cert: string;
};
export type AccountType = {
type: ACCOUNT_TYPE;
id: string;
} & AccountProps;
export type ServerAccountType = {
type: SERVER_ACCOUNT_TYPE;
id: string;
name: string;
loggedIn?: boolean;
};
export type CompleteAccount = AccountType | ServerAccountType;
export const defaultAuth = { type: SERVER_ACCOUNT, id: '' };
type State = {
initialized: boolean;
finishedFetch: boolean;
auth: AuthType;
activeAccount: string | null;
session: string | null;
account: CompleteAccount | null;
accounts: CompleteAccount[];
hasAccount: HasAccountType;
};
type ActionType =
| {
type: 'initialize';
changeId: string | null;
accountsToAdd: CompleteAccount[];
session: string | null;
}
| {
type: 'changeAccount' | 'deleteAccount';
changeId: string;
}
| {
type: 'logout';
}
| {
type: 'addServerAccounts';
accountsToAdd: CompleteAccount[];
}
| {
type: 'addAccountAndSave';
accountToAdd: AccountProps;
session?: string;
}
| {
type: 'addSession';
session: string;
}
| {
type: 'removeSession';
}
| {
type: 'deleteAll';
}
| {
type: 'resetFetch';
};
type Dispatch = (action: ActionType) => void;
const StateContext = React.createContext<State | undefined>(undefined);
const DispatchContext = React.createContext<Dispatch | undefined>(undefined);
const initialState: State = {
initialized: false,
finishedFetch: false,
auth: defaultAuth,
session: null,
activeAccount: null,
account: null,
accounts: [],
hasAccount: 'false',
};
const stateReducer = (state: State, action: ActionType): State => {
switch (action.type) {
case 'initialize': {
if (state.initialized) {
return state;
}
const { accountsToAdd, changeId, session } = action;
const { account, id } = getAccountById(changeId, accountsToAdd);
if (!account)
return {
...state,
initialized: true,
accounts: accountsToAdd,
activeAccount: changeId,
session,
};
const auth = getAuthFromAccount(account, session);
return {
...state,
initialized: true,
auth,
account,
accounts: accountsToAdd,
activeAccount: id,
session,
hasAccount: 'fetched',
};
}
case 'changeAccount': {
const { account, id } = getAccountById(action.changeId, state.accounts);
if (!account) return state;
const auth = getAuthFromAccount(account);
localStorage.setItem('active', `${id}`);
sessionStorage.removeItem('session');
return {
...state,
auth,
session: null,
account,
activeAccount: id,
hasAccount: 'fetched',
};
}
case 'logout':
localStorage.removeItem('active');
sessionStorage.clear();
return {
...state,
account: null,
activeAccount: null,
auth: defaultAuth,
session: null,
};
case 'deleteAccount': {
if (!state.accounts || state?.accounts?.length <= 0 || !state.account) {
return state;
}
const { accounts, id } = deleteAccountById(
state.account.id,
action.changeId,
state.accounts
);
localStorage.setItem('accounts', JSON.stringify(accounts));
!id && sessionStorage.removeItem('session');
return {
...state,
accounts,
...(!id && { activeId: null, session: null, account: null }),
};
}
case 'addServerAccounts': {
const clientAccounts = state.accounts.filter(
a => a.type === CLIENT_ACCOUNT
);
const completeAccounts = [...clientAccounts, ...action.accountsToAdd];
if (!state.activeAccount) {
return {
...state,
finishedFetch: true,
accounts: completeAccounts,
};
}
const { account } = getAccountById(state.activeAccount, completeAccounts);
if (!account && completeAccounts.length > 0) {
return {
...state,
finishedFetch: true,
accounts: completeAccounts,
hasAccount: 'error',
};
}
const auth = getAuthFromAccount(account, state.session);
return {
...state,
finishedFetch: true,
hasAccount: 'fetched',
auth,
account,
accounts: completeAccounts,
};
}
case 'addAccountAndSave': {
const account = addIdAndTypeToAccount(action.accountToAdd);
const activeAccount = account.id;
const accounts = [...state.accounts, account];
const auth = getAuthFromAccount(account, action.session);
if (action.session) {
sessionStorage.setItem('session', action.session);
}
localStorage.setItem('active', `${activeAccount}`);
const savedAccounts = JSON.parse(
localStorage.getItem('accounts') || '[]'
);
localStorage.setItem(
'accounts',
JSON.stringify([...savedAccounts, action.accountToAdd])
);
return {
...state,
auth,
account,
accounts,
activeAccount,
hasAccount: 'fetched',
...(action.session && { session: action.session }),
};
}
case 'addSession':
sessionStorage.setItem('session', action.session);
return {
...state,
auth: getAuthFromAccount(state.account, action.session),
session: action.session,
};
case 'removeSession':
sessionStorage.removeItem('session');
return {
...state,
auth: getAuthFromAccount(state.account),
session: null,
};
case 'deleteAll':
localStorage.clear();
sessionStorage.clear();
Cookies.remove('config');
return initialState;
case 'resetFetch':
return {
...state,
hasAccount: 'false',
};
default:
return state;
}
};
const AccountProvider: React.FC = ({ children }) => {
const [state, dispatch] = React.useReducer(stateReducer, initialState);
return (
<DispatchContext.Provider value={dispatch}>
<StateContext.Provider value={state}>{children}</StateContext.Provider>
</DispatchContext.Provider>
);
};
const useAccountState = () => {
const context = React.useContext(StateContext);
if (context === undefined) {
throw new Error('useAccountState must be used within a AccountProvider');
}
return context;
};
const useAccountDispatch = () => {
const context = React.useContext(DispatchContext);
if (context === undefined) {
throw new Error('useAccountDispatch must be used within a AccountProvider');
}
return context;
};
export { AccountProvider, useAccountState, useAccountDispatch };

View File

@ -27,7 +27,6 @@ type State = {
currency: string;
theme: string;
sidebar: boolean;
multiNodeInfo: boolean;
fetchFees: boolean;
fetchPrices: boolean;
displayValues: boolean;
@ -51,7 +50,6 @@ type ActionType =
currency?: string;
theme?: string;
sidebar?: boolean;
multiNodeInfo?: boolean;
fetchFees?: boolean;
fetchPrices?: boolean;
displayValues?: boolean;
@ -83,7 +81,6 @@ const initialState: State = {
currency: currencyTypes.indexOf(defC) > -1 ? defC : 'sat',
theme: themeTypes.indexOf(defT) > -1 ? defT : 'dark',
sidebar: true,
multiNodeInfo: false,
fetchFees,
fetchPrices,
displayValues: true,

View File

@ -1,5 +1,4 @@
import React from 'react';
import { AccountProvider } from './AccountContext';
import { BitcoinInfoProvider } from './BitcoinContext';
import { StatusProvider } from './StatusContext';
import { PriceProvider } from './PriceContext';
@ -7,15 +6,13 @@ import { ChatProvider } from './ChatContext';
import { RebalanceProvider } from './RebalanceContext';
export const ContextProvider: React.FC = ({ children }) => (
<AccountProvider>
<BitcoinInfoProvider>
<PriceProvider>
<ChatProvider>
<StatusProvider>
<RebalanceProvider>{children}</RebalanceProvider>
</StatusProvider>
</ChatProvider>
</PriceProvider>
</BitcoinInfoProvider>
</AccountProvider>
<BitcoinInfoProvider>
<PriceProvider>
<ChatProvider>
<StatusProvider>
<RebalanceProvider>{children}</RebalanceProvider>
</StatusProvider>
</ChatProvider>
</PriceProvider>
</BitcoinInfoProvider>
);

View File

@ -1,9 +1,5 @@
import React, { createContext, useContext, useReducer } from 'react';
type StateStatus = {
connected: boolean;
};
export type StatusState = {
alias: string;
color: string;
@ -22,20 +18,19 @@ export type StatusState = {
peersCount: number;
};
type CompleteState = StatusState & StateStatus;
type ActionType = {
type: 'connected' | 'disconnected';
state?: StatusState;
};
type ActionType =
| {
type: 'connected';
state?: StatusState;
}
| { type: 'disconnected' };
type Dispatch = (action: ActionType) => void;
const StateContext = createContext<CompleteState | undefined>(undefined);
const StateContext = createContext<StatusState | undefined>(undefined);
const DispatchContext = createContext<Dispatch | undefined>(undefined);
const initialState = {
connected: false,
const initialState: StatusState = {
alias: '',
color: '',
syncedToChain: false,
@ -53,15 +48,11 @@ const initialState = {
peersCount: 0,
};
const stateReducer = (
state: StatusState,
action: ActionType
): CompleteState => {
const stateReducer = (state: StatusState, action: ActionType): StatusState => {
switch (action.type) {
case 'connected':
return { ...state, ...action.state, connected: true } || initialState;
return { ...state, ...action.state } || initialState;
case 'disconnected':
return initialState;
default:
return initialState;
}

View File

@ -1,71 +0,0 @@
import {
CLIENT_ACCOUNT,
SSO_ACCOUNT,
CompleteAccount,
} from '../AccountContext';
import { getAccountById, deleteAccountById } from './context';
const firstAccount = {
name: 'Hola',
host: 'Host1',
admin: 'Admin1',
viewOnly: 'ViewOnly1',
cert: 'Cert1',
id: '123',
type: CLIENT_ACCOUNT,
};
const secondAccount = {
name: 'Chao',
id: '1234',
type: SSO_ACCOUNT,
};
const testAccounts: CompleteAccount[] = [firstAccount, secondAccount];
describe('Context Helpers', () => {
describe('should getAccountById', () => {
test('account exists', () => {
const { account, id } = getAccountById('1234', testAccounts);
expect(id).toBe('1234');
expect(account).toBe(secondAccount);
});
test('account does not exists', () => {
const { account, id } = getAccountById('false id', testAccounts);
expect(id).toBe(null);
expect(account).toBe(null);
});
});
describe('should deleteAccountById', () => {
test('account exists', () => {
const { accounts, id } = deleteAccountById('123', '1234', testAccounts);
expect(id).toBe('123');
expect(accounts).toStrictEqual([firstAccount]);
});
test('account exists and is current account', () => {
const { accounts, id } = deleteAccountById('123', '123', testAccounts);
expect(id).toBe(null);
expect(accounts).toStrictEqual([secondAccount]);
});
test('account does not exists', () => {
const { accounts, id } = deleteAccountById(
'123',
'false id',
testAccounts
);
expect(id).toBe('123');
expect(accounts).toStrictEqual(testAccounts);
});
test('one account', () => {
const { accounts, id } = deleteAccountById('123', '123', [firstAccount]);
expect(id).toBe(null);
expect(accounts).toStrictEqual([]);
});
});
});

View File

@ -1,80 +0,0 @@
import { getUUID } from '../../utils/auth';
import {
CompleteAccount,
AccountProps,
CLIENT_ACCOUNT,
AuthType,
defaultAuth,
} from '../AccountContext';
export const getAccountById = (
id: string | null,
accounts: CompleteAccount[]
) => {
if (!id) return { account: null, id: null };
const correctAccount: CompleteAccount | null | undefined = accounts.find(
a => a.id === id
);
return {
account: correctAccount || null,
id: correctAccount ? correctAccount.id : null,
};
};
export const deleteAccountById = (
currentId: string,
id: string,
accounts: CompleteAccount[]
) => {
const newAccounts: CompleteAccount[] = accounts.filter(a => a.id !== id);
if (newAccounts.length <= 0) {
return { accounts: [], id: null };
}
let activeId: string | null = currentId;
if (currentId === id) {
activeId = null;
}
return { accounts: newAccounts, id: activeId };
};
export const addIdAndTypeToAccount = (
account: AccountProps
): CompleteAccount => {
const { host, viewOnly, admin, cert } = account;
return {
...account,
type: CLIENT_ACCOUNT,
id: getUUID(`${host}-${viewOnly}-${admin !== '' ? 1 : 0}-${cert}`),
};
};
export const getAuthFromAccount = (
account: CompleteAccount | undefined | null,
session?: string | null
): AuthType => {
if (!account) return defaultAuth;
if (account.type !== CLIENT_ACCOUNT) {
return {
type: account.type,
id: account.id,
};
}
const { host, viewOnly, cert } = account;
if (!host) {
return defaultAuth;
}
if (!viewOnly && !session) {
return defaultAuth;
}
return {
type: account.type,
host,
macaroon: session || viewOnly,
cert,
};
};

View File

@ -1,6 +1,11 @@
import gql from 'graphql-tag';
import * as ApolloReactCommon from '@apollo/react-common';
import * as ApolloReactHooks from '@apollo/react-hooks';
import {
gql,
QueryHookOptions,
useQuery,
useLazyQuery,
QueryResult,
LazyQueryHookOptions,
} from '@apollo/client';
import * as Types from '../../types';
export type GetCountriesQueryVariables = Types.Exact<{ [key: string]: never }>;
@ -132,26 +137,23 @@ export const GetCountriesDocument = gql`
* });
*/
export function useGetCountriesQuery(
baseOptions?: ApolloReactHooks.QueryHookOptions<
GetCountriesQuery,
GetCountriesQueryVariables
>
baseOptions?: QueryHookOptions<GetCountriesQuery, GetCountriesQueryVariables>
) {
return ApolloReactHooks.useQuery<
GetCountriesQuery,
GetCountriesQueryVariables
>(GetCountriesDocument, baseOptions);
return useQuery<GetCountriesQuery, GetCountriesQueryVariables>(
GetCountriesDocument,
baseOptions
);
}
export function useGetCountriesLazyQuery(
baseOptions?: ApolloReactHooks.LazyQueryHookOptions<
baseOptions?: LazyQueryHookOptions<
GetCountriesQuery,
GetCountriesQueryVariables
>
) {
return ApolloReactHooks.useLazyQuery<
GetCountriesQuery,
GetCountriesQueryVariables
>(GetCountriesDocument, baseOptions);
return useLazyQuery<GetCountriesQuery, GetCountriesQueryVariables>(
GetCountriesDocument,
baseOptions
);
}
export type GetCountriesQueryHookResult = ReturnType<
typeof useGetCountriesQuery
@ -159,7 +161,7 @@ export type GetCountriesQueryHookResult = ReturnType<
export type GetCountriesLazyQueryHookResult = ReturnType<
typeof useGetCountriesLazyQuery
>;
export type GetCountriesQueryResult = ApolloReactCommon.QueryResult<
export type GetCountriesQueryResult = QueryResult<
GetCountriesQuery,
GetCountriesQueryVariables
>;
@ -189,26 +191,26 @@ export const GetCurrenciesDocument = gql`
* });
*/
export function useGetCurrenciesQuery(
baseOptions?: ApolloReactHooks.QueryHookOptions<
baseOptions?: QueryHookOptions<
GetCurrenciesQuery,
GetCurrenciesQueryVariables
>
) {
return ApolloReactHooks.useQuery<
GetCurrenciesQuery,
GetCurrenciesQueryVariables
>(GetCurrenciesDocument, baseOptions);
return useQuery<GetCurrenciesQuery, GetCurrenciesQueryVariables>(
GetCurrenciesDocument,
baseOptions
);
}
export function useGetCurrenciesLazyQuery(
baseOptions?: ApolloReactHooks.LazyQueryHookOptions<
baseOptions?: LazyQueryHookOptions<
GetCurrenciesQuery,
GetCurrenciesQueryVariables
>
) {
return ApolloReactHooks.useLazyQuery<
GetCurrenciesQuery,
GetCurrenciesQueryVariables
>(GetCurrenciesDocument, baseOptions);
return useLazyQuery<GetCurrenciesQuery, GetCurrenciesQueryVariables>(
GetCurrenciesDocument,
baseOptions
);
}
export type GetCurrenciesQueryHookResult = ReturnType<
typeof useGetCurrenciesQuery
@ -216,7 +218,7 @@ export type GetCurrenciesQueryHookResult = ReturnType<
export type GetCurrenciesLazyQueryHookResult = ReturnType<
typeof useGetCurrenciesLazyQuery
>;
export type GetCurrenciesQueryResult = ApolloReactCommon.QueryResult<
export type GetCurrenciesQueryResult = QueryResult<
GetCurrenciesQuery,
GetCurrenciesQueryVariables
>;
@ -285,23 +287,17 @@ export const GetOffersDocument = gql`
* });
*/
export function useGetOffersQuery(
baseOptions?: ApolloReactHooks.QueryHookOptions<
GetOffersQuery,
GetOffersQueryVariables
>
baseOptions?: QueryHookOptions<GetOffersQuery, GetOffersQueryVariables>
) {
return ApolloReactHooks.useQuery<GetOffersQuery, GetOffersQueryVariables>(
return useQuery<GetOffersQuery, GetOffersQueryVariables>(
GetOffersDocument,
baseOptions
);
}
export function useGetOffersLazyQuery(
baseOptions?: ApolloReactHooks.LazyQueryHookOptions<
GetOffersQuery,
GetOffersQueryVariables
>
baseOptions?: LazyQueryHookOptions<GetOffersQuery, GetOffersQueryVariables>
) {
return ApolloReactHooks.useLazyQuery<GetOffersQuery, GetOffersQueryVariables>(
return useLazyQuery<GetOffersQuery, GetOffersQueryVariables>(
GetOffersDocument,
baseOptions
);
@ -310,7 +306,7 @@ export type GetOffersQueryHookResult = ReturnType<typeof useGetOffersQuery>;
export type GetOffersLazyQueryHookResult = ReturnType<
typeof useGetOffersLazyQuery
>;
export type GetOffersQueryResult = ApolloReactCommon.QueryResult<
export type GetOffersQueryResult = QueryResult<
GetOffersQuery,
GetOffersQueryVariables
>;

View File

@ -1,4 +1,4 @@
import gql from 'graphql-tag';
import { gql } from '@apollo/client';
export const GET_HODL_COUNTRIES = gql`
query GetCountries {

View File

@ -1,10 +1,14 @@
import gql from 'graphql-tag';
import * as ApolloReactCommon from '@apollo/react-common';
import * as ApolloReactHooks from '@apollo/react-hooks';
import {
gql,
MutationFunction,
useMutation,
MutationHookOptions,
BaseMutationOptions,
MutationResult,
} from '@apollo/client';
import * as Types from '../../types';
export type AddPeerMutationVariables = Types.Exact<{
auth: Types.AuthType;
url?: Types.Maybe<Types.Scalars['String']>;
publicKey?: Types.Maybe<Types.Scalars['String']>;
socket?: Types.Maybe<Types.Scalars['String']>;
@ -18,14 +22,12 @@ export type AddPeerMutation = { __typename?: 'Mutation' } & Pick<
export const AddPeerDocument = gql`
mutation AddPeer(
$auth: authType!
$url: String
$publicKey: String
$socket: String
$isTemporary: Boolean
) {
addPeer(
auth: $auth
url: $url
publicKey: $publicKey
socket: $socket
@ -33,7 +35,7 @@ export const AddPeerDocument = gql`
)
}
`;
export type AddPeerMutationFn = ApolloReactCommon.MutationFunction<
export type AddPeerMutationFn = MutationFunction<
AddPeerMutation,
AddPeerMutationVariables
>;
@ -51,7 +53,6 @@ export type AddPeerMutationFn = ApolloReactCommon.MutationFunction<
* @example
* const [addPeerMutation, { data, loading, error }] = useAddPeerMutation({
* variables: {
* auth: // value for 'auth'
* url: // value for 'url'
* publicKey: // value for 'publicKey'
* socket: // value for 'socket'
@ -60,21 +61,16 @@ export type AddPeerMutationFn = ApolloReactCommon.MutationFunction<
* });
*/
export function useAddPeerMutation(
baseOptions?: ApolloReactHooks.MutationHookOptions<
AddPeerMutation,
AddPeerMutationVariables
>
baseOptions?: MutationHookOptions<AddPeerMutation, AddPeerMutationVariables>
) {
return ApolloReactHooks.useMutation<
AddPeerMutation,
AddPeerMutationVariables
>(AddPeerDocument, baseOptions);
return useMutation<AddPeerMutation, AddPeerMutationVariables>(
AddPeerDocument,
baseOptions
);
}
export type AddPeerMutationHookResult = ReturnType<typeof useAddPeerMutation>;
export type AddPeerMutationResult = ApolloReactCommon.MutationResult<
AddPeerMutation
>;
export type AddPeerMutationOptions = ApolloReactCommon.BaseMutationOptions<
export type AddPeerMutationResult = MutationResult<AddPeerMutation>;
export type AddPeerMutationOptions = BaseMutationOptions<
AddPeerMutation,
AddPeerMutationVariables
>;

Some files were not shown because too many files have changed in this diff Show More