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
# -----------
# LOG_LEVEL='info'
# HODL_KEY='HODL_HODL_API_KEY'
# BASE_PATH='/basePath'
# -----------
@ -24,9 +23,7 @@
# -----------
# FETCH_PRICES=false
# FETCH_FEES=false
# HODL_HODL=false
# DISABLE_LINKS=true
# NO_CLIENT_ACCOUNTS=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 '*.svg';
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 .
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
# ---------------

View file

@ -102,22 +102,7 @@ This repository consists of a **NextJS** server that handles both the backend **
## **Requirements**
- Yarn/npm installed
- Node installed (Version 12.16.0 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!**
- Node installed (Version 10 or higher)
---
@ -132,7 +117,6 @@ You can define some environment variables that ThunderHub can start with. To do
# Server Configs
# -----------
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: ''
# -----------
@ -146,9 +130,7 @@ CURRENCY = 'sat' | 'btc' | 'fiat' # Default: 'sat'
# -----------
FETCH_PRICES = true | false # Default: true
FETCH_FEES = true | false # Default: true
HODL_HODL = true | false # Default: true
DISABLE_LINKS = true | false # Default: false
NO_CLIENT_ACCOUNTS = 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.
**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**
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 .
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
# ---------------

View file

@ -14,9 +14,6 @@ COPY package.json .
COPY package-lock.json .
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
# ---------------

View file

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

View file

@ -1,32 +1,29 @@
/* eslint @typescript-eslint/no-var-requires: 0 */
import * as React from 'react';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import { ApolloClient } from 'apollo-client';
import { IncomingMessage, ServerResponse } from 'http';
import { useMemo } from 'react';
import {
ApolloClient,
InMemoryCache,
IntrospectionFragmentMatcher,
} from 'apollo-cache-inmemory';
NormalizedCacheObject,
} from '@apollo/client';
import getConfig from 'next/config';
import introspectionQueryResultData from 'src/graphql/fragmentTypes.json';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
let globalApolloClient = null;
const { publicRuntimeConfig } = getConfig();
const { apiUrl: uri } = publicRuntimeConfig;
function createIsomorphLink(ctx) {
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');
let apolloClient: ReturnType<typeof createApolloClient> | null = null;
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({
uri,
credentials: 'same-origin',
@ -34,132 +31,38 @@ function createIsomorphLink(ctx) {
}
}
/**
* 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
function createApolloClient(req?: IncomingMessage, res?: ServerResponse) {
return new ApolloClient({
ssrMode,
link: createIsomorphLink(ctx),
cache,
credentials: 'same-origin',
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(req, res),
cache: new InMemoryCache({
possibleTypes: { Transaction: ['InvoiceType', 'PaymentType'] },
}),
});
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(ctx, initialState?) {
// 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);
}
export function initializeApollo(
initialState: NormalizedCacheObject | null = null,
req?: IncomingMessage,
res?: ServerResponse
) {
const _apolloClient = apolloClient ?? createApolloClient(req, res);
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(ctx, initialState);
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return globalApolloClient;
return _apolloClient;
}
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* 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;
export function useApollo(initialState: NormalizedCacheObject | null) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}

View file

@ -6,10 +6,10 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
module.exports = withBundleAnalyzer({
poweredByHeader: false,
assetPrefix: process.env.BASE_PATH || '',
basePath: process.env.BASE_PATH || '',
serverRuntimeConfig: {
nodeEnv: process.env.NODE_ENV || 'development',
logLevel: process.env.LOG_LEVEL || 'info',
hodlKey: process.env.HODL_KEY || '',
cookiePath: process.env.COOKIE_PATH || '',
lnServerUrl: process.env.SSO_SERVER_URL || '',
lnCertPath: process.env.SSO_CERT_PATH || '',
@ -20,15 +20,12 @@ module.exports = withBundleAnalyzer({
nodeEnv: process.env.NODE_ENV || 'development',
apiUrl: `${process.env.BASE_PATH || ''}/api/v1`,
apiBaseUrl: `${process.env.API_BASE_URL || ''}/api/v1`,
basePath: process.env.BASE_PATH || '',
npmVersion: process.env.npm_package_version || '0.0.0',
defaultTheme: process.env.THEME || 'dark',
defaultCurrency: process.env.CURRENCY || 'sat',
fetchPrices: process.env.FETCH_PRICES === '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,
noClient: process.env.NO_CLIENT_ACCOUNTS === '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",
"scripts": {
"bs": "yarn build && yarn start",
"dev": "cross-env NODE_OPTIONS='--insecure-http-parser' next",
"dev:compatible": "next",
"dev": "next",
"build": "next build",
"start": "cross-env NODE_OPTIONS='--insecure-http-parser' next start",
"start:two": "cross-env NODE_OPTIONS='--insecure-http-parser' next start -p 3001",
"start:compatible": "next start",
"start:compatible:two": "next start -p 3001",
"start": "next start",
"start:two": "next start -p 3001",
"start:cookie": "sh ./scripts/initCookie.sh",
"lint": "eslint */**/*.{js,ts,tsx} --fix",
"prettier": "prettier --write **/*.{ts,tsx,js,css,html}",
"lint": "eslint . --ext ts --ext tsx --ext js",
"format": "prettier --write \"**/*.{js,ts,tsx}\"",
"release": "standard-version --sign",
"release:push": "standard-version --sign && git push --follow-tags origin master",
"release:test": "standard-version --sign --dry-run",
"release:minor": "standard-version --sign --release-as minor && git push --follow-tags origin master",
"analyze": "cross-env ANALYZE=true next build",
"generate": "graphql-codegen --config codegen.yml && yarn lint",
"analyze": "npx cross-env ANALYZE=true next build",
"generate": "graphql-codegen --config codegen.yml && yarn format",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
@ -37,35 +34,27 @@
"author": "",
"license": "MIT",
"dependencies": {
"@apollo/react-common": "^3.1.4",
"@apollo/react-hooks": "^3.1.5",
"@apollo/react-ssr": "^3.1.5",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"apollo-link-http": "^1.5.17",
"apollo-link-schema": "^1.2.5",
"apollo-server-micro": "^2.15.1",
"apollo-utilities": "^1.3.4",
"@apollo/client": "^3.1.2",
"@next/bundle-analyzer": "^9.5.1",
"@types/react": "^16.9.44",
"apollo-server-micro": "^2.16.1",
"balanceofsatoshis": "^5.40.2",
"base64url": "^3.0.1",
"bcryptjs": "^2.4.3",
"cookie": "^0.4.1",
"crypto-js": "^4.0.0",
"date-fns": "^2.14.0",
"date-fns": "^2.15.0",
"graphql": "^15.3.0",
"graphql-iso-date": "^3.6.1",
"graphql-rate-limit": "^2.0.1",
"graphql-tag": "^2.10.4",
"intersection-observer": "^0.11.0",
"js-cookie": "^2.2.1",
"js-yaml": "^3.14.0",
"jsonwebtoken": "^8.5.1",
"ln-service": "^49.3.1",
"ln-service": "^49.4.3",
"lodash.debounce": "^4.0.8",
"lodash.groupby": "^4.6.0",
"lodash.merge": "^4.6.2",
"lodash.omit": "^4.5.0",
"next": "^9.4.4",
"next": "^9.5.1",
"numeral": "^2.0.6",
"qrcode.react": "^1.0.0",
"react": "^16.13.1",
@ -77,63 +66,73 @@
"react-spinners": "^0.9.0",
"react-spring": "^8.0.27",
"react-toastify": "^6.0.8",
"react-tooltip": "^4.2.7",
"react-tooltip": "^4.2.8",
"styled-components": "^5.1.1",
"styled-react-modal": "^2.0.1",
"styled-theming": "^2.2.0",
"typescript": "^3.9.7",
"underscore": "^1.10.2",
"uuid": "^8.2.0",
"victory": "^35.0.3",
"winston": "^3.3.3",
"zxcvbn": "^4.4.2"
"uuid": "^8.3.0",
"victory": "^35.0.8",
"winston": "^3.3.3"
},
"devDependencies": {
"@babel/core": "^7.10.4",
"@commitlint/cli": "^9.0.1",
"@commitlint/config-conventional": "^9.0.1",
"@graphql-codegen/cli": "^1.17.0",
"@graphql-codegen/fragment-matcher": "^1.17.0",
"@graphql-codegen/introspection": "^1.17.0",
"@graphql-codegen/near-operation-file-preset": "^1.17.0",
"@graphql-codegen/typescript": "^1.17.0",
"@graphql-codegen/typescript-operations": "^1.17.0",
"@graphql-codegen/typescript-react-apollo": "^1.17.0",
"@graphql-codegen/typescript-resolvers": "^1.17.0",
"@next/bundle-analyzer": "^9.4.4",
"@testing-library/jest-dom": "^5.11.0",
"@testing-library/react": "^10.4.5",
"@babel/core": "^7.11.1",
"@commitlint/cli": "^9.1.1",
"@commitlint/config-conventional": "^9.1.1",
"@graphql-codegen/cli": "^1.17.7",
"@graphql-codegen/fragment-matcher": "^1.17.7",
"@graphql-codegen/introspection": "^1.17.7",
"@graphql-codegen/near-operation-file-preset": "^1.17.8",
"@graphql-codegen/typescript": "^1.17.7",
"@graphql-codegen/typescript-operations": "^1.17.7",
"@graphql-codegen/typescript-react-apollo": "^2.0.5",
"@graphql-codegen/typescript-resolvers": "^1.17.7",
"@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^10.4.8",
"@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/node": "^14.0.22",
"@types/react": "^16.9.43",
"@types/styled-components": "^5.1.1",
"@types/styled-theming": "^2.2.4",
"@typescript-eslint/eslint-plugin": "^3.6.0",
"@typescript-eslint/parser": "^3.6.0",
"apollo-server": "^2.15.1",
"apollo-server-testing": "^2.15.1",
"babel-jest": "^26.1.0",
"@types/lodash.groupby": "^4.6.6",
"@types/lodash.merge": "^4.6.6",
"@types/lodash.omit": "^4.5.6",
"@types/lodash.sortby": "^4.7.6",
"@types/node": "^14.0.27",
"@types/numeral": "0.0.28",
"@types/qrcode.react": "^1.0.1",
"@types/react-copy-to-clipboard": "^4.3.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-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",
"cross-env": "^7.0.2",
"devmoji": "^2.1.9",
"eslint": "^7.4.0",
"eslint": "^7.6.0",
"eslint-config-prettier": "^6.11.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-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.3",
"eslint-plugin-react-hooks": "^4.0.7",
"fast-diff": "^1.2.0",
"eslint-plugin-react": "^7.20.5",
"eslint-plugin-react-hooks": "^4.0.8",
"husky": "^4.2.5",
"jest": "^26.1.0",
"jest": "^26.2.2",
"jest-fetch-mock": "^3.0.3",
"lint-staged": "^10.2.11",
"prettier": "^2.0.5",
"standard-version": "^8.0.2",
"typescript": "^3.9.6"
"standard-version": "^8.0.2"
},
"husky": {
"hooks": {

View file

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

View file

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

View file

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

View file

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

View file

@ -2,9 +2,12 @@ import * as React from 'react';
import styled from 'styled-components';
import { Users } from 'react-feather';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { ChatInit } from 'src/components/chat/ChatInit';
import { ChatFetcher } from 'src/components/chat/ChatFetcher';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_MESSAGES } from 'src/graphql/queries/getMessages';
import { useNodeInfo } from 'src/hooks/UseNodeInfo';
import { useChatState } from '../src/context/ChatContext';
import { separateBySender, getSenders } from '../src/utils/chat';
import {
@ -16,7 +19,6 @@ import {
import { Contacts } from '../src/views/chat/Contacts';
import { ChatBox } from '../src/views/chat/ChatBox';
import { ChatStart } from '../src/views/chat/ChatStart';
import { useStatusState } from '../src/context/StatusContext';
import { Text } from '../src/components/typography/Styled';
import { LoadingCard } from '../src/components/loading/LoadingCard';
import { ChatCard } from '../src/views/chat/Chat.styled';
@ -29,14 +31,44 @@ const ChatLayout = styled.div`
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 { minorVersion } = useStatusState();
const { minorVersion } = useNodeInfo();
const { chats, sender, sentChats, initialized } = useChatState();
const bySender = separateBySender([...chats, ...sentChats]);
const senders = getSenders(bySender);
const senders = getSenders(bySender) || [];
const [user, setUser] = React.useState('');
const [showContacts, setShowContacts] = React.useState(false);
const [state, dispatch] = React.useReducer(reducer, initialState);
const { user, showContacts } = state;
const setUser = (user: string) => dispatch({ type: 'setUserAndHide', user });
const setName = (user: string) => dispatch({ type: 'setUser', user });
if (!initialized) {
return <LoadingCard title={'Chats'} />;
@ -66,7 +98,7 @@ const ChatView = () => {
contacts={senders}
user={user}
setUser={setUser}
setShow={setShowContacts}
setName={setName}
/>
);
}
@ -76,11 +108,11 @@ const ChatView = () => {
contacts={senders}
user={user}
setUser={setUser}
setShow={setShowContacts}
setName={setName}
hide={true}
/>
{user === 'New Chat' ? (
<ChatStart noTitle={true} />
<ChatStart noTitle={true} callback={() => setUser('')} />
) : (
<ChatBox messages={bySender[sender]} alias={user} />
)}
@ -99,7 +131,7 @@ const ChatView = () => {
</ViewSwitch>
<ViewSwitch>
<SingleLine>
<ColorButton onClick={() => setShowContacts(prev => !prev)}>
<ColorButton onClick={() => dispatch({ type: 'toggleShow' })}>
<Users size={18} />
</ColorButton>
<SubTitle>{user}</SubTitle>
@ -109,7 +141,7 @@ const ChatView = () => {
)}
<ChatCard mobileCardPadding={'0'}>
{chats.length <= 0 && sentChats.length <= 0 ? (
<ChatStart />
<ChatStart callback={() => setUser('')} />
) : (
renderChats()
)}
@ -126,4 +158,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_MESSAGES]);
}

View file

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

View file

@ -1,9 +1,11 @@
import React, { useState } from 'react';
import { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { useGetForwardsQuery } from 'src/graphql/queries/__generated__/getForwards.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { ForwardType } from 'src/graphql/types';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_FORWARDS } from 'src/graphql/queries/getForwards';
import {
SubTitle,
Card,
@ -28,11 +30,8 @@ const ForwardsView = () => {
const [time, setTime] = useState('week');
const [indexOpen, setIndexOpen] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useGetForwardsQuery({
skip: !auth,
variables: { auth, time },
variables: { time },
onError: error => toast.error(getErrorContent(error)),
});
@ -63,11 +62,13 @@ const ForwardsView = () => {
{renderButton('threeMonths', '3M')}
</SingleLine>
</CardTitle>
{data.getForwards.forwards.length <= 0 && renderNoForwards()}
{data?.getForwards?.forwards &&
data.getForwards.forwards.length <= 0 &&
renderNoForwards()}
<Card mobileCardPadding={'0'} mobileNoBackground={true}>
{data.getForwards.forwards.map((forward, index: number) => (
{data?.getForwards?.forwards?.map((forward, index) => (
<ForwardCard
forward={forward}
forward={forward as ForwardType}
key={index}
index={index + 1}
setIndexOpen={setIndexOpen}
@ -86,4 +87,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_FORWARDS]);
}

View file

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

View file

@ -1,44 +1,21 @@
import * as React from 'react';
import { Spacer } from 'src/components/spacer/Spacer';
import { withApollo } from 'config/client';
import { ServerAccounts } from 'src/components/accounts/ServerAccounts';
import { useAccountState } from 'src/context/AccountContext';
import getConfig from 'next/config';
import { ThunderStorm } from 'src/views/homepage/HomePage.styled';
import { appendBasePath } from 'src/utils/basePath';
import { SessionLogin } from '../src/views/login/SessionLogin';
import { NextPageContext } from 'next';
import { GET_SERVER_ACCOUNTS } from 'src/graphql/queries/getServerAccounts';
import { getProps } from 'src/utils/ssr';
import { TopSection } from '../src/views/homepage/Top';
import { LoginBox } from '../src/views/homepage/LoginBox';
import { Accounts } from '../src/views/homepage/Accounts';
import { LoadingCard } from '../src/components/loading/LoadingCard';
import { Section } from '../src/components/section/Section';
const { publicRuntimeConfig } = getConfig();
const { noClient } = publicRuntimeConfig;
const ContextApp = () => {
const { finishedFetch } = useAccountState();
return (
<>
<ThunderStorm alt={''} src={appendBasePath('/static/thunderstorm.gif')} />
<TopSection />
{!finishedFetch && (
<Section color={'transparent'}>
<LoadingCard loadingHeight={'160px'} />
</Section>
)}
{finishedFetch && (
<>
<SessionLogin />
<Accounts />
{!noClient && <LoginBox />}
</>
)}
<Spacer />
</>
);
};
const ContextApp = () => (
<>
<ThunderStorm alt={''} src={'/static/thunderstorm.gif'} />
<TopSection />
<Accounts />
<Spacer />
</>
);
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 { useAccountState } from 'src/context/AccountContext';
import { useGetPeersQuery } from 'src/graphql/queries/__generated__/getPeers.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { PeerType } from 'src/graphql/types';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_PEERS } from 'src/graphql/queries/getPeers';
import {
CardWithTitle,
SubTitle,
@ -14,14 +16,10 @@ import { AddPeer } from '../src/views/peers/AddPeer';
const PeersView = () => {
const [indexOpen, setIndexOpen] = useState(0);
const { auth } = useAccountState();
const { loading, data } = useGetPeersQuery({
skip: !auth,
variables: { auth },
});
const { loading, data } = useGetPeersQuery();
if (loading || !data || !data.getPeers) {
if (loading || !data?.getPeers) {
return <LoadingCard title={'Peers'} />;
}
@ -33,11 +31,11 @@ const PeersView = () => {
<Card mobileCardPadding={'0'} mobileNoBackground={true}>
{data.getPeers.map((peer, index: number) => (
<PeersCard
peer={peer}
peer={peer as PeerType}
index={index + 1}
setIndexOpen={setIndexOpen}
indexOpen={indexOpen}
key={`${index}-${peer.public_key}`}
key={`${index}-${peer?.public_key}`}
/>
))}
</Card>
@ -52,4 +50,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_PEERS]);
}

View file

@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { SimpleBalance } from 'src/views/balance/SimpleBalance';
import {
CardWithTitle,
@ -11,10 +10,13 @@ import {
} from 'src/components/generic/Styled';
import { Text } from 'src/components/typography/Styled';
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 { minorVersion } = useStatusState();
const { minorVersion } = useNodeInfo();
const [advancedType, advancedTypeSet] = useState(false);
if (minorVersion < 9) {
@ -64,4 +66,8 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [GET_CHANNELS]);
}

View file

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

View file

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

View file

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

View file

@ -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 { toast } from 'react-toastify';
import { useAccountState } from 'src/context/AccountContext';
import { InvoiceCard } from 'src/views/transactions/InvoiceCard';
import {
useGetResumeQuery,
GetResumeQuery,
} from 'src/graphql/queries/__generated__/getResume.generated';
import { GridWrapper } from 'src/components/gridWrapper/GridWrapper';
import { withApollo } from 'config/client';
import { NextPageContext } from 'next';
import { getProps } from 'src/utils/ssr';
import { GET_RESUME } from 'src/graphql/queries/getResume';
import { GET_IN_OUT } from 'src/graphql/queries/getInOut';
import {
Card,
CardWithTitle,
@ -23,11 +26,8 @@ const TransactionsView = () => {
const [indexOpen, setIndexOpen] = useState(0);
const [token, setToken] = useState('');
const { auth } = useAccountState();
const { loading, data, fetchMore } = useGetResumeQuery({
skip: !auth,
variables: { auth, token: '' },
variables: { token: '' },
onError: error => toast.error(getErrorContent(error)),
});
@ -49,7 +49,10 @@ const TransactionsView = () => {
<CardWithTitle>
<SubTitle>Transactions</SubTitle>
<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') {
return (
<InvoiceCard
@ -61,32 +64,35 @@ const TransactionsView = () => {
/>
);
}
return (
<PaymentsCard
payment={entry}
key={index}
index={index + 1}
setIndexOpen={setIndexOpen}
indexOpen={indexOpen}
/>
);
if (entry.__typename === 'PaymentType') {
return (
<PaymentsCard
payment={entry}
key={index}
index={index + 1}
setIndexOpen={setIndexOpen}
indexOpen={indexOpen}
/>
);
}
return null;
})}
<ColorButton
fullWidth={true}
withMargin={'16px 0 0'}
onClick={() => {
fetchMore({
variables: { auth, token },
variables: { token },
updateQuery: (
prev,
{
fetchMoreResult: result,
}: { fetchMoreResult: GetResumeQuery }
) => {
if (!result) return prev;
const newToken = result.getResume.token || '';
const prevEntries = prev.getResume.resume;
const newEntries = result.getResume.resume;
{ fetchMoreResult }: { fetchMoreResult?: GetResumeQuery }
): GetResumeQuery => {
if (!fetchMoreResult?.getResume) return prev;
const newToken = fetchMoreResult.getResume.token || '';
const prevEntries = prev?.getResume
? prev.getResume.resume
: [];
const newEntries = fetchMoreResult.getResume.resume;
const allTransactions = newToken
? [...prevEntries, ...newEntries]
@ -117,4 +123,11 @@ const Wrapped = () => (
</GridWrapper>
);
export default withApollo(Wrapped);
export default Wrapped;
export async function getServerSideProps(context: NextPageContext) {
return await getProps(context, [
GET_RESUME,
{ document: GET_IN_OUT, variables: { time: 'month' } },
]);
}

View file

@ -71,7 +71,7 @@ describe('getParsedAccount', () => {
return 'something else ';
});
const account = getParsedAccount(raw, 0, masterPassword, 'regtest');
expect(account.macaroon).toContain('macaroon');
expect(account?.macaroon).toContain('macaroon');
});
it('picks up other networks', () => {
@ -94,7 +94,7 @@ describe('getParsedAccount', () => {
return 'something else ';
});
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.macaroon).toContain('macaroon');
expect(account?.macaroon).toContain('macaroon');
});
describe('macaroon handling', () => {
@ -109,7 +109,7 @@ describe('getParsedAccount', () => {
};
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', () => {
const raw = {
@ -130,7 +130,7 @@ describe('getParsedAccount', () => {
}
});
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.macaroon).toBe('yay');
expect(account?.macaroon).toBe('yay');
});
it('falls back to lnd dir finally', () => {
@ -152,7 +152,7 @@ describe('getParsedAccount', () => {
}
});
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');
expect(account.cert).toBe('RAW CERT');
expect(account?.cert).toBe('RAW CERT');
});
it('falls back to certificate path after that', () => {
@ -188,7 +188,7 @@ describe('getParsedAccount', () => {
}
});
const account = getParsedAccount(raw, 0, masterPassword, 'mainnet');
expect(account.cert).toBe('yay');
expect(account?.cert).toBe('yay');
});
it('falls back to lnd dir finally', () => {
@ -207,7 +207,7 @@ describe('getParsedAccount', () => {
}
});
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 { logger } from './logger';
export const to = promise => {
export const to = async <T>(promise: Promise<T>) => {
return promise
.then(data => data)
.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 = ({
type,
value,
}): { [key: string]: string } | {} => {
}: DecodeMessageType): { [key: string]: string } | {} => {
switch (type) {
case MESSAGE_TYPE:
return { message: bufferHexToUtf(value) };

View file

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

View file

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

View file

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

View file

@ -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 = {
getMessages: { max: 10, window: '5s' },
nodeInfo: { max: 10, window: '5s' },
};
const rateLimiter = getGraphQLRateLimiter({

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import { logger } from 'server/helpers/logger';
import { toWithError } from 'server/helpers/async';
import { getChannel } from 'ln-service';
import { GetChannelType } from 'server/types/ln-service.types';
import { openChannel } from './resolvers/mutation/openChannel';
import { closeChannel } from './resolvers/mutation/closeChannel';
import { updateFees } from './resolvers/mutation/updateFees';
@ -53,7 +54,7 @@ export const channelResolvers = {
let node_policies = null;
let partner_node_policies = null;
channel.policies.forEach(policy => {
(channel as GetChannelType).policies.forEach(policy => {
if (localKey && localKey === policy.public_key) {
node_policies = {
...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 { ContextType } from 'server/types/apiTypes';
import { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
} from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { CloseChannelType } from 'server/types/ln-service.types';
import { logger } from 'server/helpers/logger';
export const closeChannel = async (
_: undefined,
@ -15,23 +12,28 @@ export const closeChannel = async (
) => {
await requestLimiter(context.ip, 'closeChannel');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
try {
const info = await lnCloseChannel({
const closeParams = {
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,
id: params.id,
target_confirmations: params.targetConfirmations,
tokens_per_vbyte: params.tokensPerVByte,
is_force_close: params.forceClose,
});
return {
transactionId: info.transaction_id,
transactionOutputIndex: info.transaction_vout,
};
} catch (error) {
logger.error('Error closing channel: %o', error);
throw new Error(getErrorMsg(error));
}
...closeParams,
})
);
logger.info('Channel closed: %o', params.id);
return {
transactionId: info.transaction_id,
transactionOutputIndex: info.transaction_vout,
};
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,22 +10,25 @@ import {
import { ContextType } from 'server/types/apiTypes';
import { to, toWithError } from 'server/helpers/async';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getAuthLnd, getCorrectAuth } from 'server/helpers/helpers';
import {
createCustomRecords,
decodeMessage,
} from 'server/helpers/customRecords';
import { logger } from 'server/helpers/logger';
import {
GetInvoicesType,
GetWalletInfoType,
} from 'server/types/ln-service.types';
export const chatResolvers = {
Query: {
getMessages: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'getMessages');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const invoiceList = await to(
const invoiceList = await to<GetInvoicesType>(
getInvoices({
lnd,
limit: params.initialize ? 100 : 5,
@ -72,7 +75,11 @@ export const chatResolvers = {
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;
}
}
@ -88,7 +95,7 @@ export const chatResolvers = {
);
const filtered = await getFiltered();
const final = filtered.filter(message => !!message);
const final = filtered.filter(Boolean) || [];
return { token: invoiceList.next, messages: final };
},
@ -97,8 +104,7 @@ export const chatResolvers = {
sendMessage: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'sendMessage');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
if (params.maxFee) {
const tokens = Math.max(params.tokens || 100, 100);
@ -126,7 +132,7 @@ export const chatResolvers = {
messageToSend = `${params.tokens},${params.message}`;
}
const nodeInfo = await to(
const nodeInfo = await to<GetWalletInfoType>(
getWalletInfo({
lnd,
})
@ -167,7 +173,8 @@ export const chatResolvers = {
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`
type getMessagesType {
token: String
messages: [messagesType]
messages: [messagesType]!
}
type messagesType {
date: String
id: String
verified: Boolean
date: String!
id: String!
verified: Boolean!
contentType: String
sender: 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));
if (error) {
if (error || !response) {
logger.debug('Unable to get latest github version');
throw new Error('NoGithubVersion');
}

View file

@ -1,6 +1,15 @@
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 orderedOutgoing = groupBy(forwards, f => f.outgoing_channel);
@ -14,7 +23,7 @@ export const getChannelVolume = forwards => {
return reduceTokens(together);
};
const reduceTokens = array => {
const reduceTokens = (array: GroupedObject | TotalGroupedObject) => {
const reducedArray = [];
for (const key in array) {
if (Object.prototype.hasOwnProperty.call(array, key)) {

View file

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

View file

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

View file

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

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 { makeExecutableSchema } from 'apollo-server-micro';
import { makeExecutableSchema } from 'graphql-tools';
import { nodeTypes } from './node/types';
import { nodeResolvers } from './node/resolvers';
import { authResolvers } from './auth/resolvers';
import { generalTypes, queryTypes, mutationTypes } from './types';
import { accountResolvers } from './account/resolvers';
import { accountTypes } from './account/types';
import { hodlTypes } from './hodlhodl/types';
import { hodlResolvers } from './hodlhodl/resolvers';
import { lnpayResolvers } from './lnpay/resolvers';
import { lnpayTypes } from './lnpay/types';
import { bitcoinResolvers } from './bitcoin/resolvers';
@ -49,7 +47,6 @@ const typeDefs = [
mutationTypes,
nodeTypes,
accountTypes,
hodlTypes,
lnpayTypes,
bitcoinTypes,
peerTypes,
@ -72,7 +69,6 @@ const resolvers = merge(
nodeResolvers,
authResolvers,
accountResolvers,
hodlResolvers,
lnpayResolvers,
bitcoinResolvers,
peerResolvers,
@ -93,4 +89,4 @@ const resolvers = merge(
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 { logger } from 'server/helpers/logger';
import { requestLimiter } from 'server/helpers/rateLimiter';
import {
getAuthLnd,
getErrorMsg,
getCorrectAuth,
getLnd,
} from 'server/helpers/helpers';
import { getErrorMsg } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { DecodedType } from 'server/types/ln-service.types';
const KEYSEND_TYPE = '5482373484';
@ -24,9 +20,9 @@ export const invoiceResolvers = {
decodeRequest: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'decode');
const lnd = getLnd(params.auth, context);
const { lnd } = context;
const decoded = await to(
const decoded = await to<DecodedType>(
decodePaymentRequest({
lnd,
request: params.request,
@ -48,8 +44,7 @@ export const invoiceResolvers = {
createInvoice: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'createInvoice');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
return await to(
createInvoiceRequest({
@ -61,8 +56,8 @@ export const invoiceResolvers = {
keysend: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'keysend');
const { auth, destination, tokens } = params;
const lnd = getLnd(auth, context);
const { destination, tokens } = params;
const { lnd } = context;
const preimage = randomBytes(32);
const secret = preimage.toString('hex');
@ -90,8 +85,7 @@ export const invoiceResolvers = {
) => {
await requestLimiter(context.ip, 'circularRebalance');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
let route;
try {
@ -120,8 +114,8 @@ export const invoiceResolvers = {
payViaRoute: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'payViaRoute');
const { auth, route: routeJSON, id } = params;
const lnd = getLnd(auth, context);
const { route: routeJSON, id } = params;
const { lnd } = context;
let route;
try {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,39 +1,45 @@
import {
getNode as getLnNode,
getWalletInfo,
getClosedChannels,
} from 'ln-service';
import { getNode, getWalletInfo, getClosedChannels } from 'ln-service';
import { to, toWithError } from 'server/helpers/async';
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 { logger } from '../../helpers/logger';
const errorNode = { alias: 'Node not found' };
type NodeParent = {
lnd: LndObject;
publicKey: string;
withChannels?: boolean;
};
export const nodeResolvers = {
Query: {
getNode: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'closedChannels');
const { auth, withoutChannels = true, publicKey } = params;
const lnd = getLnd(auth, context);
const { withoutChannels = true, publicKey } = params;
const { lnd } = context;
return { lnd, publicKey, withChannels: !withoutChannels };
},
getNodeInfo: async (_: undefined, params: any, context: ContextType) => {
await requestLimiter(context.ip, 'nodeInfo');
const auth = getCorrectAuth(params.auth, context);
const lnd = getAuthLnd(auth);
const { lnd } = context;
const info = await to(
const info = await to<GetWalletInfoType>(
getWalletInfo({
lnd,
})
);
const closedChannels = await to(
const closedChannels: ClosedChannelsType = await to(
getClosedChannels({
lnd,
})
@ -46,7 +52,7 @@ export const nodeResolvers = {
},
},
Node: {
node: async parent => {
node: async (parent: NodeParent) => {
const { lnd, withChannels, publicKey } = parent;
if (!lnd) {
@ -60,19 +66,19 @@ export const nodeResolvers = {
}
const [info, error] = await toWithError(
getLnNode({
getNode({
lnd,
is_omitting_channels: !withChannels,
public_key: publicKey,
})
);
if (error) {
if (error || !info) {
logger.debug(`Error getting node with key: ${publicKey}`);
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 {
node: nodeType
node: nodeType!
}
type nodeInfoType {
chains: [String]
color: String
active_channels_count: Int
closed_channels_count: Int
alias: String
current_block_hash: String
current_block_height: Int
is_synced_to_chain: Boolean
is_synced_to_graph: Boolean
latest_block_at: String
peers_count: Int
pending_channels_count: Int
public_key: String
uris: [String]
version: String
chains: [String!]!
color: String!
active_channels_count: Int!
closed_channels_count: Int!
alias: String!
current_block_hash: String!
current_block_height: Int!
is_synced_to_chain: Boolean!
is_synced_to_graph: Boolean!
latest_block_at: String!
peers_count: Int!
pending_channels_count: Int!
public_key: String!
uris: [String!]!
version: String!
}
`;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,8 @@
import { getChannels } from 'ln-service';
import { ContextType } from 'server/types/apiTypes';
import { requestLimiter } from 'server/helpers/rateLimiter';
import { getLnd } from 'server/helpers/helpers';
import { to } from 'server/helpers/async';
import { GetChannelsType } from 'server/types/ln-service.types';
export const getChannelReport = async (
_: undefined,
@ -11,9 +11,9 @@ export const getChannelReport = async (
) => {
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) {
return;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,20 +1,7 @@
export interface ForwardProps {
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;
}
import { ForwardType } from 'server/types/ln-service.types';
export interface ListProps {
[key: string]: ForwardProps[];
[key: string]: ForwardType[];
}
export interface ReduceObjectProps {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,15 +1,16 @@
import { ServerResponse } from 'http';
import { LndObject } from './ln-service.types';
type SSOType = {
macaroon: string | null;
export type SSOType = {
macaroon: string;
cert: string | null;
host: string | null;
socket: string;
};
type AccountType = {
export type AccountType = {
name: string;
id: string;
host: string;
socket: string;
macaroon: string;
cert: string | null;
password: string;
@ -17,10 +18,10 @@ type AccountType = {
export type ContextType = {
ip: string;
lnd: LndObject | null;
secret: string;
ssoVerified: boolean;
account: string | null;
sso: SSOType;
id: string | null;
sso: SSOType | null;
accounts: AccountType[];
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/',
fees: 'https://mempool.space/api/v1/fees/recommended',
ticker: 'https://blockchain.info/ticker',
hodlhodl: 'https://hodlhodl.com/api',
github: 'https://api.github.com/repos/apotdevin/thunderhub/releases/latest',
update: 'https://github.com/apotdevin/thunderhub#updating',
};

View file

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

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