Merge pull request #112 from apotdevin/release/0.9.0

feat: release 0.9.0
This commit is contained in:
Anthony Potdevin 2020-08-06 08:20:14 +02:00 committed by GitHub
commit 42237a3416
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
351 changed files with 6756 additions and 10866 deletions

3
.env
View file

@ -10,7 +10,6 @@
# Server Configs # Server Configs
# ----------- # -----------
# LOG_LEVEL='info' # LOG_LEVEL='info'
# HODL_KEY='HODL_HODL_API_KEY'
# BASE_PATH='/basePath' # BASE_PATH='/basePath'
# ----------- # -----------
@ -24,9 +23,7 @@
# ----------- # -----------
# FETCH_PRICES=false # FETCH_PRICES=false
# FETCH_FEES=false # FETCH_FEES=false
# HODL_HODL=false
# DISABLE_LINKS=true # DISABLE_LINKS=true
# NO_CLIENT_ACCOUNTS=true
# NO_VERSION_CHECK=true # NO_VERSION_CHECK=true
# ----------- # -----------

17
@types/index.d.ts vendored
View file

@ -3,3 +3,20 @@ declare module '*.jpg';
declare module '*.jpeg'; declare module '*.jpeg';
declare module '*.svg'; declare module '*.svg';
declare module '*.gif'; declare module '*.gif';
/**
* ln-service does not have proper types. This is slightly
* problematic, as this leads to types being `any` **a ton**
* of places.
*
* Here's an issue tracking this: https://github.com/alexbosworth/ln-service/issues/112
*
* It seems like the library is generated from Proto files.
* Could it be an idea to try and generate TypeScript declarations
* from those files?
*/
declare module 'ln-service';
declare module '@alexbosworth/request';
declare module 'balanceofsatoshis/request';
declare module 'balanceofsatoshis/swaps';
declare module 'balanceofsatoshis/balances';

View file

@ -14,9 +14,6 @@ COPY package.json .
COPY package-lock.json . COPY package-lock.json .
RUN npm install --production --silent RUN npm install --production --silent
# Install dependencies necessary for build and start
RUN npm install -D cross-env typescript @types/react @next/bundle-analyzer
# --------------- # ---------------
# Build App # Build App
# --------------- # ---------------

View file

@ -102,22 +102,7 @@ This repository consists of a **NextJS** server that handles both the backend **
## **Requirements** ## **Requirements**
- Yarn/npm installed - Yarn/npm installed
- Node installed (Version 12.16.0 or higher) - Node installed (Version 10 or higher)
**Older Versions of Node**
Earlier versions of Node can be used if you replace the following commands:
```js
//Yarn
yarn start -> yarn start:compatible
yarn dev -> yarn dev:compatible
//NPM
npm start -> npm start:compatible
npm run dev -> npm run dev:compatible
```
**HodlHodl integration will not work with older versions of Node!**
--- ---
@ -132,7 +117,6 @@ You can define some environment variables that ThunderHub can start with. To do
# Server Configs # Server Configs
# ----------- # -----------
LOG_LEVEL = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly' # Default: 'info' LOG_LEVEL = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly' # Default: 'info'
HODL_KEY = '[Key provided by HodlHodl]' # Default: ''
BASE_PATH = '[Base path where you want to have thunderhub running i.e. '/btcpay']' # Default: '' BASE_PATH = '[Base path where you want to have thunderhub running i.e. '/btcpay']' # Default: ''
# ----------- # -----------
@ -146,9 +130,7 @@ CURRENCY = 'sat' | 'btc' | 'fiat' # Default: 'sat'
# ----------- # -----------
FETCH_PRICES = true | false # Default: true FETCH_PRICES = true | false # Default: true
FETCH_FEES = true | false # Default: true FETCH_FEES = true | false # Default: true
HODL_HODL = true | false # Default: true
DISABLE_LINKS = true | false # Default: false DISABLE_LINKS = true | false # Default: false
NO_CLIENT_ACCOUNTS = true | false # Default: false
NO_VERSION_CHECK = true | false # Default: false NO_VERSION_CHECK = true | false # Default: false
``` ```
@ -252,16 +234,6 @@ ThunderHub shows you links for quick viewing of nodes by public key on [1ml.com]
If you don't want to show these links, you can set `DISABLE_LINKS=true` in your `.env` file. If you don't want to show these links, you can set `DISABLE_LINKS=true` in your `.env` file.
**HodlHodl**
ThunderHub has a HodlHodl integration to view offers from this platform.
If you want to disable this integration, you can set `HODL_HODL=false` in your `.env` file.
**Client Accounts**
ThunderHub allows you to create accounts on the browser which are also encrypted and stored in the same browser.
If you want to disable this option and only allow accounts that are created on the server, you can set `NO_CLIENT_ACCOUNTS=true` in your `.env` file.
**Version Check** **Version Check**
ThunderHub gets the latest available version from [Github](https://api.github.com/repos/apotdevin/thunderhub/releases/latest) and shows you a message if you are on an older version. ThunderHub gets the latest available version from [Github](https://api.github.com/repos/apotdevin/thunderhub/releases/latest) and shows you a message if you are on an older version.

View file

@ -14,9 +14,6 @@ COPY package.json .
COPY package-lock.json . COPY package-lock.json .
RUN npm install --production --silent RUN npm install --production --silent
# Install dependencies necessary for build and start
RUN npm install -D cross-env typescript @types/react @next/bundle-analyzer
# --------------- # ---------------
# Build App # Build App
# --------------- # ---------------

View file

@ -14,9 +14,6 @@ COPY package.json .
COPY package-lock.json . COPY package-lock.json .
RUN npm install --production --silent RUN npm install --production --silent
# Install dependencies necessary for build and start
RUN npm install -D cross-env typescript @types/react @next/bundle-analyzer
# --------------- # ---------------
# Build App # Build App
# --------------- # ---------------

View file

@ -17,6 +17,7 @@ generates:
withComponent: false withComponent: false
withHOC: false withHOC: false
withHooks: true withHooks: true
reactApolloVersion: 3
plugins: plugins:
- 'typescript-operations' - 'typescript-operations'
- 'typescript-react-apollo' - 'typescript-react-apollo'

View file

@ -1,32 +1,29 @@
/* eslint @typescript-eslint/no-var-requires: 0 */ /* eslint @typescript-eslint/no-var-requires: 0 */
import * as React from 'react'; import { IncomingMessage, ServerResponse } from 'http';
import Head from 'next/head'; import { useMemo } from 'react';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { import {
ApolloClient,
InMemoryCache, InMemoryCache,
IntrospectionFragmentMatcher, NormalizedCacheObject,
} from 'apollo-cache-inmemory'; } from '@apollo/client';
import getConfig from 'next/config'; import getConfig from 'next/config';
import introspectionQueryResultData from 'src/graphql/fragmentTypes.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
let globalApolloClient = null;
const { publicRuntimeConfig } = getConfig(); const { publicRuntimeConfig } = getConfig();
const { apiUrl: uri } = publicRuntimeConfig; const { apiUrl: uri } = publicRuntimeConfig;
function createIsomorphLink(ctx) { let apolloClient: ReturnType<typeof createApolloClient> | null = null;
if (typeof window === 'undefined') {
const { SchemaLink } = require('apollo-link-schema');
const schema = require('server/schema');
return new SchemaLink({ schema, context: ctx });
} else {
const { HttpLink } = require('apollo-link-http');
function createIsomorphLink(req?: IncomingMessage, res?: ServerResponse) {
if (typeof window === 'undefined') {
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/client/link/http');
return new HttpLink({ return new HttpLink({
uri, uri,
credentials: 'same-origin', credentials: 'same-origin',
@ -34,132 +31,38 @@ function createIsomorphLink(ctx) {
} }
} }
/** function createApolloClient(req?: IncomingMessage, res?: ServerResponse) {
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient(ctx = {}, initialState = {}) {
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
return new ApolloClient({ return new ApolloClient({
ssrMode, credentials: 'same-origin',
link: createIsomorphLink(ctx), ssrMode: typeof window === 'undefined',
cache, link: createIsomorphLink(req, res),
cache: new InMemoryCache({
possibleTypes: { Transaction: ['InvoiceType', 'PaymentType'] },
}),
}); });
} }
/** export function initializeApollo(
* Always creates a new apollo client on the server initialState: NormalizedCacheObject | null = null,
* Creates or reuses apollo client in the browser. req?: IncomingMessage,
* @param {Object} initialState res?: ServerResponse
*/ ) {
function initApolloClient(ctx, initialState?) { const _apolloClient = apolloClient ?? createApolloClient(req, res);
// 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 your page has Next.js data fetching methods that use Apollo Client, the initial state
if (!globalApolloClient) { // get hydrated here
globalApolloClient = createApolloClient(ctx, initialState); 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;
return globalApolloClient; return _apolloClient;
} }
/** export function useApollo(initialState: NormalizedCacheObject | null) {
* Creates and provides the apolloContext const store = useMemo(() => initializeApollo(initialState), [initialState]);
* to a next.js PageTree. Use it by wrapping return store;
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(undefined, apolloState);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// 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 (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient({
res: ctx.res,
req: ctx.req,
}));
// 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;
}
// 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;
} }

View file

@ -6,10 +6,10 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
module.exports = withBundleAnalyzer({ module.exports = withBundleAnalyzer({
poweredByHeader: false, poweredByHeader: false,
assetPrefix: process.env.BASE_PATH || '', assetPrefix: process.env.BASE_PATH || '',
basePath: process.env.BASE_PATH || '',
serverRuntimeConfig: { serverRuntimeConfig: {
nodeEnv: process.env.NODE_ENV || 'development', nodeEnv: process.env.NODE_ENV || 'development',
logLevel: process.env.LOG_LEVEL || 'info', logLevel: process.env.LOG_LEVEL || 'info',
hodlKey: process.env.HODL_KEY || '',
cookiePath: process.env.COOKIE_PATH || '', cookiePath: process.env.COOKIE_PATH || '',
lnServerUrl: process.env.SSO_SERVER_URL || '', lnServerUrl: process.env.SSO_SERVER_URL || '',
lnCertPath: process.env.SSO_CERT_PATH || '', lnCertPath: process.env.SSO_CERT_PATH || '',
@ -20,15 +20,12 @@ module.exports = withBundleAnalyzer({
nodeEnv: process.env.NODE_ENV || 'development', nodeEnv: process.env.NODE_ENV || 'development',
apiUrl: `${process.env.BASE_PATH || ''}/api/v1`, apiUrl: `${process.env.BASE_PATH || ''}/api/v1`,
apiBaseUrl: `${process.env.API_BASE_URL || ''}/api/v1`, apiBaseUrl: `${process.env.API_BASE_URL || ''}/api/v1`,
basePath: process.env.BASE_PATH || '',
npmVersion: process.env.npm_package_version || '0.0.0', npmVersion: process.env.npm_package_version || '0.0.0',
defaultTheme: process.env.THEME || 'dark', defaultTheme: process.env.THEME || 'dark',
defaultCurrency: process.env.CURRENCY || 'sat', defaultCurrency: process.env.CURRENCY || 'sat',
fetchPrices: process.env.FETCH_PRICES === 'false' ? false : true, fetchPrices: process.env.FETCH_PRICES === 'false' ? false : true,
fetchFees: process.env.FETCH_FEES === 'false' ? false : true, fetchFees: process.env.FETCH_FEES === 'false' ? false : true,
hodlhodl: process.env.HODL_HODL === 'false' ? false : true,
disableLinks: process.env.DISABLE_LINKS === 'true' ? true : false, 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, noVersionCheck: process.env.NO_VERSION_CHECK === 'true' ? true : false,
}, },
}); });

4439
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,22 +5,19 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"bs": "yarn build && yarn start", "bs": "yarn build && yarn start",
"dev": "cross-env NODE_OPTIONS='--insecure-http-parser' next", "dev": "next",
"dev:compatible": "next",
"build": "next build", "build": "next build",
"start": "cross-env NODE_OPTIONS='--insecure-http-parser' next start", "start": "next start",
"start:two": "cross-env NODE_OPTIONS='--insecure-http-parser' next start -p 3001", "start:two": "next start -p 3001",
"start:compatible": "next start",
"start:compatible:two": "next start -p 3001",
"start:cookie": "sh ./scripts/initCookie.sh", "start:cookie": "sh ./scripts/initCookie.sh",
"lint": "eslint */**/*.{js,ts,tsx} --fix", "lint": "eslint . --ext ts --ext tsx --ext js",
"prettier": "prettier --write **/*.{ts,tsx,js,css,html}", "format": "prettier --write \"**/*.{js,ts,tsx}\"",
"release": "standard-version --sign", "release": "standard-version --sign",
"release:push": "standard-version --sign && git push --follow-tags origin master", "release:push": "standard-version --sign && git push --follow-tags origin master",
"release:test": "standard-version --sign --dry-run", "release:test": "standard-version --sign --dry-run",
"release:minor": "standard-version --sign --release-as minor && git push --follow-tags origin master", "release:minor": "standard-version --sign --release-as minor && git push --follow-tags origin master",
"analyze": "cross-env ANALYZE=true next build", "analyze": "npx 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": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
@ -37,35 +34,27 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@apollo/react-common": "^3.1.4", "@apollo/client": "^3.1.2",
"@apollo/react-hooks": "^3.1.5", "@next/bundle-analyzer": "^9.5.1",
"@apollo/react-ssr": "^3.1.5", "@types/react": "^16.9.44",
"apollo-cache-inmemory": "^1.6.6", "apollo-server-micro": "^2.16.1",
"apollo-client": "^2.6.10",
"apollo-link-http": "^1.5.17",
"apollo-link-schema": "^1.2.5",
"apollo-server-micro": "^2.15.1",
"apollo-utilities": "^1.3.4",
"balanceofsatoshis": "^5.40.2", "balanceofsatoshis": "^5.40.2",
"base64url": "^3.0.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"crypto-js": "^4.0.0", "date-fns": "^2.15.0",
"date-fns": "^2.14.0",
"graphql": "^15.3.0", "graphql": "^15.3.0",
"graphql-iso-date": "^3.6.1", "graphql-iso-date": "^3.6.1",
"graphql-rate-limit": "^2.0.1", "graphql-rate-limit": "^2.0.1",
"graphql-tag": "^2.10.4",
"intersection-observer": "^0.11.0", "intersection-observer": "^0.11.0",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"js-yaml": "^3.14.0", "js-yaml": "^3.14.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"ln-service": "^49.3.1", "ln-service": "^49.4.3",
"lodash.debounce": "^4.0.8", "lodash.debounce": "^4.0.8",
"lodash.groupby": "^4.6.0", "lodash.groupby": "^4.6.0",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"lodash.omit": "^4.5.0", "lodash.omit": "^4.5.0",
"next": "^9.4.4", "next": "^9.5.1",
"numeral": "^2.0.6", "numeral": "^2.0.6",
"qrcode.react": "^1.0.0", "qrcode.react": "^1.0.0",
"react": "^16.13.1", "react": "^16.13.1",
@ -77,63 +66,73 @@
"react-spinners": "^0.9.0", "react-spinners": "^0.9.0",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-toastify": "^6.0.8", "react-toastify": "^6.0.8",
"react-tooltip": "^4.2.7", "react-tooltip": "^4.2.8",
"styled-components": "^5.1.1", "styled-components": "^5.1.1",
"styled-react-modal": "^2.0.1", "styled-react-modal": "^2.0.1",
"styled-theming": "^2.2.0", "styled-theming": "^2.2.0",
"typescript": "^3.9.7",
"underscore": "^1.10.2", "underscore": "^1.10.2",
"uuid": "^8.2.0", "uuid": "^8.3.0",
"victory": "^35.0.3", "victory": "^35.0.8",
"winston": "^3.3.3", "winston": "^3.3.3"
"zxcvbn": "^4.4.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.10.4", "@babel/core": "^7.11.1",
"@commitlint/cli": "^9.0.1", "@commitlint/cli": "^9.1.1",
"@commitlint/config-conventional": "^9.0.1", "@commitlint/config-conventional": "^9.1.1",
"@graphql-codegen/cli": "^1.17.0", "@graphql-codegen/cli": "^1.17.7",
"@graphql-codegen/fragment-matcher": "^1.17.0", "@graphql-codegen/fragment-matcher": "^1.17.7",
"@graphql-codegen/introspection": "^1.17.0", "@graphql-codegen/introspection": "^1.17.7",
"@graphql-codegen/near-operation-file-preset": "^1.17.0", "@graphql-codegen/near-operation-file-preset": "^1.17.8",
"@graphql-codegen/typescript": "^1.17.0", "@graphql-codegen/typescript": "^1.17.7",
"@graphql-codegen/typescript-operations": "^1.17.0", "@graphql-codegen/typescript-operations": "^1.17.7",
"@graphql-codegen/typescript-react-apollo": "^1.17.0", "@graphql-codegen/typescript-react-apollo": "^2.0.5",
"@graphql-codegen/typescript-resolvers": "^1.17.0", "@graphql-codegen/typescript-resolvers": "^1.17.7",
"@next/bundle-analyzer": "^9.4.4", "@testing-library/jest-dom": "^5.11.2",
"@testing-library/jest-dom": "^5.11.0", "@testing-library/react": "^10.4.8",
"@testing-library/react": "^10.4.5", "@types/bcryptjs": "^2.4.2",
"@types/cookie": "^0.4.0",
"@types/graphql-iso-date": "^3.4.0",
"@types/js-cookie": "^2.2.6",
"@types/js-yaml": "^3.12.5",
"@types/jsonwebtoken": "^8.5.0", "@types/jsonwebtoken": "^8.5.0",
"@types/node": "^14.0.22", "@types/lodash.groupby": "^4.6.6",
"@types/react": "^16.9.43", "@types/lodash.merge": "^4.6.6",
"@types/styled-components": "^5.1.1", "@types/lodash.omit": "^4.5.6",
"@types/styled-theming": "^2.2.4", "@types/lodash.sortby": "^4.7.6",
"@typescript-eslint/eslint-plugin": "^3.6.0", "@types/node": "^14.0.27",
"@typescript-eslint/parser": "^3.6.0", "@types/numeral": "0.0.28",
"apollo-server": "^2.15.1", "@types/qrcode.react": "^1.0.1",
"apollo-server-testing": "^2.15.1", "@types/react-copy-to-clipboard": "^4.3.0",
"babel-jest": "^26.1.0", "@types/styled-components": "^5.1.2",
"@types/styled-react-modal": "^1.2.0",
"@types/styled-theming": "^2.2.5",
"@types/underscore": "^1.10.18",
"@types/uuid": "^8.0.1",
"@typescript-eslint/eslint-plugin": "^3.8.0",
"@typescript-eslint/parser": "^3.8.0",
"apollo-server": "^2.16.1",
"apollo-server-testing": "^2.16.1",
"babel-jest": "^26.2.2",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",
"babel-plugin-inline-react-svg": "^1.1.1", "babel-plugin-inline-react-svg": "^1.1.1",
"babel-plugin-styled-components": "^1.10.7", "babel-plugin-styled-components": "^1.11.1",
"babel-preset-react-app": "^9.1.2", "babel-preset-react-app": "^9.1.2",
"cross-env": "^7.0.2",
"devmoji": "^2.1.9", "devmoji": "^2.1.9",
"eslint": "^7.4.0", "eslint": "^7.6.0",
"eslint-config-prettier": "^6.11.0", "eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^23.18.0", "eslint-plugin-jest": "^23.20.0",
"eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.1.4", "eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.3", "eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-hooks": "^4.0.7", "eslint-plugin-react-hooks": "^4.0.8",
"fast-diff": "^1.2.0",
"husky": "^4.2.5", "husky": "^4.2.5",
"jest": "^26.1.0", "jest": "^26.2.2",
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "^3.0.3",
"lint-staged": "^10.2.11", "lint-staged": "^10.2.11",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"standard-version": "^8.0.2", "standard-version": "^8.0.2"
"typescript": "^3.9.6"
}, },
"husky": { "husky": {
"hooks": { "hooks": {

View file

@ -4,6 +4,9 @@ import { ModalProvider, BaseModalBackground } from 'styled-react-modal';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Head from 'next/head'; import Head from 'next/head';
import { StyledToastContainer } from 'src/components/toastContainer/ToastContainer'; import { StyledToastContainer } from 'src/components/toastContainer/ToastContainer';
import { AppProps } from 'next/app';
import { ApolloProvider } from '@apollo/client';
import { useApollo } from 'config/client';
import { ContextProvider } from '../src/context/ContextProvider'; import { ContextProvider } from '../src/context/ContextProvider';
import { useConfigState, ConfigProvider } from '../src/context/ConfigContext'; import { useConfigState, ConfigProvider } from '../src/context/ConfigContext';
import { GlobalStyles } from '../src/styles/GlobalStyle'; import { GlobalStyles } from '../src/styles/GlobalStyle';
@ -11,7 +14,6 @@ import { Header } from '../src/layouts/header/Header';
import { Footer } from '../src/layouts/footer/Footer'; import { Footer } from '../src/layouts/footer/Footer';
import 'react-toastify/dist/ReactToastify.min.css'; import 'react-toastify/dist/ReactToastify.min.css';
import { PageWrapper, HeaderBodyWrapper } from '../src/layouts/Layout.styled'; import { PageWrapper, HeaderBodyWrapper } from '../src/layouts/Layout.styled';
import { parseCookies } from '../src/utils/cookies';
import 'react-circular-progressbar/dist/styles.css'; import 'react-circular-progressbar/dist/styles.css';
const Wrapper: React.FC = ({ children }) => { const Wrapper: React.FC = ({ children }) => {
@ -36,34 +38,21 @@ const Wrapper: React.FC = ({ children }) => {
); );
}; };
const App = ({ Component, pageProps, initialConfig }) => ( export default function App({ Component, pageProps }: AppProps) {
<> const apolloClient = useApollo(pageProps.initialApolloState);
<Head> return (
<title>ThunderHub - Lightning Node Manager</title> <ApolloProvider client={apolloClient}>
</Head> <Head>
<ConfigProvider initialConfig={initialConfig}> <title>ThunderHub - Lightning Node Manager</title>
<ContextProvider> </Head>
<Wrapper> <ConfigProvider initialConfig={pageProps.initialConfig}>
<Component {...pageProps} /> <ContextProvider>
</Wrapper> <Wrapper>
</ContextProvider> <Component {...pageProps} />
</ConfigProvider> </Wrapper>
<StyledToastContainer /> </ContextProvider>
</> </ConfigProvider>
); <StyledToastContainer />
</ApolloProvider>
App.getInitialProps = async props => { );
const cookies = parseCookies(props.ctx.req); }
if (!cookies?.theme) {
return { initialConfig: 'dark' };
}
try {
const initialConfig = cookies.theme || 'dark';
return { initialConfig };
} catch (error) {
return { initialConfig: 'dark' };
}
};
export default App;

View file

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

View file

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; 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 { ChainTransactions } from '../src/views/chain/transactions/ChainTransactions';
import { ChainUtxos } from '../src/views/chain/utxos/ChainUtxos'; import { ChainUtxos } from '../src/views/chain/utxos/ChainUtxos';
@ -20,4 +22,8 @@ const Wrapped = () => (
</GridWrapper> </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 React, { useState, useEffect } from 'react';
import { useAccountState } from 'src/context/AccountContext';
import { useGetChannelAmountInfoQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated'; import { useGetChannelAmountInfoQuery } from 'src/graphql/queries/__generated__/getNodeInfo.generated';
import styled from 'styled-components'; import styled from 'styled-components';
import { Settings } from 'react-feather'; import { Settings } from 'react-feather';
import { IconCursor } from 'src/views/channels/channels/Channel.style'; import { IconCursor } from 'src/views/channels/channels/Channel.style';
import { ChannelManage } from 'src/views/channels/channels/ChannelManage'; import { ChannelManage } from 'src/views/channels/channels/ChannelManage';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; 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 { Channels } from '../src/views/channels/channels/Channels';
import { PendingChannels } from '../src/views/channels/pendingChannels/PendingChannels'; import { PendingChannels } from '../src/views/channels/pendingChannels/PendingChannels';
import { ClosedChannels } from '../src/views/channels/closedChannels/ClosedChannels'; import { ClosedChannels } from '../src/views/channels/closedChannels/ClosedChannels';
@ -41,12 +42,7 @@ const ChannelView = () => {
closed: 0, closed: 0,
}); });
const { auth } = useAccountState(); const { data } = useGetChannelAmountInfoQuery();
const { data } = useGetChannelAmountInfoQuery({
skip: !auth,
variables: { auth },
});
useEffect(() => { useEffect(() => {
if (data && data.getNodeInfo) { if (data && data.getNodeInfo) {
@ -119,4 +115,8 @@ const Wrapped = () => (
</GridWrapper> </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,12 @@ import * as React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Users } from 'react-feather'; import { Users } from 'react-feather';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { ChatInit } from 'src/components/chat/ChatInit'; import { ChatInit } from 'src/components/chat/ChatInit';
import { ChatFetcher } from 'src/components/chat/ChatFetcher'; 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 { useNodeInfo } from 'src/hooks/UseNodeInfo';
import { useChatState } from '../src/context/ChatContext'; import { useChatState } from '../src/context/ChatContext';
import { separateBySender, getSenders } from '../src/utils/chat'; import { separateBySender, getSenders } from '../src/utils/chat';
import { import {
@ -16,7 +19,6 @@ import {
import { Contacts } from '../src/views/chat/Contacts'; import { Contacts } from '../src/views/chat/Contacts';
import { ChatBox } from '../src/views/chat/ChatBox'; import { ChatBox } from '../src/views/chat/ChatBox';
import { ChatStart } from '../src/views/chat/ChatStart'; import { ChatStart } from '../src/views/chat/ChatStart';
import { useStatusState } from '../src/context/StatusContext';
import { Text } from '../src/components/typography/Styled'; import { Text } from '../src/components/typography/Styled';
import { LoadingCard } from '../src/components/loading/LoadingCard'; import { LoadingCard } from '../src/components/loading/LoadingCard';
import { ChatCard } from '../src/views/chat/Chat.styled'; import { ChatCard } from '../src/views/chat/Chat.styled';
@ -29,14 +31,44 @@ const ChatLayout = styled.div`
withHeight && 'height: 600px'} withHeight && 'height: 600px'}
`; `;
type State = {
user: string;
showContacts: boolean;
};
type Action =
| {
type: 'setUserAndHide' | 'setUser';
user: string;
}
| { type: 'toggleShow' };
const initialState: State = { user: '', showContacts: false };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'setUser':
return { ...state, user: action.user };
case 'setUserAndHide':
return { user: action.user, showContacts: false };
case 'toggleShow':
return { ...state, showContacts: !state.showContacts };
default:
return state;
}
};
const ChatView = () => { const ChatView = () => {
const { minorVersion } = useStatusState(); const { minorVersion } = useNodeInfo();
const { chats, sender, sentChats, initialized } = useChatState(); const { chats, sender, sentChats, initialized } = useChatState();
const bySender = separateBySender([...chats, ...sentChats]); const bySender = separateBySender([...chats, ...sentChats]);
const senders = getSenders(bySender); const senders = getSenders(bySender) || [];
const [user, setUser] = React.useState(''); const [state, dispatch] = React.useReducer(reducer, initialState);
const [showContacts, setShowContacts] = React.useState(false); const { user, showContacts } = state;
const setUser = (user: string) => dispatch({ type: 'setUserAndHide', user });
const setName = (user: string) => dispatch({ type: 'setUser', user });
if (!initialized) { if (!initialized) {
return <LoadingCard title={'Chats'} />; return <LoadingCard title={'Chats'} />;
@ -66,7 +98,7 @@ const ChatView = () => {
contacts={senders} contacts={senders}
user={user} user={user}
setUser={setUser} setUser={setUser}
setShow={setShowContacts} setName={setName}
/> />
); );
} }
@ -76,11 +108,11 @@ const ChatView = () => {
contacts={senders} contacts={senders}
user={user} user={user}
setUser={setUser} setUser={setUser}
setShow={setShowContacts} setName={setName}
hide={true} hide={true}
/> />
{user === 'New Chat' ? ( {user === 'New Chat' ? (
<ChatStart noTitle={true} /> <ChatStart noTitle={true} callback={() => setUser('')} />
) : ( ) : (
<ChatBox messages={bySender[sender]} alias={user} /> <ChatBox messages={bySender[sender]} alias={user} />
)} )}
@ -99,7 +131,7 @@ const ChatView = () => {
</ViewSwitch> </ViewSwitch>
<ViewSwitch> <ViewSwitch>
<SingleLine> <SingleLine>
<ColorButton onClick={() => setShowContacts(prev => !prev)}> <ColorButton onClick={() => dispatch({ type: 'toggleShow' })}>
<Users size={18} /> <Users size={18} />
</ColorButton> </ColorButton>
<SubTitle>{user}</SubTitle> <SubTitle>{user}</SubTitle>
@ -109,7 +141,7 @@ const ChatView = () => {
)} )}
<ChatCard mobileCardPadding={'0'}> <ChatCard mobileCardPadding={'0'}>
{chats.length <= 0 && sentChats.length <= 0 ? ( {chats.length <= 0 && sentChats.length <= 0 ? (
<ChatStart /> <ChatStart callback={() => setUser('')} />
) : ( ) : (
renderChats() renderChats()
)} )}
@ -126,4 +158,8 @@ const Wrapped = () => (
</GridWrapper> </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 React, { useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { ChevronRight, ChevronUp, ChevronDown } from 'react-feather'; import { ChevronRight, ChevronUp, ChevronDown } from 'react-feather';
import { useAccountState } from 'src/context/AccountContext';
import { useChannelFeesQuery } from 'src/graphql/queries/__generated__/getChannelFees.generated'; import { useChannelFeesQuery } from 'src/graphql/queries/__generated__/getChannelFees.generated';
import { useUpdateFeesMutation } from 'src/graphql/mutations/__generated__/updateFees.generated'; import { useUpdateFeesMutation } from 'src/graphql/mutations/__generated__/updateFees.generated';
import { InputWithDeco } from 'src/components/input/InputWithDeco'; import { InputWithDeco } from 'src/components/input/InputWithDeco';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import styled from 'styled-components'; import styled from 'styled-components';
import { useStatusState } from 'src/context/StatusContext';
import { ChannelFeeType } from 'src/graphql/types'; 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 { useNodeInfo } from 'src/hooks/UseNodeInfo';
import { import {
Card, Card,
CardWithTitle, CardWithTitle,
@ -22,15 +24,13 @@ import {
import { getErrorContent } from '../src/utils/error'; import { getErrorContent } from '../src/utils/error';
import { LoadingCard } from '../src/components/loading/LoadingCard'; import { LoadingCard } from '../src/components/loading/LoadingCard';
import { FeeCard } from '../src/views/fees/FeeCard'; 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` const WithPointer = styled.div`
cursor: pointer; cursor: pointer;
`; `;
const FeesView = () => { const FeesView = () => {
const { minorVersion, revision } = useStatusState(); const { minorVersion, revision } = useNodeInfo();
const canMax = (minorVersion === 7 && revision > 1) || minorVersion > 7; const canMax = (minorVersion === 7 && revision > 1) || minorVersion > 7;
const canMin = (minorVersion === 8 && revision > 2) || minorVersion > 8; const canMin = (minorVersion === 8 && revision > 2) || minorVersion > 8;
@ -43,11 +43,7 @@ const FeesView = () => {
const [max, setMax] = useState(0); const [max, setMax] = useState(0);
const [min, setMin] = useState(0); const [min, setMin] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useChannelFeesQuery({ const { loading, data } = useChannelFeesQuery({
skip: !auth,
variables: { auth },
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
}); });
@ -73,101 +69,107 @@ const FeesView = () => {
return ( return (
<> <>
<AdminSwitch> <Card>
<Card> <WithPointer>
<WithPointer> <SingleLine onClick={() => setIsEdit(prev => !prev)}>
<SingleLine onClick={() => setIsEdit(prev => !prev)}> <Sub4Title>Update All Channel Details</Sub4Title>
<Sub4Title>Update All Channel Details</Sub4Title> {isEdit ? <ChevronUp /> : <ChevronDown />}
{isEdit ? <ChevronUp /> : <ChevronDown />} </SingleLine>
</SingleLine> </WithPointer>
</WithPointer> {isEdit && (
{isEdit && ( <>
<> <Separation />
<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 <InputWithDeco
title={'BaseFee'} title={'Max HTLC'}
value={baseFee} value={max}
placeholder={'sats'} placeholder={'sats'}
amount={baseFee} amount={max}
override={'sat'} override={'sat'}
inputType={'number'} inputType={'number'}
inputCallback={value => setBaseFee(Number(value))} inputCallback={value => setMax(Number(value))}
/> />
)}
{canMin && (
<InputWithDeco <InputWithDeco
title={'Fee Rate'} title={'Min HTLC'}
value={feeRate} value={min}
placeholder={'ppm'} placeholder={'sats'}
amount={feeRate} amount={min}
override={'ppm'} override={'sat'}
inputType={'number'} inputType={'number'}
inputCallback={value => setFeeRate(Number(value))} inputCallback={value => setMin(Number(value))}
/> />
<InputWithDeco )}
title={'CLTV Delta'} <RightAlign>
value={cltv} <ColorButton
placeholder={'cltv delta'} onClick={() =>
customAmount={cltv ? cltv.toString() : ''} updateFees({
inputType={'number'} variables: {
inputCallback={value => setCLTV(Number(value))} ...(baseFee !== 0 && { base_fee_tokens: baseFee }),
/> ...(feeRate !== 0 && { fee_rate: feeRate }),
{canMax && ( ...(cltv !== 0 && { cltv_delta: cltv }),
<InputWithDeco ...(max !== 0 &&
title={'Max HTLC'} canMax && {
value={max} max_htlc_mtokens: (max * 1000).toString(),
placeholder={'sats'} }),
amount={max} ...(min !== 0 &&
override={'sat'} canMin && {
inputType={'number'} min_htlc_mtokens: (min * 1000).toString(),
inputCallback={value => setMax(Number(value))} }),
/> },
)} })
{canMin && ( }
<InputWithDeco disabled={
title={'Min HTLC'} baseFee === 0 &&
value={min} feeRate === 0 &&
placeholder={'sats'} cltv === 0 &&
amount={min} max === 0 &&
override={'sat'} min === 0
inputType={'number'} }
inputCallback={value => setMin(Number(value))} fullWidth={true}
/> withMargin={'16px 0 0'}
)} >
<RightAlign> Update Fees
<SecureButton <ChevronRight size={18} />
callback={updateFees} </ColorButton>
variables={{ </RightAlign>
...(baseFee !== 0 && { base_fee_tokens: baseFee }), </>
...(feeRate !== 0 && { fee_rate: feeRate }), )}
...(cltv !== 0 && { cltv_delta: cltv }), </Card>
...(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>
<CardWithTitle> <CardWithTitle>
<SubTitle>Channel Details</SubTitle> <SubTitle>Channel Details</SubTitle>
<Card mobileCardPadding={'0'} mobileNoBackground={true}> <Card mobileCardPadding={'0'} mobileNoBackground={true}>
{data.getChannelFees.map((channel: ChannelFeeType, index: number) => ( {data.getChannelFees.map((channel, index) => (
<FeeCard <FeeCard
channel={channel} channel={channel as ChannelFeeType}
index={index + 1} index={index + 1}
setIndexOpen={setIndexOpen} setIndexOpen={setIndexOpen}
indexOpen={indexOpen} indexOpen={indexOpen}
@ -186,4 +188,8 @@ const Wrapped = () => (
</GridWrapper> </GridWrapper>
); );
export default withApollo(Wrapped); export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [CHANNEL_FEES]);
}

View file

@ -1,9 +1,11 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { useGetForwardsQuery } from 'src/graphql/queries/__generated__/getForwards.generated'; import { useGetForwardsQuery } from 'src/graphql/queries/__generated__/getForwards.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; 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 { import {
SubTitle, SubTitle,
Card, Card,
@ -28,11 +30,8 @@ const ForwardsView = () => {
const [time, setTime] = useState('week'); const [time, setTime] = useState('week');
const [indexOpen, setIndexOpen] = useState(0); const [indexOpen, setIndexOpen] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useGetForwardsQuery({ const { loading, data } = useGetForwardsQuery({
skip: !auth, variables: { time },
variables: { auth, time },
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
}); });
@ -63,11 +62,13 @@ const ForwardsView = () => {
{renderButton('threeMonths', '3M')} {renderButton('threeMonths', '3M')}
</SingleLine> </SingleLine>
</CardTitle> </CardTitle>
{data.getForwards.forwards.length <= 0 && renderNoForwards()} {data?.getForwards?.forwards &&
data.getForwards.forwards.length <= 0 &&
renderNoForwards()}
<Card mobileCardPadding={'0'} mobileNoBackground={true}> <Card mobileCardPadding={'0'} mobileNoBackground={true}>
{data.getForwards.forwards.map((forward, index: number) => ( {data?.getForwards?.forwards?.map((forward, index) => (
<ForwardCard <ForwardCard
forward={forward} forward={forward as ForwardType}
key={index} key={index}
index={index + 1} index={index + 1}
setIndexOpen={setIndexOpen} setIndexOpen={setIndexOpen}
@ -86,4 +87,8 @@ const Wrapped = () => (
</GridWrapper> </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 React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { Version } from 'src/components/version/Version'; 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 { NetworkInfo } from '../src/views/home/networkInfo/NetworkInfo';
import { AccountInfo } from '../src/views/home/account/AccountInfo'; import { AccountInfo } from '../src/views/home/account/AccountInfo';
import { QuickActions } from '../src/views/home/quickActions/QuickActions'; 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 { ForwardBox } from '../src/views/home/reports/forwardReport';
import { LiquidReport } from '../src/views/home/reports/liquidReport/LiquidReport'; import { LiquidReport } from '../src/views/home/reports/liquidReport/LiquidReport';
import { ConnectCard } from '../src/views/home/connect/Connect'; import { ConnectCard } from '../src/views/home/connect/Connect';
import { NodeBar } from '../src/components/nodeInfo/NodeBar';
const HomeView = () => { const HomeView = () => {
return ( return (
<> <>
<Version /> <Version />
<AccountInfo /> <AccountInfo />
<NodeBar />
<ConnectCard /> <ConnectCard />
<QuickActions /> <QuickActions />
<FlowBox /> <FlowBox />
@ -33,4 +33,8 @@ const Wrapped = () => (
</GridWrapper> </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,21 @@
import * as React from 'react'; import * as React from 'react';
import { Spacer } from 'src/components/spacer/Spacer'; import { Spacer } from 'src/components/spacer/Spacer';
import { withApollo } from 'config/client';
import { ServerAccounts } from 'src/components/accounts/ServerAccounts'; 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 { ThunderStorm } from 'src/views/homepage/HomePage.styled';
import { appendBasePath } from 'src/utils/basePath'; import { NextPageContext } from 'next';
import { SessionLogin } from '../src/views/login/SessionLogin'; import { GET_SERVER_ACCOUNTS } from 'src/graphql/queries/getServerAccounts';
import { getProps } from 'src/utils/ssr';
import { TopSection } from '../src/views/homepage/Top'; import { TopSection } from '../src/views/homepage/Top';
import { LoginBox } from '../src/views/homepage/LoginBox';
import { Accounts } from '../src/views/homepage/Accounts'; 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 ContextApp = () => (
const { noClient } = publicRuntimeConfig; <>
<ThunderStorm alt={''} src={'/static/thunderstorm.gif'} />
const ContextApp = () => { <TopSection />
const { finishedFetch } = useAccountState(); <Accounts />
<Spacer />
return ( </>
<> );
<ThunderStorm alt={''} src={appendBasePath('/static/thunderstorm.gif')} />
<TopSection />
{!finishedFetch && (
<Section color={'transparent'}>
<LoadingCard loadingHeight={'160px'} />
</Section>
)}
{finishedFetch && (
<>
<SessionLogin />
<Accounts />
{!noClient && <LoginBox />}
</>
)}
<Spacer />
</>
);
};
const Wrapped = () => ( const Wrapped = () => (
<> <>
@ -47,4 +24,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,8 +1,10 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useAccountState } from 'src/context/AccountContext';
import { useGetPeersQuery } from 'src/graphql/queries/__generated__/getPeers.generated'; import { useGetPeersQuery } from 'src/graphql/queries/__generated__/getPeers.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; 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 { import {
CardWithTitle, CardWithTitle,
SubTitle, SubTitle,
@ -14,14 +16,10 @@ import { AddPeer } from '../src/views/peers/AddPeer';
const PeersView = () => { const PeersView = () => {
const [indexOpen, setIndexOpen] = useState(0); const [indexOpen, setIndexOpen] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useGetPeersQuery({ const { loading, data } = useGetPeersQuery();
skip: !auth,
variables: { auth },
});
if (loading || !data || !data.getPeers) { if (loading || !data?.getPeers) {
return <LoadingCard title={'Peers'} />; return <LoadingCard title={'Peers'} />;
} }
@ -33,11 +31,11 @@ const PeersView = () => {
<Card mobileCardPadding={'0'} mobileNoBackground={true}> <Card mobileCardPadding={'0'} mobileNoBackground={true}>
{data.getPeers.map((peer, index: number) => ( {data.getPeers.map((peer, index: number) => (
<PeersCard <PeersCard
peer={peer} peer={peer as PeerType}
index={index + 1} index={index + 1}
setIndexOpen={setIndexOpen} setIndexOpen={setIndexOpen}
indexOpen={indexOpen} indexOpen={indexOpen}
key={`${index}-${peer.public_key}`} key={`${index}-${peer?.public_key}`}
/> />
))} ))}
</Card> </Card>
@ -52,4 +50,8 @@ const Wrapped = () => (
</GridWrapper> </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 React, { useState } from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { SimpleBalance } from 'src/views/balance/SimpleBalance'; import { SimpleBalance } from 'src/views/balance/SimpleBalance';
import { import {
CardWithTitle, CardWithTitle,
@ -11,10 +10,13 @@ import {
} from 'src/components/generic/Styled'; } from 'src/components/generic/Styled';
import { Text } from 'src/components/typography/Styled'; import { Text } from 'src/components/typography/Styled';
import { AdvancedBalance } from 'src/views/balance/AdvancedBalance'; import { AdvancedBalance } from 'src/views/balance/AdvancedBalance';
import { useStatusState } from '../src/context/StatusContext'; import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_CHANNELS } from 'src/graphql/queries/getChannels';
import { useNodeInfo } from 'src/hooks/UseNodeInfo';
const BalanceView = () => { const BalanceView = () => {
const { minorVersion } = useStatusState(); const { minorVersion } = useNodeInfo();
const [advancedType, advancedTypeSet] = useState(false); const [advancedType, advancedTypeSet] = useState(false);
if (minorVersion < 9) { if (minorVersion < 9) {
@ -64,4 +66,8 @@ const Wrapped = () => (
</GridWrapper> </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 React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; 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 { SingleLine } from '../src/components/generic/Styled';
import { InterfaceSettings } from '../src/views/settings/Interface'; import { InterfaceSettings } from '../src/views/settings/Interface';
import { AccountSettings } from '../src/views/settings/Account'; import { AccountSettings } from '../src/views/settings/Account';
import { DangerView } from '../src/views/settings/Danger'; import { DangerView } from '../src/views/settings/Danger';
import { CurrentSettings } from '../src/views/settings/Current';
import { ChatSettings } from '../src/views/settings/Chat'; import { ChatSettings } from '../src/views/settings/Chat';
import { PrivacySettings } from '../src/views/settings/Privacy'; import { PrivacySettings } from '../src/views/settings/Privacy';
@ -25,7 +25,6 @@ const SettingsView = () => {
<InterfaceSettings /> <InterfaceSettings />
<PrivacySettings /> <PrivacySettings />
<ChatSettings /> <ChatSettings />
<CurrentSettings />
<AccountSettings /> <AccountSettings />
<DangerView /> <DangerView />
</> </>
@ -38,4 +37,8 @@ const Wrapped = () => (
</GridWrapper> </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 React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { VolumeStats } from 'src/views/stats/FlowStats'; import { VolumeStats } from 'src/views/stats/FlowStats';
import { TimeStats } from 'src/views/stats/TimeStats'; import { TimeStats } from 'src/views/stats/TimeStats';
import { FeeStats } from 'src/views/stats/FeeStats'; import { FeeStats } from 'src/views/stats/FeeStats';
import { StatResume } from 'src/views/stats/StatResume'; import { StatResume } from 'src/views/stats/StatResume';
import { StatsProvider } from 'src/views/stats/context'; 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'; import { SingleLine } from '../src/components/generic/Styled';
export const ButtonRow = styled.div` export const ButtonRow = styled.div`
@ -37,4 +41,12 @@ const Wrapped = () => (
</GridWrapper> </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 React from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { Bakery } from 'src/views/tools/bakery/Bakery'; import { Bakery } from 'src/views/tools/bakery/Bakery';
import { Accounting } from 'src/views/tools/accounting/Accounting'; 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 { BackupsView } from '../src/views/tools/backups/Backups';
import { MessagesView } from '../src/views/tools/messages/Messages'; import { MessagesView } from '../src/views/tools/messages/Messages';
import { WalletVersion } from '../src/views/tools/WalletVersion'; import { WalletVersion } from '../src/views/tools/WalletVersion';
@ -23,4 +25,8 @@ const Wrapped = () => (
</GridWrapper> </GridWrapper>
); );
export default withApollo(Wrapped); export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_WALLET_INFO]);
}

View file

@ -1,171 +0,0 @@
import React, { useState } from 'react';
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 {
CardWithTitle,
SubTitle,
Card,
DarkSubTitle,
ResponsiveLine,
} from '../src/components/generic/Styled';
import { LoadingCard } from '../src/components/loading/LoadingCard';
import { OfferCard } from '../src/views/trading/OfferCard';
import { OfferFilters } from '../src/views/trading/OfferFilters';
import { Link } from '../src/components/link/Link';
import { ColorButton } from '../src/components/buttons/colorButton/ColorButton';
import { decode } from '../src/utils/helpers';
const { publicRuntimeConfig } = getConfig();
const { hodlhodl } = publicRuntimeConfig;
export interface QueryProps {
pagination: {
limit: number;
offset: number;
};
filters: {};
sort: {
by: string;
direction: string;
};
}
const defaultQuery: QueryProps = {
pagination: {
limit: 25,
offset: 0,
},
filters: {},
sort: {
by: '',
direction: '',
},
};
const TradingView = () => {
const { query } = useRouter();
let decoded: QueryProps = defaultQuery;
if (query?.filter) {
const { filter } = query;
try {
if (typeof filter === 'string') {
decoded = JSON.parse(decode(filter));
} else {
decoded = JSON.parse(decode(filter[0]));
}
} catch (error) {
toast.error('Incorrect url.');
}
}
const queryObject = {
...defaultQuery,
...decoded,
};
const [indexOpen, setIndexOpen] = useState(0);
const [page, setPage] = useState(1);
const [fetching, setFetching] = useState(false);
const { data, loading, fetchMore, error } = useGetOffersQuery({
skip: !hodlhodl,
variables: { filter: JSON.stringify(queryObject) },
});
if (!hodlhodl) {
return (
<CardWithTitle>
<SubTitle>P2P Trading</SubTitle>
<Card bottom={'16px'}>
HodlHodl integration is disabled from the server.
</Card>
</CardWithTitle>
);
}
if (error) {
return (
<CardWithTitle>
<SubTitle>P2P Trading</SubTitle>
<Card bottom={'16px'}>Failed to connect with HodlHodl.</Card>
</CardWithTitle>
);
}
if (loading || !data || !data.getOffers) {
return <LoadingCard title={'P2P Trading'} />;
}
const amountOfOffers = data.getOffers.length;
const {
pagination: { limit },
} = queryObject;
return (
<CardWithTitle>
<ResponsiveLine>
<SubTitle>P2P Trading</SubTitle>
<DarkSubTitle>
Powered by <Link href={'https://hodlhodl.com/'}>HodlHodl</Link>
</DarkSubTitle>
</ResponsiveLine>
<Card bottom={'16px'}>
<OfferFilters offerFilters={queryObject} />
</Card>
<Card bottom={'8px'} mobileCardPadding={'0'} mobileNoBackground={true}>
{amountOfOffers <= 0 && <DarkSubTitle>No Offers Found</DarkSubTitle>}
{data.getOffers.map((offer, index: number) => (
<OfferCard
offer={offer}
index={index + 1}
setIndexOpen={setIndexOpen}
indexOpen={indexOpen}
key={`${index}-${offer.id}`}
/>
))}
</Card>
{amountOfOffers > 0 && amountOfOffers === limit * page && (
<ColorButton
loading={fetching}
disabled={fetching}
onClick={() => {
setFetching(true);
fetchMore({
variables: {
filter: JSON.stringify({
...queryObject,
pagination: { limit, offset: limit * page },
}),
},
updateQuery: (prev, { fetchMoreResult: result }) => {
if (!result) return prev;
setFetching(false);
setPage(prev => prev + 1);
return {
getOffers: [...prev.getOffers, ...result.getOffers],
};
},
});
}}
>
Show More
</ColorButton>
)}
</CardWithTitle>
);
};
const Wrapped = () => (
<GridWrapper>
<TradingView />
</GridWrapper>
);
export default withApollo(Wrapped);

View file

@ -1,13 +1,16 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { InvoiceCard } from 'src/views/transactions/InvoiceCard'; import { InvoiceCard } from 'src/views/transactions/InvoiceCard';
import { import {
useGetResumeQuery, useGetResumeQuery,
GetResumeQuery, GetResumeQuery,
} from 'src/graphql/queries/__generated__/getResume.generated'; } from 'src/graphql/queries/__generated__/getResume.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper'; 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 { import {
Card, Card,
CardWithTitle, CardWithTitle,
@ -23,11 +26,8 @@ const TransactionsView = () => {
const [indexOpen, setIndexOpen] = useState(0); const [indexOpen, setIndexOpen] = useState(0);
const [token, setToken] = useState(''); const [token, setToken] = useState('');
const { auth } = useAccountState();
const { loading, data, fetchMore } = useGetResumeQuery({ const { loading, data, fetchMore } = useGetResumeQuery({
skip: !auth, variables: { token: '' },
variables: { auth, token: '' },
onError: error => toast.error(getErrorContent(error)), onError: error => toast.error(getErrorContent(error)),
}); });
@ -49,7 +49,10 @@ const TransactionsView = () => {
<CardWithTitle> <CardWithTitle>
<SubTitle>Transactions</SubTitle> <SubTitle>Transactions</SubTitle>
<Card bottom={'8px'} mobileCardPadding={'0'} mobileNoBackground={true}> <Card bottom={'8px'} mobileCardPadding={'0'} mobileNoBackground={true}>
{resumeList.map((entry, index: number) => { {resumeList?.map((entry, index: number) => {
if (!entry) {
return null;
}
if (entry.__typename === 'InvoiceType') { if (entry.__typename === 'InvoiceType') {
return ( return (
<InvoiceCard <InvoiceCard
@ -61,32 +64,35 @@ const TransactionsView = () => {
/> />
); );
} }
return ( if (entry.__typename === 'PaymentType') {
<PaymentsCard return (
payment={entry} <PaymentsCard
key={index} payment={entry}
index={index + 1} key={index}
setIndexOpen={setIndexOpen} index={index + 1}
indexOpen={indexOpen} setIndexOpen={setIndexOpen}
/> indexOpen={indexOpen}
); />
);
}
return null;
})} })}
<ColorButton <ColorButton
fullWidth={true} fullWidth={true}
withMargin={'16px 0 0'} withMargin={'16px 0 0'}
onClick={() => { onClick={() => {
fetchMore({ fetchMore({
variables: { auth, token }, variables: { token },
updateQuery: ( updateQuery: (
prev, prev,
{ { fetchMoreResult }: { fetchMoreResult?: GetResumeQuery }
fetchMoreResult: result, ): GetResumeQuery => {
}: { fetchMoreResult: GetResumeQuery } if (!fetchMoreResult?.getResume) return prev;
) => { const newToken = fetchMoreResult.getResume.token || '';
if (!result) return prev; const prevEntries = prev?.getResume
const newToken = result.getResume.token || ''; ? prev.getResume.resume
const prevEntries = prev.getResume.resume; : [];
const newEntries = result.getResume.resume; const newEntries = fetchMoreResult.getResume.resume;
const allTransactions = newToken const allTransactions = newToken
? [...prevEntries, ...newEntries] ? [...prevEntries, ...newEntries]
@ -117,4 +123,11 @@ const Wrapped = () => (
</GridWrapper> </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' } },
]);
}

View file

@ -71,7 +71,7 @@ describe('getParsedAccount', () => {
return 'something else '; return 'something else ';
}); });
const account = getParsedAccount(raw, 0, masterPassword, 'regtest'); const account = getParsedAccount(raw, 0, masterPassword, 'regtest');
expect(account.macaroon).toContain('macaroon'); expect(account?.macaroon).toContain('macaroon');
}); });
it('picks up other networks', () => { it('picks up other networks', () => {
@ -94,7 +94,7 @@ describe('getParsedAccount', () => {
return 'something else '; return 'something else ';
}); });
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet'); const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.macaroon).toContain('macaroon'); expect(account?.macaroon).toContain('macaroon');
}); });
describe('macaroon handling', () => { describe('macaroon handling', () => {
@ -109,7 +109,7 @@ describe('getParsedAccount', () => {
}; };
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet'); const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.macaroon).toBe('RAW MACAROON'); expect(account?.macaroon).toBe('RAW MACAROON');
}); });
it('falls back to macaroon path after that', () => { it('falls back to macaroon path after that', () => {
const raw = { const raw = {
@ -130,7 +130,7 @@ describe('getParsedAccount', () => {
} }
}); });
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet'); const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.macaroon).toBe('yay'); expect(account?.macaroon).toBe('yay');
}); });
it('falls back to lnd dir finally', () => { it('falls back to lnd dir finally', () => {
@ -152,7 +152,7 @@ describe('getParsedAccount', () => {
} }
}); });
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet'); const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.macaroon).toBe('yay'); expect(account?.macaroon).toBe('yay');
}); });
}); });
@ -168,7 +168,7 @@ describe('getParsedAccount', () => {
}; };
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet'); const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.cert).toBe('RAW CERT'); expect(account?.cert).toBe('RAW CERT');
}); });
it('falls back to certificate path after that', () => { it('falls back to certificate path after that', () => {
@ -188,7 +188,7 @@ describe('getParsedAccount', () => {
} }
}); });
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet'); const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.cert).toBe('yay'); expect(account?.cert).toBe('yay');
}); });
it('falls back to lnd dir finally', () => { it('falls back to lnd dir finally', () => {
@ -207,7 +207,7 @@ describe('getParsedAccount', () => {
} }
}); });
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet'); const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.cert).toBe('yay'); expect(account?.cert).toBe('yay');
}); });
}); });
}); });

View file

@ -1,7 +1,7 @@
import { getErrorMsg } from './helpers'; import { getErrorMsg } from './helpers';
import { logger } from './logger'; import { logger } from './logger';
export const to = promise => { export const to = async <T>(promise: Promise<T>) => {
return promise return promise
.then(data => data) .then(data => data)
.catch(err => { .catch(err => {
@ -10,6 +10,15 @@ export const to = promise => {
}); });
}; };
export const toWithError = promise => { /*
return promise.then(data => [data, undefined]).catch(err => [undefined, err]); * This is hard/impossible to type correctly. What we are describing
* here is a set of two states: either we have a result and no error,
* _or_ we have no result and an error. Unfortunately TypeScript is
* not able to infer this correctly...
* https://github.com/microsoft/TypeScript/issues/12184
*/
export const toWithError = async <T>(promise: Promise<T>) => {
return promise
.then(data => [data, undefined] as const)
.catch(err => [undefined, err] as const);
}; };

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

@ -68,10 +68,15 @@ export const createCustomRecords = ({
]; ];
}; };
type DecodeMessageType = {
type: string;
value: string;
};
export const decodeMessage = ({ export const decodeMessage = ({
type, type,
value, value,
}): { [key: string]: string } | {} => { }: DecodeMessageType): { [key: string]: string } | {} => {
switch (type) { switch (type) {
case MESSAGE_TYPE: case MESSAGE_TYPE:
return { message: bufferHexToUtf(value) }; return { message: bufferHexToUtf(value) };

View file

@ -4,8 +4,9 @@ import path from 'path';
import os from 'os'; import os from 'os';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { getUUID } from 'src/utils/auth';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { AccountType as ContextAccountType } from 'server/types/apiTypes';
import { getUUID } from './auth';
type EncodingType = 'hex' | 'utf-8'; type EncodingType = 'hex' | 'utf-8';
type BitcoinNetwork = 'mainnet' | 'regtest' | 'testnet'; type BitcoinNetwork = 'mainnet' | 'regtest' | 'testnet';
@ -17,14 +18,14 @@ type AccountType = {
network?: BitcoinNetwork; network?: BitcoinNetwork;
macaroonPath?: string; macaroonPath?: string;
certificatePath?: string; certificatePath?: string;
password?: string; password?: string | null;
macaroon?: string; macaroon?: string;
certificate?: string; certificate?: string;
}; };
type ParsedAccount = { type ParsedAccount = {
name: string; name: string;
id: string; id: string;
host: string; socket: string;
macaroon: string; macaroon: string;
cert: string; cert: string;
password: string; password: string;
@ -36,7 +37,7 @@ type AccountConfigType = {
accounts: AccountType[]; accounts: AccountType[];
}; };
const isValidNetwork = (network: string): network is BitcoinNetwork => const isValidNetwork = (network: string | null): network is BitcoinNetwork =>
network === 'mainnet' || network === 'regtest' || network === 'testnet'; network === 'mainnet' || network === 'regtest' || network === 'testnet';
export const PRE_PASS_STRING = 'thunderhub-'; export const PRE_PASS_STRING = 'thunderhub-';
@ -78,7 +79,8 @@ export const parseYaml = (filePath: string): AccountConfigType | null => {
try { try {
const yamlObject = yaml.safeLoad(yamlConfig); const yamlObject = yaml.safeLoad(yamlConfig);
return yamlObject; // TODO: validate this, before returning?
return yamlObject as AccountConfigType;
} catch (err) { } catch (err) {
logger.error( logger.error(
'Something went wrong while parsing the YAML config file: \n' + err 'Something went wrong while parsing the YAML config file: \n' + err
@ -115,12 +117,15 @@ export const hashPasswords = (
const cloned = { ...config }; const cloned = { ...config };
let hashedMasterPassword = config.masterPassword; let hashedMasterPassword = config?.masterPassword;
if (hashedMasterPassword?.indexOf(PRE_PASS_STRING) < 0) { if (
hashedMasterPassword &&
hashedMasterPassword.indexOf(PRE_PASS_STRING) < 0
) {
hasChanged = true; hasChanged = true;
hashedMasterPassword = `${PRE_PASS_STRING}${bcrypt.hashSync( hashedMasterPassword = `${PRE_PASS_STRING}${bcrypt.hashSync(
config.masterPassword, hashedMasterPassword,
12 12
)}`; )}`;
} }
@ -178,7 +183,7 @@ const getCertificate = ({
const getMacaroon = ( const getMacaroon = (
{ macaroon, macaroonPath, network, lndDir }: AccountType, { macaroon, macaroonPath, network, lndDir }: AccountType,
defaultNetwork: BitcoinNetwork defaultNetwork: BitcoinNetwork
): string => { ): string | null => {
if (macaroon) { if (macaroon) {
return macaroon; return macaroon;
} }
@ -203,24 +208,24 @@ const getMacaroon = (
); );
}; };
export const getAccounts = (filePath: string) => { export const getAccounts = (filePath: string): ContextAccountType[] => {
if (filePath === '') { if (filePath === '') {
logger.verbose('No account config file path provided'); logger.verbose('No account config file path provided');
return null; return [];
} }
const accountConfig = parseYaml(filePath); const accountConfig = parseYaml(filePath);
if (!accountConfig) { if (!accountConfig) {
logger.info(`No account config file found at path ${filePath}`); logger.info(`No account config file found at path ${filePath}`);
return null; return [];
} }
return getAccountsFromYaml(accountConfig, filePath); return getAccountsFromYaml(accountConfig, filePath) as ContextAccountType[];
}; };
export const getParsedAccount = ( export const getParsedAccount = (
account: AccountType, account: AccountType,
index: number, index: number,
masterPassword: string, masterPassword: string | null,
defaultNetwork: BitcoinNetwork defaultNetwork: BitcoinNetwork
): ParsedAccount | null => { ): ParsedAccount | null => {
const { const {
@ -276,12 +281,12 @@ export const getParsedAccount = (
const id = getUUID(`${name}${serverUrl}${macaroon}${cert}`); const id = getUUID(`${name}${serverUrl}${macaroon}${cert}`);
return { return {
name, name: name || '',
id, id,
host: serverUrl, socket: serverUrl || '',
macaroon, macaroon,
cert, cert: cert || '',
password: password || masterPassword, password: password || masterPassword || '',
}; };
}; };
@ -297,7 +302,7 @@ export const getAccountsFromYaml = (
} }
const { defaultNetwork, masterPassword, accounts } = hashPasswords( const { defaultNetwork, masterPassword, accounts } = hashPasswords(
hashed, hashed || false,
config, config,
filePath filePath
); );
@ -314,7 +319,7 @@ export const getAccountsFromYaml = (
logger.info( logger.info(
`Server accounts that will be available: ${parsedAccounts `Server accounts that will be available: ${parsedAccounts
.map(({ name }) => name) .map(account => account?.name)
.join(', ')}` .join(', ')}`
); );

View file

@ -1,6 +1,11 @@
import { getNode, getChannel } from 'ln-service'; import { getNode, getChannel } from 'ln-service';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import { toWithError } from 'server/helpers/async'; import { toWithError } from 'server/helpers/async';
import {
LndObject,
GetChannelType,
GetNodeType,
} from 'server/types/ln-service.types';
const errorNode = { const errorNode = {
alias: 'Partner node not found', alias: 'Partner node not found',
@ -10,7 +15,7 @@ const errorNode = {
export const getNodeFromChannel = async ( export const getNodeFromChannel = async (
id: string, id: string,
publicKey: string, publicKey: string,
lnd lnd: LndObject | null
) => { ) => {
const [channelInfo, channelError] = await toWithError( const [channelInfo, channelError] = await toWithError(
getChannel({ getChannel({
@ -19,15 +24,15 @@ export const getNodeFromChannel = async (
}) })
); );
if (channelError) { if (channelError || !channelInfo) {
logger.verbose(`Error getting channel with id ${id}: %o`, channelError); logger.verbose(`Error getting channel with id ${id}: %o`, channelError);
return errorNode; return errorNode;
} }
const partnerPublicKey = const partnerPublicKey =
channelInfo.policies[0].public_key !== publicKey (channelInfo as GetChannelType).policies[0].public_key !== publicKey
? channelInfo.policies[0].public_key ? (channelInfo as GetChannelType).policies[0].public_key
: channelInfo.policies[1].public_key; : (channelInfo as GetChannelType).policies[1].public_key;
const [nodeInfo, nodeError] = await toWithError( const [nodeInfo, nodeError] = await toWithError(
getNode({ getNode({
@ -37,7 +42,7 @@ export const getNodeFromChannel = async (
}) })
); );
if (nodeError) { if (nodeError || !nodeInfo) {
logger.verbose( logger.verbose(
`Error getting node with public key ${partnerPublicKey}: %o`, `Error getting node with public key ${partnerPublicKey}: %o`,
nodeError nodeError
@ -46,7 +51,7 @@ export const getNodeFromChannel = async (
} }
return { return {
alias: nodeInfo.alias, alias: (nodeInfo as GetNodeType).alias,
color: nodeInfo.color, color: (nodeInfo as GetNodeType).color,
}; };
}; };

View file

@ -1,23 +1,7 @@
import { authenticatedLndGrpc } from 'ln-service';
import getConfig from 'next/config'; 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 { nodeEnv } = serverRuntimeConfig || {};
const { noClient } = publicRuntimeConfig || {};
type LndAuthType = {
cert: string;
macaroon: string;
host: string;
};
export const getIp = (req: any) => { export const getIp = (req: any) => {
if (!req || !req.headers) { if (!req || !req.headers) {
@ -31,78 +15,6 @@ export const getIp = (req: any) => {
return ip; 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) {
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 => { export const getErrorMsg = (error: any[] | string): string => {
if (typeof error === 'string') { if (typeof error === 'string') {
return error; return error;

View file

@ -1,18 +0,0 @@
export const getHodlParams = (params: any): string => {
let paramString = '?';
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const element = params[key];
for (const subKey in element) {
if (Object.prototype.hasOwnProperty.call(element, subKey)) {
const subElement = element[subKey];
paramString = `${paramString}&${key}[${subKey}]=${subElement}`;
}
}
}
}
return paramString;
};

View file

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

View file

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

View file

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

View file

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

View file

@ -6,53 +6,66 @@ import {
PRE_PASS_STRING, PRE_PASS_STRING,
} from 'server/helpers/fileHelpers'; } from 'server/helpers/fileHelpers';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { SSO_ACCOUNT, SERVER_ACCOUNT } from 'src/context/AccountContext';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import cookie from 'cookie'; import cookie from 'cookie';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { appConstants } from 'server/utils/appConstants';
const { serverRuntimeConfig } = getConfig() || {}; const { serverRuntimeConfig } = getConfig() || {};
const { cookiePath, nodeEnv } = serverRuntimeConfig || {}; const { cookiePath, nodeEnv } = serverRuntimeConfig || {};
export const authResolvers = { export const authResolvers = {
Query: { Query: {
getAuthToken: async (_: undefined, params: any, context: ContextType) => { getAuthToken: async (
_: undefined,
params: any,
context: ContextType
): Promise<boolean> => {
const { ip, secret, sso, res } = context; const { ip, secret, sso, res } = context;
await requestLimiter(ip, 'getAuthToken'); await requestLimiter(ip, 'getAuthToken');
if (!sso.host || !sso.macaroon) { if (!sso) {
logger.warn('No SSO account available');
return false;
}
if (!sso.socket || !sso.macaroon) {
logger.warn('Host and macaroon are required for SSO'); logger.warn('Host and macaroon are required for SSO');
return null; return false;
} }
if (!params.cookie) { if (!params.cookie) {
return null; return false;
} }
if (cookiePath === '') { if (cookiePath === '') {
logger.warn('SSO auth not available since no cookie path was provided'); logger.warn('SSO auth not available since no cookie path was provided');
return null; return false;
} }
const cookieFile = readCookie(cookiePath); const cookieFile = readCookie(cookiePath);
if ( if (
cookieFile.trim() === params.cookie.trim() || (cookieFile && cookieFile.trim() === params.cookie.trim()) ||
nodeEnv === 'development' nodeEnv === 'development'
) { ) {
refreshCookie(cookiePath); refreshCookie(cookiePath);
const token = jwt.sign({ user: SSO_ACCOUNT }, secret); const token = jwt.sign({ id: 'sso' }, secret);
res.setHeader( res.setHeader(
'Set-Cookie', 'Set-Cookie',
cookie.serialize('SSOAuth', token, { httpOnly: true, sameSite: true }) cookie.serialize(appConstants.cookieName, token, {
httpOnly: true,
sameSite: true,
path: '/',
})
); );
return true; return true;
} }
logger.debug(`Cookie ${params.cookie} different to file ${cookieFile}`); logger.debug(`Cookie ${params.cookie} different to file ${cookieFile}`);
return null; return false;
}, },
getSessionToken: async ( getSessionToken: async (
_: undefined, _: undefined,
@ -78,17 +91,13 @@ export const authResolvers = {
} }
logger.debug(`Correct password for account ${params.id}`); logger.debug(`Correct password for account ${params.id}`);
const token = jwt.sign( const token = jwt.sign({ id: params.id }, secret);
{
id: params.id,
},
secret
);
res.setHeader( res.setHeader(
'Set-Cookie', 'Set-Cookie',
cookie.serialize('AccountAuth', token, { cookie.serialize(appConstants.cookieName, token, {
httpOnly: true, httpOnly: true,
sameSite: true, sameSite: true,
path: '/',
}) })
); );
return true; return true;
@ -99,20 +108,10 @@ export const authResolvers = {
const { ip, res } = context; const { ip, res } = context;
await requestLimiter(ip, 'logout'); await requestLimiter(ip, 'logout');
if (params.type === SSO_ACCOUNT) { res.setHeader(
res.setHeader( 'Set-Cookie',
'Set-Cookie', cookie.serialize(appConstants.cookieName, '', { maxAge: 1 })
cookie.serialize('SSOAuth', '', { maxAge: 1 }) );
);
return true;
}
if (params.type === SERVER_ACCOUNT) {
res.setHeader(
'Set-Cookie',
cookie.serialize('AccountAuth', '', { maxAge: 1 })
);
return true;
}
return true; return true;
}, },
}, },

View file

@ -1,15 +1,12 @@
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async'; import { to } from 'server/helpers/async';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import { AuthType } from 'src/context/AccountContext';
import { rebalance } from 'balanceofsatoshis/swaps'; import { rebalance } from 'balanceofsatoshis/swaps';
import { getAccountingReport } from 'balanceofsatoshis/balances'; import { getAccountingReport } from 'balanceofsatoshis/balances';
import request from '@alexbosworth/request'; import request from '@alexbosworth/request';
import { RebalanceResponseType } from 'server/types/balanceofsatoshis.types';
type RebalanceType = { type RebalanceType = {
auth: AuthType;
avoid?: String[]; avoid?: String[];
in_through?: String; in_through?: String;
is_avoiding_high_inbound?: Boolean; is_avoiding_high_inbound?: Boolean;
@ -23,7 +20,6 @@ type RebalanceType = {
}; };
type AccountingType = { type AccountingType = {
auth: AuthType;
category?: String; category?: String;
currency?: String; currency?: String;
fiat?: String; fiat?: String;
@ -38,8 +34,7 @@ export const bosResolvers = {
params: AccountingType, params: AccountingType,
context: ContextType context: ContextType
) => { ) => {
const { auth, ...settings } = params; const { lnd } = context;
const lnd = getLnd(auth, context);
const response = await to( const response = await to(
getAccountingReport({ getAccountingReport({
@ -47,7 +42,7 @@ export const bosResolvers = {
logger, logger,
request, request,
is_csv: true, is_csv: true,
...settings, ...params,
}) })
); );
@ -61,7 +56,6 @@ export const bosResolvers = {
context: ContextType context: ContextType
) => { ) => {
const { const {
auth,
avoid, avoid,
in_through, in_through,
is_avoiding_high_inbound, is_avoiding_high_inbound,
@ -73,16 +67,16 @@ export const bosResolvers = {
out_through, out_through,
target, target,
} = params; } = params;
const lnd = getLnd(auth, context); const { lnd } = context;
const filteredParams = { const filteredParams = {
avoid, avoid,
out_channels, out_channels,
...(in_through && { in_through }), ...(in_through && { in_through }),
...(is_avoiding_high_inbound && { is_avoiding_high_inbound }), ...(is_avoiding_high_inbound && { is_avoiding_high_inbound }),
...(max_fee > 0 && { max_fee }), ...(max_fee && max_fee > 0 && { max_fee }),
...(max_fee_rate > 0 && { max_fee_rate }), ...(max_fee_rate && max_fee_rate > 0 && { max_fee_rate }),
...(max_rebalance > 0 && { max_rebalance }), ...(max_rebalance && max_rebalance > 0 && { max_rebalance }),
...(node && { node }), ...(node && { node }),
...(out_through && { out_through }), ...(out_through && { out_through }),
...(target && { target }), ...(target && { target }),
@ -90,7 +84,7 @@ export const bosResolvers = {
logger.info('Rebalance Params: %o', filteredParams); logger.info('Rebalance Params: %o', filteredParams);
const response = await to( const response = await to<RebalanceResponseType>(
rebalance({ rebalance({
lnd, lnd,
logger, logger,

View file

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

View file

@ -1,6 +1,6 @@
import { import {
getChainBalance as getBalance, getChainBalance,
getPendingChainBalance as getPending, getPendingChainBalance,
getChainTransactions, getChainTransactions,
getUtxos, getUtxos,
sendToChainAddress, sendToChainAddress,
@ -9,12 +9,16 @@ import {
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { import { getErrorMsg } from 'server/helpers/helpers';
getAuthLnd,
getErrorMsg,
getCorrectAuth,
} from 'server/helpers/helpers';
import { sortBy } from 'underscore'; import { sortBy } from 'underscore';
import { to } from 'server/helpers/async';
import {
GetChainBalanceType,
GetPendingChainBalanceType,
GetChainTransactionsType,
GetUtxosType,
SendToChainAddressType,
} from 'server/types/ln-service.types';
interface ChainBalanceProps { interface ChainBalanceProps {
chain_balance: number; chain_balance: number;
@ -33,18 +37,14 @@ export const chainResolvers = {
) => { ) => {
await requestLimiter(context.ip, 'chainBalance'); await requestLimiter(context.ip, 'chainBalance');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
try { const value: ChainBalanceProps = await to<GetChainBalanceType>(
const value: ChainBalanceProps = await getBalance({ getChainBalance({
lnd, lnd,
}); })
return value.chain_balance; );
} catch (error) { return value.chain_balance;
logger.error('Error getting chain balance: %o', error);
throw new Error(getErrorMsg(error));
}
}, },
getPendingChainBalance: async ( getPendingChainBalance: async (
_: undefined, _: undefined,
@ -53,18 +53,16 @@ export const chainResolvers = {
) => { ) => {
await requestLimiter(context.ip, 'pendingChainBalance'); await requestLimiter(context.ip, 'pendingChainBalance');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
try { const pendingValue: PendingChainBalanceProps = await to<
const pendingValue: PendingChainBalanceProps = await getPending({ GetPendingChainBalanceType
>(
getPendingChainBalance({
lnd, lnd,
}); })
return pendingValue.pending_chain_balance; );
} catch (error) { return pendingValue.pending_chain_balance;
logger.error('Error getting pending chain balance: %o', error);
throw new Error(getErrorMsg(error));
}
}, },
getChainTransactions: async ( getChainTransactions: async (
_: undefined, _: undefined,
@ -73,46 +71,35 @@ export const chainResolvers = {
) => { ) => {
await requestLimiter(context.ip, 'chainTransactions'); await requestLimiter(context.ip, 'chainTransactions');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
try { const transactionList = await to<GetChainTransactionsType>(
const transactionList = await getChainTransactions({ getChainTransactions({
lnd, lnd,
}); })
);
const transactions = sortBy( const transactions = sortBy(
transactionList.transactions, transactionList.transactions,
'created_at' 'created_at'
).reverse(); ).reverse();
return transactions; return transactions;
} catch (error) {
logger.error('Error getting chain transactions: %o', error);
throw new Error(getErrorMsg(error));
}
}, },
getUtxos: async (_: undefined, params: any, context: ContextType) => { getUtxos: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getUtxos'); await requestLimiter(context.ip, 'getUtxos');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
try { const info = await to<GetUtxosType>(getUtxos({ lnd }));
const { utxos } = await getUtxos({ lnd });
return utxos; return info?.utxos;
} catch (error) {
logger.error('Error getting utxos: %o', error);
throw new Error(getErrorMsg(error));
}
}, },
}, },
Mutation: { Mutation: {
createAddress: async (_: undefined, params: any, context: ContextType) => { createAddress: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getAddress'); await requestLimiter(context.ip, 'getAddress');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const format = params.nested ? 'np2wpkh' : 'p2wpkh'; const format = params.nested ? 'np2wpkh' : 'p2wpkh';
@ -132,8 +119,7 @@ export const chainResolvers = {
sendToAddress: async (_: undefined, params: any, context: ContextType) => { sendToAddress: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'sendToAddress'); await requestLimiter(context.ip, 'sendToAddress');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const props = params.fee const props = params.fee
? { fee_tokens_per_vbyte: params.fee } ? { fee_tokens_per_vbyte: params.fee }
@ -143,26 +129,23 @@ export const chainResolvers = {
const sendAll = params.sendAll ? { is_send_all: true } : {}; const sendAll = params.sendAll ? { is_send_all: true } : {};
try { const send = await to<SendToChainAddressType>(
const send = await sendToChainAddress({ sendToChainAddress({
lnd, lnd,
address: params.address, address: params.address,
...(params.tokens && { tokens: params.tokens }), ...(params.tokens && { tokens: params.tokens }),
...props, ...props,
...sendAll, ...sendAll,
}); })
);
return { return {
confirmationCount: send.confirmation_count, confirmationCount: send.confirmation_count,
id: send.id, id: send.id,
isConfirmed: send.is_confirmed, isConfirmed: send.is_confirmed,
isOutgoing: send.is_outgoing, isOutgoing: send.is_outgoing,
...(send.tokens && { tokens: send.tokens }), ...(send.tokens && { tokens: send.tokens }),
}; };
} catch (error) {
logger.error('Error sending to chain address: %o', error);
throw new Error(getErrorMsg(error));
}
}, },
}, },
}; };

View file

@ -2,19 +2,19 @@ import { gql } from 'apollo-server-micro';
export const chainTypes = gql` export const chainTypes = gql`
type getUtxosType { type getUtxosType {
address: String address: String!
address_format: String address_format: String!
confirmation_count: Int confirmation_count: Int!
output_script: String output_script: String!
tokens: Int tokens: Int!
transaction_id: String transaction_id: String!
transaction_vout: Int transaction_vout: Int!
} }
type sendToType { type sendToType {
confirmationCount: String confirmationCount: String!
id: String id: String!
isConfirmed: Boolean isConfirmed: Boolean!
isOutgoing: Boolean isOutgoing: Boolean!
tokens: Int tokens: Int
} }
@ -22,10 +22,10 @@ export const chainTypes = gql`
block_id: String block_id: String
confirmation_count: Int confirmation_count: Int
confirmation_height: Int confirmation_height: Int
created_at: String created_at: String!
fee: Int fee: Int
id: String id: String!
output_addresses: [String] output_addresses: [String]!
tokens: Int tokens: Int!
} }
`; `;

View file

@ -1,6 +1,7 @@
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import { toWithError } from 'server/helpers/async'; import { toWithError } from 'server/helpers/async';
import { getChannel } from 'ln-service'; import { getChannel } from 'ln-service';
import { GetChannelType } from 'server/types/ln-service.types';
import { openChannel } from './resolvers/mutation/openChannel'; import { openChannel } from './resolvers/mutation/openChannel';
import { closeChannel } from './resolvers/mutation/closeChannel'; import { closeChannel } from './resolvers/mutation/closeChannel';
import { updateFees } from './resolvers/mutation/updateFees'; import { updateFees } from './resolvers/mutation/updateFees';
@ -53,7 +54,7 @@ export const channelResolvers = {
let node_policies = null; let node_policies = null;
let partner_node_policies = null; let partner_node_policies = null;
channel.policies.forEach(policy => { (channel as GetChannelType).policies.forEach(policy => {
if (localKey && localKey === policy.public_key) { if (localKey && localKey === policy.public_key) {
node_policies = { node_policies = {
...policy, ...policy,
@ -67,7 +68,11 @@ export const channelResolvers = {
} }
}); });
return { ...channel, node_policies, partner_node_policies }; return {
...(channel as GetChannelType),
node_policies,
partner_node_policies,
};
}, },
}, },
}; };

View file

@ -1,12 +1,9 @@
import { closeChannel as lnCloseChannel } from 'ln-service'; import { closeChannel as lnCloseChannel } from 'ln-service';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { import { to } from 'server/helpers/async';
getAuthLnd, import { CloseChannelType } from 'server/types/ln-service.types';
getErrorMsg, import { logger } from 'server/helpers/logger';
getCorrectAuth,
} from 'server/helpers/helpers';
export const closeChannel = async ( export const closeChannel = async (
_: undefined, _: undefined,
@ -15,23 +12,28 @@ export const closeChannel = async (
) => { ) => {
await requestLimiter(context.ip, 'closeChannel'); await requestLimiter(context.ip, 'closeChannel');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
try { const closeParams = {
const info = await lnCloseChannel({ id: params.id,
target_confirmations: params.targetConfirmations,
tokens_per_vbyte: params.tokensPerVByte,
is_force_close: params.forceClose,
};
logger.info('Closing channel with params: %o', closeParams);
const info = await to<CloseChannelType>(
lnCloseChannel({
lnd, lnd,
id: params.id, ...closeParams,
target_confirmations: params.targetConfirmations, })
tokens_per_vbyte: params.tokensPerVByte, );
is_force_close: params.forceClose,
}); logger.info('Channel closed: %o', params.id);
return {
transactionId: info.transaction_id, return {
transactionOutputIndex: info.transaction_vout, transactionId: info.transaction_id,
}; transactionOutputIndex: info.transaction_vout,
} catch (error) { };
logger.error('Error closing channel: %o', error);
throw new Error(getErrorMsg(error));
}
}; };

View file

@ -2,42 +2,54 @@ import { openChannel as lnOpenChannel } from 'ln-service';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { import { to } from 'server/helpers/async';
getAuthLnd, import { OpenChannelType } from 'server/types/ln-service.types';
getErrorMsg,
getCorrectAuth, type OpenChannelParams = {
} from 'server/helpers/helpers'; isPrivate: boolean;
amount: number;
partnerPublicKey: string;
tokensPerVByte: number;
pushTokens: number;
};
export const openChannel = async ( export const openChannel = async (
_: undefined, _: undefined,
params: any, params: OpenChannelParams,
context: ContextType context: ContextType
) => { ) => {
await requestLimiter(context.ip, 'openChannel'); await requestLimiter(context.ip, 'openChannel');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth); const {
isPrivate,
amount,
partnerPublicKey,
tokensPerVByte,
pushTokens = 0,
} = params;
const openParams = { const openParams = {
is_private: params.isPrivate, is_private: isPrivate,
local_tokens: params.amount, local_tokens: amount,
partner_public_key: params.partnerPublicKey, partner_public_key: partnerPublicKey,
chain_fee_tokens_per_vbyte: params.tokensPerVByte, chain_fee_tokens_per_vbyte: tokensPerVByte,
give_tokens: Math.min(pushTokens, amount),
}; };
logger.debug('Opening channel: %o', openParams); logger.info('Opening channel with params: %o', openParams);
try { const info = await to<OpenChannelType>(
const info = await lnOpenChannel({ lnOpenChannel({
lnd, lnd,
...openParams, ...openParams,
}); })
return { );
transactionId: info.transaction_id,
transactionOutputIndex: info.transaction_vout, logger.info('Channel opened');
};
} catch (error) { return {
logger.error('Error opening channel: %o', error); transactionId: info.transaction_id,
throw new Error(getErrorMsg(error)); transactionOutputIndex: info.transaction_vout,
} };
}; };

View file

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

View file

@ -1,12 +1,8 @@
import { getChannelBalance as getLnChannelBalance } from 'ln-service'; import { getChannelBalance as getLnChannelBalance } from 'ln-service';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd, import { to } from 'server/helpers/async';
getErrorMsg,
getCorrectAuth,
} from 'server/helpers/helpers';
interface ChannelBalanceProps { interface ChannelBalanceProps {
channel_balance: number; channel_balance: number;
@ -20,19 +16,15 @@ export const getChannelBalance = async (
) => { ) => {
await requestLimiter(context.ip, 'channelBalance'); await requestLimiter(context.ip, 'channelBalance');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
try { const channelBalance: ChannelBalanceProps = await to(
const channelBalance: ChannelBalanceProps = await getLnChannelBalance({ getLnChannelBalance({
lnd, lnd,
}); })
return { );
confirmedBalance: channelBalance.channel_balance, return {
pendingBalance: channelBalance.pending_balance, confirmedBalance: channelBalance.channel_balance,
}; pendingBalance: channelBalance.pending_balance,
} catch (error) { };
logger.error('Error getting channel balance: %o', error);
throw new Error(getErrorMsg(error));
}
}; };

View file

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

View file

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

View file

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

View file

@ -55,36 +55,36 @@ export const channelTypes = gql`
} }
type channelBalanceType { type channelBalanceType {
confirmedBalance: Int confirmedBalance: Int!
pendingBalance: Int pendingBalance: Int!
} }
type channelType { type channelType {
capacity: Int capacity: Int!
commit_transaction_fee: Int commit_transaction_fee: Int!
commit_transaction_weight: Int commit_transaction_weight: Int!
id: String id: String!
is_active: Boolean is_active: Boolean!
is_closing: Boolean is_closing: Boolean!
is_opening: Boolean is_opening: Boolean!
is_partner_initiated: Boolean is_partner_initiated: Boolean!
is_private: Boolean is_private: Boolean!
is_static_remote_key: Boolean is_static_remote_key: Boolean
local_balance: Int local_balance: Int!
local_reserve: Int local_reserve: Int!
partner_public_key: String partner_public_key: String!
received: Int received: Int!
remote_balance: Int remote_balance: Int!
remote_reserve: Int remote_reserve: Int!
sent: Int sent: Int!
time_offline: Int time_offline: Int
time_online: Int time_online: Int
transaction_id: String transaction_id: String!
transaction_vout: Int transaction_vout: Int!
unsettled_balance: Int unsettled_balance: Int!
partner_node_info: Node partner_node_info: Node!
partner_fee_info: Channel partner_fee_info: Channel
channel_age: Int channel_age: Int!
} }
type closeChannelType { type closeChannelType {
@ -93,21 +93,21 @@ export const channelTypes = gql`
} }
type closedChannelType { type closedChannelType {
capacity: Int capacity: Int!
close_confirm_height: Int close_confirm_height: Int
close_transaction_id: String close_transaction_id: String
final_local_balance: Int final_local_balance: Int!
final_time_locked_balance: Int final_time_locked_balance: Int!
id: String id: String
is_breach_close: Boolean is_breach_close: Boolean!
is_cooperative_close: Boolean is_cooperative_close: Boolean!
is_funding_cancel: Boolean is_funding_cancel: Boolean!
is_local_force_close: Boolean is_local_force_close: Boolean!
is_remote_force_close: Boolean is_remote_force_close: Boolean!
partner_public_key: String partner_public_key: String!
transaction_id: String transaction_id: String!
transaction_vout: Int transaction_vout: Int!
partner_node_info: Node partner_node_info: Node!
} }
type openChannelType { type openChannelType {
@ -117,19 +117,19 @@ export const channelTypes = gql`
type pendingChannelType { type pendingChannelType {
close_transaction_id: String close_transaction_id: String
is_active: Boolean is_active: Boolean!
is_closing: Boolean is_closing: Boolean!
is_opening: Boolean is_opening: Boolean!
local_balance: Int local_balance: Int!
local_reserve: Int local_reserve: Int!
partner_public_key: String partner_public_key: String!
received: Int received: Int!
remote_balance: Int remote_balance: Int!
remote_reserve: Int remote_reserve: Int!
sent: Int sent: Int!
transaction_fee: Int transaction_fee: Int
transaction_id: String transaction_id: String!
transaction_vout: Int transaction_vout: Int!
partner_node_info: Node partner_node_info: Node!
} }
`; `;

View file

@ -10,22 +10,25 @@ import {
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { to, toWithError } from 'server/helpers/async'; import { to, toWithError } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { import {
createCustomRecords, createCustomRecords,
decodeMessage, decodeMessage,
} from 'server/helpers/customRecords'; } from 'server/helpers/customRecords';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import {
GetInvoicesType,
GetWalletInfoType,
} from 'server/types/ln-service.types';
export const chatResolvers = { export const chatResolvers = {
Query: { Query: {
getMessages: async (_: undefined, params: any, context: ContextType) => { getMessages: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getMessages'); await requestLimiter(context.ip, 'getMessages');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const invoiceList = await to( const invoiceList = await to<GetInvoicesType>(
getInvoices({ getInvoices({
lnd, lnd,
limit: params.initialize ? 100 : 5, limit: params.initialize ? 100 : 5,
@ -72,7 +75,11 @@ export const chatResolvers = {
logger.debug(`Error verifying message: ${messageToVerify}`); logger.debug(`Error verifying message: ${messageToVerify}`);
} }
if (!error && verified?.signed_by === customRecords.sender) { if (
!error &&
(verified as { signed_by: string })?.signed_by ===
customRecords.sender
) {
isVerified = true; isVerified = true;
} }
} }
@ -88,7 +95,7 @@ export const chatResolvers = {
); );
const filtered = await getFiltered(); const filtered = await getFiltered();
const final = filtered.filter(message => !!message); const final = filtered.filter(Boolean) || [];
return { token: invoiceList.next, messages: final }; return { token: invoiceList.next, messages: final };
}, },
@ -97,8 +104,7 @@ export const chatResolvers = {
sendMessage: async (_: undefined, params: any, context: ContextType) => { sendMessage: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'sendMessage'); await requestLimiter(context.ip, 'sendMessage');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
if (params.maxFee) { if (params.maxFee) {
const tokens = Math.max(params.tokens || 100, 100); const tokens = Math.max(params.tokens || 100, 100);
@ -126,7 +132,7 @@ export const chatResolvers = {
messageToSend = `${params.tokens},${params.message}`; messageToSend = `${params.tokens},${params.message}`;
} }
const nodeInfo = await to( const nodeInfo = await to<GetWalletInfoType>(
getWalletInfo({ getWalletInfo({
lnd, lnd,
}) })
@ -167,7 +173,8 @@ export const chatResolvers = {
messages: customRecords, messages: customRecords,
}) })
); );
return safe_fee; // +1 is needed so that a fee of 0 doesnt evaluate to false
return safe_fee + 1;
}, },
}, },
}; };

View file

@ -3,13 +3,13 @@ import { gql } from 'apollo-server-micro';
export const chatTypes = gql` export const chatTypes = gql`
type getMessagesType { type getMessagesType {
token: String token: String
messages: [messagesType] messages: [messagesType]!
} }
type messagesType { type messagesType {
date: String date: String!
id: String id: String!
verified: Boolean verified: Boolean!
contentType: String contentType: String
sender: String sender: String
alias: String alias: String

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

@ -15,7 +15,7 @@ export const githubResolvers = {
const [response, error] = await toWithError(fetch(appUrls.github)); const [response, error] = await toWithError(fetch(appUrls.github));
if (error) { if (error || !response) {
logger.debug('Unable to get latest github version'); logger.debug('Unable to get latest github version');
throw new Error('NoGithubVersion'); throw new Error('NoGithubVersion');
} }

View file

@ -1,6 +1,15 @@
import { groupBy } from 'underscore'; import { groupBy } from 'underscore';
import { ForwardType } from 'server/types/ln-service.types';
export const getChannelVolume = forwards => { type GroupedObject = {
[key: string]: ForwardType[];
};
type TotalGroupedObject = {
[key: string]: { tokens: number }[];
};
export const getChannelVolume = (forwards: ForwardType[]) => {
const orderedIncoming = groupBy(forwards, f => f.incoming_channel); const orderedIncoming = groupBy(forwards, f => f.incoming_channel);
const orderedOutgoing = groupBy(forwards, f => f.outgoing_channel); const orderedOutgoing = groupBy(forwards, f => f.outgoing_channel);
@ -14,7 +23,7 @@ export const getChannelVolume = forwards => {
return reduceTokens(together); return reduceTokens(together);
}; };
const reduceTokens = array => { const reduceTokens = (array: GroupedObject | TotalGroupedObject) => {
const reducedArray = []; const reducedArray = [];
for (const key in array) { for (const key in array) {
if (Object.prototype.hasOwnProperty.call(array, key)) { if (Object.prototype.hasOwnProperty.call(array, key)) {

View file

@ -1,9 +1,9 @@
import { getChannels, getChannel, getWalletInfo } from 'ln-service'; import { getChannels, getChannel, getWalletInfo } from 'ln-service';
import { getCorrectAuth, getAuthLnd } from 'server/helpers/helpers';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { to, toWithError } from 'server/helpers/async'; import { to, toWithError } from 'server/helpers/async';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { GetChannelsType, GetChannelType } from 'server/types/ln-service.types';
import { getFeeScore, getAverage, getMyFeeScore } from '../helpers'; import { getFeeScore, getAverage, getMyFeeScore } from '../helpers';
type ChannelFeesType = { type ChannelFeesType = {
@ -18,32 +18,33 @@ type ChannelFeesType = {
export default async (_: undefined, params: any, context: ContextType) => { export default async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getFeeHealth'); await requestLimiter(context.ip, 'getFeeHealth');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const { public_key } = await to(getWalletInfo({ lnd })); const { public_key } = await to(getWalletInfo({ lnd }));
const { channels } = await to(getChannels({ lnd })); const { channels } = await to<GetChannelsType>(getChannels({ lnd }));
const getChannelList = () => const getChannelList = () =>
Promise.all( Promise.all(
channels channels
.map(async channel => { .map(async channel => {
const { id, partner_public_key: publicKey } = channel; const { id, partner_public_key: publicKey } = channel;
const [{ policies }, channelError] = await toWithError( const [channelInfo, channelError] = await toWithError(
getChannel({ getChannel({
lnd, lnd,
id, id,
}) })
); );
if (channelError) { if (channelError || !channelInfo) {
logger.debug( logger.debug(
`Error getting channel with id ${id}: %o`, `Error getting channel with id ${id}: %o`,
channelError channelError
); );
return; return null;
} }
const policies = (channelInfo as GetChannelType).policies;
let partnerBaseFee = 0; let partnerBaseFee = 0;
let partnerFeeRate = 0; let partnerFeeRate = 0;
let myBaseFee = 0; let myBaseFee = 0;
@ -77,7 +78,7 @@ export default async (_: undefined, params: any, context: ContextType) => {
const list = await getChannelList(); const list = await getChannelList();
const health = list.map((channel: ChannelFeesType) => { const health = (list as ChannelFeesType[]).map((channel: ChannelFeesType) => {
const partnerRateScore = getFeeScore(2000, channel.partnerFeeRate); const partnerRateScore = getFeeScore(2000, channel.partnerFeeRate);
const partnerBaseScore = getFeeScore(100000, channel.partnerBaseFee); const partnerBaseScore = getFeeScore(100000, channel.partnerBaseFee);
const myRateScore = getMyFeeScore(2000, channel.myFeeRate, 200); const myRateScore = getMyFeeScore(2000, channel.myFeeRate, 200);

View file

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

View file

@ -1,9 +1,12 @@
import { getForwards, getChannels, getWalletInfo } from 'ln-service'; import { getForwards, getChannels, getWalletInfo } from 'ln-service';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getCorrectAuth, getAuthLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async'; import { to } from 'server/helpers/async';
import { subMonths } from 'date-fns'; import { subMonths } from 'date-fns';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import {
GetChannelsType,
GetForwardsType,
} from 'server/types/ln-service.types';
import { getChannelVolume, getChannelIdInfo, getAverage } from '../helpers'; import { getChannelVolume, getChannelIdInfo, getAverage } from '../helpers';
const monthInBlocks = 4380; const monthInBlocks = 4380;
@ -11,22 +14,26 @@ const monthInBlocks = 4380;
export default async (_: undefined, params: any, context: ContextType) => { export default async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getVolumeHealth'); await requestLimiter(context.ip, 'getVolumeHealth');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const before = new Date().toISOString(); const before = new Date().toISOString();
const after = subMonths(new Date(), 1).toISOString(); const after = subMonths(new Date(), 1).toISOString();
const { current_block_height } = await to(getWalletInfo({ lnd })); const { current_block_height } = await to(getWalletInfo({ lnd }));
const { channels } = await to(getChannels({ lnd })); const { channels } = await to<GetChannelsType>(getChannels({ lnd }));
const { forwards } = await to(getForwards({ lnd, after, before })); const { forwards } = await to<GetForwardsType>(
getForwards({ lnd, after, before })
);
const channelVolume = getChannelVolume(forwards); const channelVolume: { channel: string; tokens: number }[] = getChannelVolume(
forwards
);
const channelDetails = channels const channelDetails = channels
.map(channel => { .map(channel => {
const { tokens } = const { tokens } = channelVolume.find(c => c.channel === channel.id) || {
channelVolume.find(c => c.channel === channel.id) || {}; tokens: 0,
};
const info = getChannelIdInfo(channel.id); const info = getChannelIdInfo(channel.id);
if (!info) return; if (!info) return;
@ -45,23 +52,26 @@ export default async (_: undefined, params: any, context: ContextType) => {
}) })
.filter(Boolean); .filter(Boolean);
const average = getAverage(channelDetails.map(c => c.volumeNormalized)); const average = getAverage(channelDetails.map(c => c?.volumeNormalized || 0));
const health = channelDetails.map(channel => { const health = channelDetails
const diff = (channel.volumeNormalized - average) / average || -1; .map(channel => {
const score = Math.round((diff + 1) * 100); if (!channel) return null;
const diff = (channel.volumeNormalized - average) / average || -1;
const score = Math.round((diff + 1) * 100);
return { return {
id: channel.id, id: channel.id,
score, score,
volumeNormalized: channel.volumeNormalized, volumeNormalized: channel.volumeNormalized,
averageVolumeNormalized: average, averageVolumeNormalized: average,
partner: { publicKey: channel.publicKey, lnd }, partner: { publicKey: channel.publicKey, lnd },
}; };
}); })
.filter(Boolean);
const globalAverage = Math.round( const globalAverage = Math.round(
getAverage(health.map(c => Math.min(c.score, 100))) getAverage(health.map(c => Math.min(c?.score || 0, 100)))
); );
return { score: globalAverage, channels: health }; return { score: globalAverage, channels: health };

View file

@ -1,103 +0,0 @@
import getConfig from 'next/config';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { logger } from 'server/helpers/logger';
import { appUrls } from 'server/utils/appUrls';
import { getHodlParams } from 'server/helpers/hodlHelpers';
const { serverRuntimeConfig } = getConfig() || {};
const { hodlKey } = serverRuntimeConfig || {};
const defaultQuery = {
filters: {},
sort: {
by: '',
direction: '',
},
};
export const hodlResolvers = {
Query: {
getCountries: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getCountries');
const headers = {
Authorization: `Bearer ${hodlKey}`,
};
try {
const response = await fetch(`${appUrls.hodlhodl}/v1/countries`, {
headers,
});
const json = await response.json();
if (json) {
const { countries } = json;
return countries;
}
throw new Error('Problem getting HodlHodl countries.');
} catch (error) {
logger.error('Error getting HodlHodl countries: %o', error);
throw new Error('Problem getting HodlHodl countries.');
}
},
getCurrencies: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getCurrencies');
const headers = {
Authorization: `Bearer ${hodlKey}`,
};
try {
const response = await fetch(`${appUrls.hodlhodl}/v1/currencies`, {
headers,
});
const json = await response.json();
if (json) {
const { currencies } = json;
return currencies;
}
throw new Error('Problem getting HodlHodl currencies.');
} catch (error) {
logger.error('Error getting HodlHodl currencies: %o', error);
throw new Error('Problem getting HodlHodl currencies.');
}
},
getOffers: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getOffers');
let queryParams = defaultQuery;
if (params.filter) {
try {
queryParams = JSON.parse(params.filter);
} catch (error) {
queryParams = defaultQuery;
}
}
try {
const fullParams = {
...queryParams,
};
const paramString = getHodlParams(fullParams);
const response = await fetch(
`${appUrls.hodlhodl}/v1/offers${paramString}`
);
const json = await response.json();
if (json) {
const { offers } = json;
return offers;
}
throw new Error('Problem getting HodlHodl offers.');
} catch (error) {
logger.error('Error getting HodlHodl offers: %o', error);
throw new Error('Problem getting HodlHodl offers.');
}
},
},
};

View file

@ -1,69 +0,0 @@
import { gql } from 'apollo-server-micro';
export const hodlTypes = gql`
type hodlCountryType {
code: String
name: String
native_name: String
currency_code: String
currency_name: String
}
type hodlCurrencyType {
code: String
name: String
type: String
}
type hodlOfferFeeType {
author_fee_rate: String
}
type hodlOfferPaymentType {
id: String
version: String
payment_method_id: String
payment_method_type: String
payment_method_name: String
}
type hodlOfferTraderType {
login: String
online_status: String
rating: String
trades_count: Int
url: String
verified: Boolean
verified_by: String
strong_hodler: Boolean
country: String
country_code: String
average_payment_time_minutes: Int
average_release_time_minutes: Int
days_since_last_trade: Int
}
type hodlOfferType {
id: String
version: String
asset_code: String
searchable: Boolean
country: String
country_code: String
working_now: Boolean
side: String
title: String
description: String
currency_code: String
price: String
min_amount: String
max_amount: String
first_trade_limit: String
fee: hodlOfferFeeType
balance: String
payment_window_minutes: Int
confirmations: Int
payment_method_instructions: [hodlOfferPaymentType]
trader: hodlOfferTraderType
}
`;

View file

@ -1,13 +1,11 @@
import merge from 'lodash.merge'; import merge from 'lodash.merge';
import { makeExecutableSchema } from 'apollo-server-micro'; import { makeExecutableSchema } from 'graphql-tools';
import { nodeTypes } from './node/types'; import { nodeTypes } from './node/types';
import { nodeResolvers } from './node/resolvers'; import { nodeResolvers } from './node/resolvers';
import { authResolvers } from './auth/resolvers'; import { authResolvers } from './auth/resolvers';
import { generalTypes, queryTypes, mutationTypes } from './types'; import { generalTypes, queryTypes, mutationTypes } from './types';
import { accountResolvers } from './account/resolvers'; import { accountResolvers } from './account/resolvers';
import { accountTypes } from './account/types'; import { accountTypes } from './account/types';
import { hodlTypes } from './hodlhodl/types';
import { hodlResolvers } from './hodlhodl/resolvers';
import { lnpayResolvers } from './lnpay/resolvers'; import { lnpayResolvers } from './lnpay/resolvers';
import { lnpayTypes } from './lnpay/types'; import { lnpayTypes } from './lnpay/types';
import { bitcoinResolvers } from './bitcoin/resolvers'; import { bitcoinResolvers } from './bitcoin/resolvers';
@ -49,7 +47,6 @@ const typeDefs = [
mutationTypes, mutationTypes,
nodeTypes, nodeTypes,
accountTypes, accountTypes,
hodlTypes,
lnpayTypes, lnpayTypes,
bitcoinTypes, bitcoinTypes,
peerTypes, peerTypes,
@ -72,7 +69,6 @@ const resolvers = merge(
nodeResolvers, nodeResolvers,
authResolvers, authResolvers,
accountResolvers, accountResolvers,
hodlResolvers,
lnpayResolvers, lnpayResolvers,
bitcoinResolvers, bitcoinResolvers,
peerResolvers, peerResolvers,
@ -93,4 +89,4 @@ const resolvers = merge(
tbaseResolvers tbaseResolvers
); );
export default makeExecutableSchema({ typeDefs, resolvers }); export const schema = makeExecutableSchema({ typeDefs, resolvers });

View file

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

View file

@ -4,16 +4,16 @@ export const invoiceTypes = gql`
type decodeType { type decodeType {
chain_address: String chain_address: String
cltv_delta: Int cltv_delta: Int
description: String description: String!
description_hash: String description_hash: String
destination: String destination: String!
expires_at: String expires_at: String!
id: String id: String!
mtokens: String mtokens: String!
payment: String payment: String
routes: [[RouteType]] routes: [[RouteType]]!
safe_tokens: Int safe_tokens: Int!
tokens: Int tokens: Int!
destination_node: Node! destination_node: Node!
probe_route: ProbeRoute probe_route: ProbeRoute
} }
@ -23,7 +23,7 @@ export const invoiceTypes = gql`
channel: String channel: String
cltv_delta: Int cltv_delta: Int
fee_rate: Int fee_rate: Int
public_key: String public_key: String!
} }
type payType { type payType {

View file

@ -6,14 +6,18 @@ import { appUrls } from 'server/utils/appUrls';
export const lnpayResolvers = { export const lnpayResolvers = {
Query: { Query: {
getLnPay: async (_: undefined, params: any, context: ContextType) => { getLnPay: async (
_: undefined,
params: { amount: number },
context: ContextType
) => {
await requestLimiter(context.ip, 'getLnPay'); await requestLimiter(context.ip, 'getLnPay');
const [response, error] = await toWithError( const [response, error] = await toWithError(
fetch(`${appUrls.lnpay}?amount=${params.amount}`) fetch(`${appUrls.lnpay}?amount=${params.amount}`)
); );
if (error) { if (error || !response) {
logger.debug('Unable to get lnpay invoice: %o', error); logger.debug('Unable to get lnpay invoice: %o', error);
throw new Error('NoLnPayInvoice'); throw new Error('NoLnPayInvoice');
} }
@ -26,7 +30,7 @@ export const lnpayResolvers = {
const [response, error] = await toWithError(fetch(appUrls.lnpay)); const [response, error] = await toWithError(fetch(appUrls.lnpay));
if (error) { if (error || !response) {
logger.debug('Unable to connect to ThunderHub LNPAY'); logger.debug('Unable to connect to ThunderHub LNPAY');
throw new Error('NoLnPay'); throw new Error('NoLnPay');
} }

View file

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

View file

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

View file

@ -1,39 +1,45 @@
import { import { getNode, getWalletInfo, getClosedChannels } from 'ln-service';
getNode as getLnNode,
getWalletInfo,
getClosedChannels,
} from 'ln-service';
import { to, toWithError } from 'server/helpers/async'; import { to, toWithError } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth, getLnd } from '../../helpers/helpers'; import {
ClosedChannelsType,
LndObject,
GetWalletInfoType,
GetNodeType,
} from 'server/types/ln-service.types';
import { ContextType } from '../../types/apiTypes'; import { ContextType } from '../../types/apiTypes';
import { logger } from '../../helpers/logger'; import { logger } from '../../helpers/logger';
const errorNode = { alias: 'Node not found' }; const errorNode = { alias: 'Node not found' };
type NodeParent = {
lnd: LndObject;
publicKey: string;
withChannels?: boolean;
};
export const nodeResolvers = { export const nodeResolvers = {
Query: { Query: {
getNode: async (_: undefined, params: any, context: ContextType) => { getNode: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'closedChannels'); await requestLimiter(context.ip, 'closedChannels');
const { auth, withoutChannels = true, publicKey } = params; const { withoutChannels = true, publicKey } = params;
const lnd = getLnd(auth, context); const { lnd } = context;
return { lnd, publicKey, withChannels: !withoutChannels }; return { lnd, publicKey, withChannels: !withoutChannels };
}, },
getNodeInfo: async (_: undefined, params: any, context: ContextType) => { getNodeInfo: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'nodeInfo'); await requestLimiter(context.ip, 'nodeInfo');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const info = await to( const info = await to<GetWalletInfoType>(
getWalletInfo({ getWalletInfo({
lnd, lnd,
}) })
); );
const closedChannels = await to( const closedChannels: ClosedChannelsType = await to(
getClosedChannels({ getClosedChannels({
lnd, lnd,
}) })
@ -46,7 +52,7 @@ export const nodeResolvers = {
}, },
}, },
Node: { Node: {
node: async parent => { node: async (parent: NodeParent) => {
const { lnd, withChannels, publicKey } = parent; const { lnd, withChannels, publicKey } = parent;
if (!lnd) { if (!lnd) {
@ -60,19 +66,19 @@ export const nodeResolvers = {
} }
const [info, error] = await toWithError( const [info, error] = await toWithError(
getLnNode({ getNode({
lnd, lnd,
is_omitting_channels: !withChannels, is_omitting_channels: !withChannels,
public_key: publicKey, public_key: publicKey,
}) })
); );
if (error) { if (error || !info) {
logger.debug(`Error getting node with key: ${publicKey}`); logger.debug(`Error getting node with key: ${publicKey}`);
return errorNode; return errorNode;
} }
return { ...info, public_key: publicKey }; return { ...(info as GetNodeType), public_key: publicKey };
}, },
}, },
}; };

View file

@ -11,24 +11,24 @@ export const nodeTypes = gql`
} }
type Node { type Node {
node: nodeType node: nodeType!
} }
type nodeInfoType { type nodeInfoType {
chains: [String] chains: [String!]!
color: String color: String!
active_channels_count: Int active_channels_count: Int!
closed_channels_count: Int closed_channels_count: Int!
alias: String alias: String!
current_block_hash: String current_block_hash: String!
current_block_height: Int current_block_height: Int!
is_synced_to_chain: Boolean is_synced_to_chain: Boolean!
is_synced_to_graph: Boolean is_synced_to_graph: Boolean!
latest_block_at: String latest_block_at: String!
peers_count: Int peers_count: Int!
pending_channels_count: Int pending_channels_count: Int!
public_key: String public_key: String!
uris: [String] uris: [String!]!
version: String version: String!
} }
`; `;

View file

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

View file

@ -2,15 +2,15 @@ import { gql } from 'apollo-server-micro';
export const peerTypes = gql` export const peerTypes = gql`
type peerType { type peerType {
bytes_received: Int bytes_received: Int!
bytes_sent: Int bytes_sent: Int!
is_inbound: Boolean is_inbound: Boolean!
is_sync_peer: Boolean is_sync_peer: Boolean
ping_time: Int ping_time: Int!
public_key: String public_key: String!
socket: String socket: String!
tokens_received: Int tokens_received: Int!
tokens_sent: Int tokens_sent: Int!
partner_node_info: Node partner_node_info: Node!
} }
`; `;

View file

@ -6,15 +6,21 @@ import {
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger'; import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { toWithError, to } from 'server/helpers/async'; import { toWithError, to } from 'server/helpers/async';
import { LndObject, ProbeForRouteType } from 'server/types/ln-service.types';
type RouteParent = {
lnd: LndObject;
destination: string;
tokens: number;
};
export const routeResolvers = { export const routeResolvers = {
Query: { Query: {
getRoutes: async (_: undefined, params: any, context: ContextType) => { getRoutes: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getRoutes'); await requestLimiter(context.ip, 'getRoutes');
const lnd = getLnd(params.auth, context); const { lnd } = context;
const { public_key } = await getWalletInfo({ lnd }); const { public_key } = await getWalletInfo({ lnd });
@ -37,7 +43,7 @@ export const routeResolvers = {
}, },
}, },
ProbeRoute: { ProbeRoute: {
route: async parent => { route: async (parent: RouteParent) => {
const { lnd, destination, tokens } = parent; const { lnd, destination, tokens } = parent;
if (!lnd) { if (!lnd) {
@ -61,19 +67,20 @@ export const routeResolvers = {
return null; return null;
} }
if (!info.route) { if (!(info as ProbeForRouteType).route) {
logger.debug( logger.debug(
`No route found to destination ${destination} for ${tokens} tokens` `No route found to destination ${destination} for ${tokens} tokens`
); );
return null; return null;
} }
const hopsWithNodes = info.route.hops.map(h => ({ const hopsWithNodes =
...h, (info as ProbeForRouteType).route?.hops.map(h => ({
node: { lnd, publicKey: h.public_key }, ...h,
})); node: { lnd, publicKey: h.public_key },
})) || [];
return { ...info.route, hops: hopsWithNodes }; return { ...(info as ProbeForRouteType).route, hops: hopsWithNodes };
}, },
}, },
}; };

View file

@ -19,12 +19,17 @@ export const tbaseResolvers = {
body: JSON.stringify({ query }), body: JSON.stringify({ query }),
}) })
); );
if (fetchError) return []; if (fetchError || !response) return [];
const result = await response.json(); const result = await response.json();
const { errors, data } = result || {}; const { errors, data } = result || {};
if (errors) return []; if (errors) return [];
return data?.getNodes?.filter(n => n.public_key && n.socket) || []; return (
data?.getNodes?.filter(
(n: { public_key: string; socket: string }) =>
n.public_key && n.socket
) || []
);
}, },
}, },
}; };

View file

@ -4,7 +4,7 @@ export const tbaseTypes = gql`
type baseNodesType { type baseNodesType {
_id: String _id: String
name: String name: String
public_key: String public_key: String!
socket: String socket: String!
} }
`; `;

View file

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

View file

@ -1,67 +0,0 @@
export interface PaymentProps {
created_at: string;
destination: string;
fee: number;
fee_mtokens: string;
hops: string[];
id: string;
is_confirmed: boolean;
is_outgoing: boolean;
mtokens: string;
request: string;
secret: string;
tokens: number;
}
export interface PaymentsProps {
payments: PaymentProps[];
}
interface InvoiceMessagesType {
type: string;
value: string;
}
export interface InvoicePaymentProps {
confirmed_at: string;
created_at: string;
created_height: number;
in_channel: string;
is_canceled: boolean;
is_confirmed: boolean;
is_held: boolean;
messages: InvoiceMessagesType[];
mtokens: string;
pending_index: number;
tokens: number;
}
export interface InvoiceProps {
chain_address: string;
confirmed_at: string;
created_at: string;
description: string;
description_hash: string;
expires_at: string;
id: string;
is_canceled: boolean;
is_confirmed: boolean;
is_held: boolean;
is_outgoing: boolean;
is_private: boolean;
payments: InvoicePaymentProps[];
received: number;
received_mtokens: string;
request: string;
secret: string;
tokens: number;
}
export interface InvoicesProps {
invoices: InvoiceProps[];
next: string;
}
export interface NodeProps {
alias: string;
}

View file

@ -8,18 +8,24 @@ import { compareDesc, subHours, subDays, subMonths, subYears } from 'date-fns';
import { sortBy } from 'underscore'; import { sortBy } from 'underscore';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async'; import { to } from 'server/helpers/async';
import { ForwardCompleteProps } from '../widgets/resolvers/interface'; import {
import { PaymentsProps, InvoicesProps } from './interface'; GetInvoicesType,
GetPaymentsType,
InvoiceType,
PaymentType,
GetForwardsType,
} from 'server/types/ln-service.types';
type TransactionType = InvoiceType | PaymentType;
type TransactionWithType = { isTypeOf: string } & TransactionType;
export const transactionResolvers = { export const transactionResolvers = {
Query: { Query: {
getResume: async (_: undefined, params: any, context: ContextType) => { getResume: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'payments'); await requestLimiter(context.ip, 'payments');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const invoiceProps = params.token const invoiceProps = params.token
? { token: params.token } ? { token: params.token }
@ -30,7 +36,7 @@ export const transactionResolvers = {
let token = ''; let token = '';
let withInvoices = true; let withInvoices = true;
const invoiceList: InvoicesProps = await to( const invoiceList = await to<GetInvoicesType>(
getInvoices({ getInvoices({
lnd, lnd,
...invoiceProps, ...invoiceProps,
@ -52,10 +58,10 @@ export const transactionResolvers = {
const { date } = invoices[invoices.length - 1]; const { date } = invoices[invoices.length - 1];
firstInvoiceDate = invoices[0].date; firstInvoiceDate = invoices[0].date;
lastInvoiceDate = date; lastInvoiceDate = date;
token = invoiceList.next; token = invoiceList.next || '';
} }
const paymentList: PaymentsProps = await to( const paymentList = await to<GetPaymentsType>(
getPayments({ getPayments({
lnd, lnd,
}) })
@ -70,7 +76,7 @@ export const transactionResolvers = {
isTypeOf: 'PaymentType', isTypeOf: 'PaymentType',
})); }));
const filterArray = payment => { const filterArray = (payment: typeof payments[number]) => {
const last = const last =
compareDesc(new Date(lastInvoiceDate), new Date(payment.date)) === 1; compareDesc(new Date(lastInvoiceDate), new Date(payment.date)) === 1;
const first = params.token const first = params.token
@ -97,8 +103,7 @@ export const transactionResolvers = {
getForwards: async (_: undefined, params: any, context: ContextType) => { getForwards: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'forwards'); await requestLimiter(context.ip, 'forwards');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
let startDate = new Date(); let startDate = new Date();
const endDate = new Date(); const endDate = new Date();
@ -123,7 +128,7 @@ export const transactionResolvers = {
}) })
); );
const forwardsList: ForwardCompleteProps = await to( const forwardsList = await to<GetForwardsType>(
getLnForwards({ getLnForwards({
lnd, lnd,
after: startDate, after: startDate,
@ -153,7 +158,7 @@ export const transactionResolvers = {
}, },
}, },
Transaction: { Transaction: {
__resolveType(parent) { __resolveType(parent: TransactionWithType) {
return parent.isTypeOf; return parent.isTypeOf;
}, },
}, },

View file

@ -24,7 +24,7 @@ export const transactionTypes = gql`
destination_node: Node destination_node: Node
fee: Int! fee: Int!
fee_mtokens: String! fee_mtokens: String!
hops: [Node] hops: [Node!]!
id: String! id: String!
index: Int index: Int
is_confirmed: Boolean! is_confirmed: Boolean!

View file

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

View file

@ -2,14 +2,14 @@ import { gql } from 'apollo-server-micro';
export const walletTypes = gql` export const walletTypes = gql`
type walletInfoType { type walletInfoType {
build_tags: [String] build_tags: [String!]!
commit_hash: String commit_hash: String!
is_autopilotrpc_enabled: Boolean is_autopilotrpc_enabled: Boolean!
is_chainrpc_enabled: Boolean is_chainrpc_enabled: Boolean!
is_invoicesrpc_enabled: Boolean is_invoicesrpc_enabled: Boolean!
is_signrpc_enabled: Boolean is_signrpc_enabled: Boolean!
is_walletrpc_enabled: Boolean is_walletrpc_enabled: Boolean!
is_watchtowerrpc_enabled: Boolean is_watchtowerrpc_enabled: Boolean!
is_wtclientrpc_enabled: Boolean is_wtclientrpc_enabled: Boolean!
} }
`; `;

View file

@ -1,8 +1,8 @@
import { getChannels } from 'ln-service'; import { getChannels } from 'ln-service';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async'; import { to } from 'server/helpers/async';
import { GetChannelsType } from 'server/types/ln-service.types';
export const getChannelReport = async ( export const getChannelReport = async (
_: undefined, _: undefined,
@ -11,9 +11,9 @@ export const getChannelReport = async (
) => { ) => {
await requestLimiter(context.ip, 'channelReport'); await requestLimiter(context.ip, 'channelReport');
const lnd = getLnd(params.auth, context); const { lnd } = context;
const info = await to(getChannels({ lnd })); const info = await to<GetChannelsType>(getChannels({ lnd }));
if (!info || info?.channels?.length <= 0) { if (!info || info?.channels?.length <= 0) {
return; return;

View file

@ -4,10 +4,13 @@ import { sortBy } from 'underscore';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { getNodeFromChannel } from 'server/helpers/getNodeFromChannel'; import { getNodeFromChannel } from 'server/helpers/getNodeFromChannel';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async'; import { to } from 'server/helpers/async';
import {
GetForwardsType,
GetWalletInfoType,
} from 'server/types/ln-service.types';
import { countArray, countRoutes } from './helpers'; import { countArray, countRoutes } from './helpers';
import { ForwardCompleteProps } from './interface';
export const getForwardChannelsReport = async ( export const getForwardChannelsReport = async (
_: undefined, _: undefined,
@ -16,8 +19,7 @@ export const getForwardChannelsReport = async (
) => { ) => {
await requestLimiter(context.ip, 'forwardChannels'); await requestLimiter(context.ip, 'forwardChannels');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
let startDate = new Date(); let startDate = new Date();
const endDate = new Date(); const endDate = new Date();
@ -76,7 +78,7 @@ export const getForwardChannelsReport = async (
}) })
); );
const forwardsList: ForwardCompleteProps = await to( const forwardsList = await to<GetForwardsType>(
getForwards({ getForwards({
lnd, lnd,
after: startDate, after: startDate,
@ -84,7 +86,7 @@ export const getForwardChannelsReport = async (
}) })
); );
const walletInfo: { public_key: string } = await to( const walletInfo = await to<GetWalletInfoType>(
getWalletInfo({ getWalletInfo({
lnd, lnd,
}) })
@ -101,9 +103,15 @@ export const getForwardChannelsReport = async (
while (!finishedFetching) { while (!finishedFetching) {
if (next) { if (next) {
const moreForwards = await to(getForwards({ lnd, token: next })); const moreForwards = await to<GetForwardsType>(
getForwards({ lnd, token: next })
);
forwards = [...forwards, ...moreForwards.forwards]; forwards = [...forwards, ...moreForwards.forwards];
next = moreForwards.next; if (moreForwards.next) {
next = moreForwards.next;
} else {
finishedFetching = true;
}
} else { } else {
finishedFetching = true; finishedFetching = true;
} }

View file

@ -8,10 +8,10 @@ import {
} from 'date-fns'; } from 'date-fns';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async'; import { to } from 'server/helpers/async';
import { GetForwardsType } from 'server/types/ln-service.types';
import { reduceForwardArray } from './helpers'; import { reduceForwardArray } from './helpers';
import { ForwardCompleteProps } from './interface';
export const getForwardReport = async ( export const getForwardReport = async (
_: undefined, _: undefined,
@ -20,8 +20,7 @@ export const getForwardReport = async (
) => { ) => {
await requestLimiter(context.ip, 'forwardReport'); await requestLimiter(context.ip, 'forwardReport');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
let startDate = new Date(); let startDate = new Date();
const endDate = new Date(); const endDate = new Date();
@ -45,7 +44,7 @@ export const getForwardReport = async (
startDate = subHours(endDate, 24); startDate = subHours(endDate, 24);
} }
const forwardsList: ForwardCompleteProps = await to( const forwardsList = await to<GetForwardsType>(
getForwards({ getForwards({
lnd, lnd,
after: startDate, after: startDate,
@ -64,7 +63,9 @@ export const getForwardReport = async (
while (!finishedFetching) { while (!finishedFetching) {
if (next) { if (next) {
const moreForwards = await to(getForwards({ lnd, token: next })); const moreForwards = await to<GetForwardsType>(
getForwards({ lnd, token: next })
);
forwards = [...forwards, ...moreForwards.forwards]; forwards = [...forwards, ...moreForwards.forwards];
next = moreForwards.next; next = moreForwards.next;
} else { } else {

View file

@ -3,8 +3,12 @@ import { differenceInHours, differenceInCalendarDays } from 'date-fns';
import { groupBy } from 'underscore'; import { groupBy } from 'underscore';
import { ContextType } from 'server/types/apiTypes'; import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter'; import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import { to } from 'server/helpers/async'; import { to } from 'server/helpers/async';
import {
GetInvoicesType,
GetPaymentsType,
} from 'server/types/ln-service.types';
import { reduceInOutArray } from './helpers'; import { reduceInOutArray } from './helpers';
export const getInOut = async ( export const getInOut = async (
@ -14,8 +18,7 @@ export const getInOut = async (
) => { ) => {
await requestLimiter(context.ip, 'getInOut'); await requestLimiter(context.ip, 'getInOut');
const auth = getCorrectAuth(params.auth, context); const { lnd } = context;
const lnd = getAuthLnd(auth);
const endDate = new Date(); const endDate = new Date();
let periods = 7; let periods = 7;
@ -35,8 +38,10 @@ export const getInOut = async (
difference = (date: string) => differenceInHours(endDate, new Date(date)); difference = (date: string) => differenceInHours(endDate, new Date(date));
} }
const invoiceList = await to(getInvoices({ lnd, limit: 50 })); const invoiceList = await to<GetInvoicesType>(
const paymentList = await to(getPayments({ lnd })); getInvoices({ lnd, limit: 50 })
);
const paymentList = await to<GetPaymentsType>(getPayments({ lnd }));
let invoiceArray = invoiceList.invoices; let invoiceArray = invoiceList.invoices;
let next = invoiceList.next; let next = invoiceList.next;
@ -51,7 +56,9 @@ export const getInOut = async (
const dif = difference(lastInvoice.created_at); const dif = difference(lastInvoice.created_at);
if (next && dif < periods) { if (next && dif < periods) {
const newInvoices = await to(getInvoices({ lnd, token: next })); const newInvoices = await to<GetInvoicesType>(
getInvoices({ lnd, token: next })
);
invoiceArray = [...invoiceArray, ...newInvoices.invoices]; invoiceArray = [...invoiceArray, ...newInvoices.invoices];
next = newInvoices.next; next = newInvoices.next;
} else { } else {

View file

@ -1,6 +1,6 @@
import { reduce, groupBy } from 'underscore'; import { reduce, groupBy } from 'underscore';
import { ForwardType } from 'server/types/ln-service.types';
import { import {
ForwardProps,
ReduceObjectProps, ReduceObjectProps,
ListProps, ListProps,
InOutProps, InOutProps,
@ -11,7 +11,7 @@ export const reduceForwardArray = (list: ListProps) => {
const reducedOrder = []; const reducedOrder = [];
for (const key in list) { for (const key in list) {
if (Object.prototype.hasOwnProperty.call(list, key)) { if (Object.prototype.hasOwnProperty.call(list, key)) {
const element: ForwardProps[] = list[key]; const element: ForwardType[] = list[key];
const reducedArray: ReduceObjectProps = reduce( const reducedArray: ReduceObjectProps = reduce(
element, element,
(a: ReduceObjectProps, b: ReduceObjectProps) => { (a: ReduceObjectProps, b: ReduceObjectProps) => {
@ -19,7 +19,8 @@ export const reduceForwardArray = (list: ListProps) => {
fee: a.fee + b.fee, fee: a.fee + b.fee,
tokens: a.tokens + b.tokens, tokens: a.tokens + b.tokens,
}; };
} },
{ fee: 0, tokens: 0 }
); );
reducedOrder.push({ reducedOrder.push({
period: Number(key), period: Number(key),
@ -39,9 +40,10 @@ export const reduceInOutArray = (list: InOutListProps) => {
const element: InOutProps[] = list[key]; const element: InOutProps[] = list[key];
const reducedArray: InOutProps = reduce( const reducedArray: InOutProps = reduce(
element, element,
(a: ReduceObjectProps, b: ReduceObjectProps) => ({ (a, b) => ({
tokens: a.tokens + b.tokens, tokens: a.tokens + b.tokens,
}) }),
{ tokens: 0 }
); );
reducedOrder.push({ reducedOrder.push({
period: Number(key), period: Number(key),
@ -53,7 +55,7 @@ export const reduceInOutArray = (list: InOutListProps) => {
return reducedOrder; return reducedOrder;
}; };
export const countArray = (list: ForwardProps[], type: boolean) => { export const countArray = (list: ForwardType[], type: boolean) => {
const inOrOut = type ? 'incoming_channel' : 'outgoing_channel'; const inOrOut = type ? 'incoming_channel' : 'outgoing_channel';
const grouped = groupBy(list, inOrOut); const grouped = groupBy(list, inOrOut);
@ -82,7 +84,7 @@ export const countArray = (list: ForwardProps[], type: boolean) => {
return channelInfo; return channelInfo;
}; };
export const countRoutes = (list: ForwardProps[]) => { export const countRoutes = (list: ForwardType[]) => {
const grouped = groupBy(list, 'route'); const grouped = groupBy(list, 'route');
const channelInfo = []; const channelInfo = [];

View file

@ -1,20 +1,7 @@
export interface ForwardProps { import { ForwardType } from 'server/types/ln-service.types';
created_at: string;
fee: number;
fee_mtokens: string;
incoming_channel: string;
mtokens: string;
outgoing_channel: string;
tokens: number;
}
export interface ForwardCompleteProps {
forwards: ForwardProps[];
next: string;
}
export interface ListProps { export interface ListProps {
[key: string]: ForwardProps[]; [key: string]: ForwardType[];
} }
export interface ReduceObjectProps { export interface ReduceObjectProps {

View file

@ -2,6 +2,14 @@ import * as res from '../lnServiceResponse';
export const authenticatedLndGrpc = jest.fn().mockReturnValue({}); 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 export const getNetworkInfo = jest
.fn() .fn()
.mockReturnValue(Promise.resolve(res.getNetworkInfoResponse)); .mockReturnValue(Promise.resolve(res.getNetworkInfoResponse));

View file

@ -342,6 +342,39 @@ export const getNodeResponse = {
updated_at: '2011-10-05T14:48:00.000Z', 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 = { export const getChannelResponse = {
capacity: 1000, capacity: 1000,
id: '100x1x1', id: '100x1x1',

View file

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

View file

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

View file

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

View file

@ -0,0 +1 @@
export type RebalanceResponseType = { rebalance: [{}, {}, {}] };

View file

@ -0,0 +1,104 @@
export type LndObject = {};
export type ChannelType = {
id: string;
tokens: number;
is_partner_initiated: boolean;
commit_transaction_fee: number;
is_active: boolean;
local_balance: number;
remote_balance: number;
partner_public_key: string;
time_offline?: number;
time_online?: number;
};
export type DecodedType = {
destination: string;
tokens: number;
};
export type ClosedChannelsType = {
channels: [];
};
export type CloseChannelType = {
transaction_id: string;
transaction_vout: number;
};
export type OpenChannelType = {
transaction_id: string;
transaction_vout: number;
};
export type InvoiceType = {
id: string;
created_at: string;
confirmed_at: string;
tokens: number;
is_confirmed: boolean;
received: number;
payments: { messages: [{ type: string; value: string }] }[];
};
export type PaymentType = {
created_at: string;
is_confirmed: boolean;
tokens: number;
destination: string;
hops: string[];
};
export type ForwardType = {
tokens: number;
incoming_channel: string;
outgoing_channel: string;
created_at: string;
fee: number;
};
export type GetWalletInfoType = {
alias: string;
public_key: string;
};
export type GetNodeType = { alias: string; color: string };
export type UtxoType = {};
export type ChainTransaction = {};
export type ProbeForRouteType = { route?: { hops: [{ public_key: string }] } };
export type GetChannelType = {
policies: {
public_key: string;
base_fee_mtokens: string;
fee_rate: number;
}[];
};
export type GetChannelsType = { channels: ChannelType[] };
export type GetForwardsType = { forwards: ForwardType[]; next?: string };
export type GetInvoicesType = { invoices: InvoiceType[]; next?: string };
export type GetPaymentsType = { payments: PaymentType[]; next?: string };
export type GetChainBalanceType = { chain_balance: number };
export type GetPendingChainBalanceType = { pending_chain_balance: number };
export type GetChainTransactionsType = { transactions: ChainTransaction[] };
export type GetUtxosType = { utxos: UtxoType[] };
export type SendToChainAddressType = {
id: string;
confirmation_count: number;
is_confirmed: boolean;
is_outgoing: boolean;
tokens: number | null;
};

View file

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

View file

@ -15,7 +15,6 @@ export const appUrls = {
blockchain: 'https://www.blockchain.com/btc/tx/', blockchain: 'https://www.blockchain.com/btc/tx/',
fees: 'https://mempool.space/api/v1/fees/recommended', fees: 'https://mempool.space/api/v1/fees/recommended',
ticker: 'https://blockchain.info/ticker', ticker: 'https://blockchain.info/ticker',
hodlhodl: 'https://hodlhodl.com/api',
github: 'https://api.github.com/repos/apotdevin/thunderhub/releases/latest', github: 'https://api.github.com/repos/apotdevin/thunderhub/releases/latest',
update: 'https://github.com/apotdevin/thunderhub#updating', update: 'https://github.com/apotdevin/thunderhub#updating',
}; };

View file

@ -1,23 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import {
useAccountDispatch,
useAccountState,
CompleteAccount,
} 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 { useRouter } from 'next/router';
import { appendBasePath } from 'src/utils/basePath';
import { getUrlParam } from 'src/utils/url'; import { getUrlParam } from 'src/utils/url';
import { useGetAuthTokenQuery } from 'src/graphql/queries/__generated__/getAuthToken.generated'; import { useGetAuthTokenQuery } from 'src/graphql/queries/__generated__/getAuthToken.generated';
import { toast } from 'react-toastify';
export const ServerAccounts = () => { export const ServerAccounts: React.FC = () => {
const { hasAccount } = useAccountState(); const { push, query } = useRouter();
const dispatch = useAccountDispatch();
const { push, pathname, query } = useRouter();
const { data, loading, refetch } = useGetServerAccountsQuery();
const cookieParam = getUrlParam(query?.token); const cookieParam = getUrlParam(query?.token);
@ -28,41 +16,14 @@ export const ServerAccounts = () => {
}); });
React.useEffect(() => { React.useEffect(() => {
if (cookieParam && authData && authData.getAuthToken) { if (!cookieParam || !authData) return;
refetch(); if (authData.getAuthToken) {
push(appendBasePath('/')); push('/');
} }
}, [push, authData, cookieParam, refetch]); if (!authData.getAuthToken) {
toast.warning('Unable to SSO. Check your logs.');
React.useEffect(() => {
if (hasAccount === 'error' && pathname !== '/') {
toast.error('No account found');
dispatch({ type: 'resetFetch' });
push(appendBasePath('/'));
} }
}, [hasAccount, push, dispatch, pathname]); }, [push, authData, cookieParam]);
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 => 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]);
return null; return null;
}; };

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