From 056f784689464cc6ed798f6dab96388da83062ee Mon Sep 17 00:00:00 2001 From: jamaljsr Date: Mon, 13 Apr 2020 17:24:12 -0400 Subject: [PATCH 1/2] feat: add multi-language support --- app/package.json | 4 ++ app/src/App.tsx | 12 +++--- app/src/hooks/usePrefixedTranslation.ts | 24 ++++++++++++ app/src/i18n/index.ts | 50 +++++++++++++++++++++++++ app/src/i18n/locales/en-US.json | 7 ++++ app/src/index.tsx | 1 + app/src/setupTests.ts | 2 + app/yarn.lock | 36 +++++++++++++++++- 8 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 app/src/hooks/usePrefixedTranslation.ts create mode 100644 app/src/i18n/index.ts create mode 100644 app/src/i18n/locales/en-US.json diff --git a/app/package.json b/app/package.json index cbabe5091..98e0d4e49 100644 --- a/app/package.json +++ b/app/package.json @@ -15,10 +15,13 @@ "proxy": "https://localhost:8443", "dependencies": { "@improbable-eng/grpc-web": "0.12.0", + "i18next": "19.4.1", + "i18next-browser-languagedetector": "4.0.2", "mobx": "5.15.4", "mobx-react": "6.2.2", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-i18next": "11.3.4", "react-scripts": "3.4.1" }, "devDependencies": { @@ -48,6 +51,7 @@ "src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts", "!src/types/**/*.{js,ts}", + "!src/i18n/**/*.{js,ts}", "!src/index.tsx" ] }, diff --git a/app/src/App.tsx b/app/src/App.tsx index 41082072b..f787514a7 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -1,10 +1,12 @@ import React, { useEffect } from 'react'; import { observer } from 'mobx-react'; import './App.css'; +import usePrefixedTranslation from 'hooks/usePrefixedTranslation'; import { channel, node, swap } from 'action'; import store from 'store'; const App = () => { + const { l } = usePrefixedTranslation('App'); useEffect(() => { // fetch node info when the component is mounted const fetchInfo = async () => await node.getInfo(); @@ -13,25 +15,25 @@ const App = () => { return (
-

Node Info

+

{l('App.nodeInfo')}

{store.info && ( <> - + - + - + - + diff --git a/app/src/hooks/usePrefixedTranslation.ts b/app/src/hooks/usePrefixedTranslation.ts new file mode 100644 index 000000000..45246514e --- /dev/null +++ b/app/src/hooks/usePrefixedTranslation.ts @@ -0,0 +1,24 @@ +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; + +/** + * A hook which returns a `t` function that inserts a prefix in each key lookup + * @param prefix the prefix to use for all translation keys + */ +const usePrefixedTranslation = (prefix: string) => { + const { t } = useTranslation(); + // the new `t` function that will append the prefix + const translate = useCallback( + (key: string, options?: string | object) => { + // if the key contains a '.', then don't add the prefix + return key.includes('.') ? t(key, options) : t(`${prefix}.${key}`, options); + }, + [prefix, t], + ); + + return { + l: translate, + }; +}; + +export default usePrefixedTranslation; diff --git a/app/src/i18n/index.ts b/app/src/i18n/index.ts new file mode 100644 index 000000000..0c4cb2b8e --- /dev/null +++ b/app/src/i18n/index.ts @@ -0,0 +1,50 @@ +import { initReactI18next } from 'react-i18next'; +import i18n, { InitOptions } from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import enUS from './locales/en-US.json'; + +const defaultLanguage = 'en-US'; + +export const languages: { [index: string]: string } = { + 'en-US': 'English', +}; + +/** + * create a mapping of locales -> translations + */ +const resources = Object.keys(languages).reduce((acc: { [key: string]: any }, lang) => { + switch (lang) { + case 'en-US': + acc[lang] = { translation: enUS }; + break; + } + return acc; +}, {}); + +/** + * create an array of allowed languages + */ +const whitelist = Object.keys(languages).reduce((acc: string[], lang) => { + acc.push(lang); + + if (lang.includes('-')) { + acc.push(lang.substring(0, lang.indexOf('-'))); + } + + return acc; +}, []); + +const config: InitOptions = { + lng: defaultLanguage, + resources, + whitelist, + fallbackLng: defaultLanguage, + keySeparator: false, + interpolation: { + escapeValue: false, + }, +}; + +i18n.use(LanguageDetector).use(initReactI18next).init(config); + +export default i18n; diff --git a/app/src/i18n/locales/en-US.json b/app/src/i18n/locales/en-US.json new file mode 100644 index 000000000..45085c924 --- /dev/null +++ b/app/src/i18n/locales/en-US.json @@ -0,0 +1,7 @@ +{ + "App.nodeInfo": "Node Info", + "App.pubkey": "Pubkey", + "App.alias": "Alias", + "App.version": "Version", + "App.numChannels": "# Channels" +} diff --git a/app/src/index.tsx b/app/src/index.tsx index 1f1b280de..066754f1e 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import 'mobx-react/batchingForReactDom'; +import './i18n'; import './index.css'; import App from './App'; diff --git a/app/src/setupTests.ts b/app/src/setupTests.ts index bc6f73e8f..53363b55d 100644 --- a/app/src/setupTests.ts +++ b/app/src/setupTests.ts @@ -5,3 +5,5 @@ import 'mobx-react-lite/batchingForReactDom'; // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import '@testing-library/jest-dom/extend-expect'; +// enable i18n translations in unit tests +import './i18n'; diff --git a/app/yarn.lock b/app/yarn.lock index ed5be49ca..11900a257 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -899,7 +899,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.9.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== @@ -5003,6 +5003,13 @@ html-minifier-terser@^5.0.1: relateurl "^0.2.7" terser "^4.6.3" +html-parse-stringify2@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a" + integrity sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o= + dependencies: + void-elements "^2.0.1" + html-webpack-plugin@4.0.0-beta.11: version "4.0.0-beta.11" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.11.tgz#3059a69144b5aecef97708196ca32f9e68677715" @@ -5102,6 +5109,20 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= +i18next-browser-languagedetector@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.0.2.tgz#eb02535cc5e57dd534fc60abeede05a3823a8551" + integrity sha512-AK4IZ3XST4HIKShgpB2gOFeDPrMOnZx56GLA6dGo/8rvkiczIlq05lV8w77c3ShEZxtTZeUVRI4Q/cBFFVXS/w== + dependencies: + "@babel/runtime" "^7.5.5" + +i18next@19.4.1: + version "19.4.1" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.4.1.tgz#4929d15d3d01e4712350a368d005cefa50ff5455" + integrity sha512-dC3ue15jkLebN2je4xEjfjVYd/fSAo+UVK9f+JxvceCJRowkI+S0lGohgKejqU+FYLfvw9IAPylIIEWwR8Djrg== + dependencies: + "@babel/runtime" "^7.3.1" + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -8572,6 +8593,14 @@ react-error-overlay@^6.0.7: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== +react-i18next@11.3.4: + version "11.3.4" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.3.4.tgz#355df5fe5133e5e30302d166f529678100ffc968" + integrity sha512-IRZMD7PAM3C+fJNzRbyLNi1ZD0kc3Z3obBspJjEl+9H+ME41PhVor3BpdIqv/Rm7lUoGhMjmpu42J45ooJ61KA== + dependencies: + "@babel/runtime" "^7.3.1" + html-parse-stringify2 "2.0.1" + react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -10353,6 +10382,11 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== +void-elements@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" + integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= + w3c-hr-time@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" From ee760b4b1ecbf470d1017c59808d96060ebb8445 Mon Sep 17 00:00:00 2001 From: jamaljsr Date: Tue, 14 Apr 2020 13:25:57 -0400 Subject: [PATCH 2/2] feat: add configurable logging infrastructure --- README.md | 18 +++++++ app/package.json | 2 + app/src/action/channel.ts | 5 +- app/src/action/node.ts | 5 +- app/src/action/swap.ts | 5 +- app/src/api/grpc.ts | 9 ++++ app/src/api/lnd.ts | 2 +- app/src/config.ts | 11 ++++- app/src/i18n/index.ts | 3 ++ app/src/index.tsx | 3 ++ app/src/util/log.ts | 99 +++++++++++++++++++++++++++++++++++++++ app/yarn.lock | 19 +++++--- 12 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 app/src/util/log.ts diff --git a/README.md b/README.md index 4ddf53941..b8c93eb7a 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,21 @@ Open browser at http://localhost:3000 ```sh yarn test ``` + +## Logging + +Client-side logs are disabled by default in production builds and enabled by default in a development environment. In production, logging can be turned on by adding a couple keys to your browser's `localStorage`. Simply run these two JS statements in you browser's DevTools console: + +``` +localStorage.setItem('debug', '*'); localStorage.setItem('debug-level', 'debug'); +``` + +The value for `debug` is a namespace filter which determines which portions of the app to display logs for. The namespaces currently used by the app are as follows: + +- `main`: logs general application messages +- `action`: logs all actions that modify the internal application state +- `grpc`: logs all GRPC API requests and responses + +Example filters: `main,action` will only log main and action messages. `*,-action` will log everything except action messages. + +The value for `debug-level` determines the verbosity of the logs. The value can be one of `debug`, `info`, `warn`, or `error`. diff --git a/app/package.json b/app/package.json index 98e0d4e49..d19cd430b 100644 --- a/app/package.json +++ b/app/package.json @@ -15,6 +15,7 @@ "proxy": "https://localhost:8443", "dependencies": { "@improbable-eng/grpc-web": "0.12.0", + "debug": "4.1.1", "i18next": "19.4.1", "i18next-browser-languagedetector": "4.0.2", "mobx": "5.15.4", @@ -28,6 +29,7 @@ "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", + "@types/debug": "4.1.5", "@types/google-protobuf": "3.7.2", "@types/jest": "^24.0.0", "@types/node": "^12.0.0", diff --git a/app/src/action/channel.ts b/app/src/action/channel.ts index d3c683abe..cc7544a88 100644 --- a/app/src/action/channel.ts +++ b/app/src/action/channel.ts @@ -1,4 +1,5 @@ -import { action } from 'mobx'; +import { action, toJS } from 'mobx'; +import { actionLog as log } from 'util/log'; import LndApi from 'api/lnd'; import { Store } from 'store'; @@ -19,6 +20,7 @@ class ChannelAction { * fetch channels from the LND RPC */ @action.bound async getChannels() { + log.info('fetching channels'); const channels = await this._lnd.listChannels(); this._store.channels = channels.channelsList.map(c => ({ chanId: c.chanId, @@ -29,6 +31,7 @@ class ChannelAction { uptime: c.uptime, active: c.active, })); + log.info('updated store.channels', toJS(this._store.channels)); } } diff --git a/app/src/action/node.ts b/app/src/action/node.ts index 32f7f7069..bd8c4a2f1 100644 --- a/app/src/action/node.ts +++ b/app/src/action/node.ts @@ -1,4 +1,5 @@ -import { action } from 'mobx'; +import { action, toJS } from 'mobx'; +import { actionLog as log } from 'util/log'; import LndApi from 'api/lnd'; import { Store } from 'store'; @@ -19,7 +20,9 @@ class NodeAction { * fetch node info from the LND RPC */ @action.bound async getInfo() { + log.info('fetching node information'); this._store.info = await this._lnd.getInfo(); + log.info('updated store.info', toJS(this._store.info)); } } diff --git a/app/src/action/swap.ts b/app/src/action/swap.ts index cb5470903..24ba6f71d 100644 --- a/app/src/action/swap.ts +++ b/app/src/action/swap.ts @@ -1,5 +1,6 @@ -import { action } from 'mobx'; +import { action, toJS } from 'mobx'; import { SwapState, SwapType } from 'types/generated/loop_pb'; +import { actionLog as log } from 'util/log'; import LoopApi from 'api/loop'; import { Store } from 'store'; @@ -20,6 +21,7 @@ class SwapAction { * fetch swaps from the Loop RPC */ @action.bound async listSwaps() { + log.info('fetching Loop history'); const loopSwaps = await this._loop.listSwaps(); this._store.swaps = loopSwaps.swapsList // sort the list with newest first as the API returns them out of order @@ -31,6 +33,7 @@ class SwapAction { createdOn: new Date(s.initiationTime / 1000 / 1000), status: this._stateToString(s.state), })); + log.info('updated store.swaps', toJS(this._store.swaps)); } /** diff --git a/app/src/api/grpc.ts b/app/src/api/grpc.ts index 7d6f271f0..0ee8aab51 100644 --- a/app/src/api/grpc.ts +++ b/app/src/api/grpc.ts @@ -3,6 +3,7 @@ import { ProtobufMessage } from '@improbable-eng/grpc-web/dist/typings/message'; import { Metadata } from '@improbable-eng/grpc-web/dist/typings/metadata'; import { UnaryMethodDefinition } from '@improbable-eng/grpc-web/dist/typings/service'; import { DEV_HOST } from 'config'; +import { grpcLog as log } from 'util/log'; /** * Executes a single GRPC request and returns a promise which will resolve with the response @@ -16,16 +17,24 @@ export const grpcRequest = => { return new Promise((resolve, reject) => { + log.debug( + `Request: ${methodDescriptor.service.serviceName}.${methodDescriptor.methodName}`, + ); + log.debug(` - req: `, request.toObject()); grpc.unary(methodDescriptor, { host: DEV_HOST, request, metadata, onEnd: ({ status, statusMessage, headers, message, trailers }) => { + log.debug(' - status', status, statusMessage); + log.debug(' - headers', headers); if (status === grpc.Code.OK && message) { + log.debug(' - message', message.toObject()); resolve(message as TRes); } else { reject(new Error(`${status}: ${statusMessage}`)); } + log.debug(' - trailers', trailers); }, }); }); diff --git a/app/src/api/lnd.ts b/app/src/api/lnd.ts index 1b78d3690..781dfc52f 100644 --- a/app/src/api/lnd.ts +++ b/app/src/api/lnd.ts @@ -16,7 +16,7 @@ class LndApi { * call the LND `GetInfo` RPC and return the response */ async getInfo(): Promise { - const req = new LND.GetInfoResponse(); + const req = new LND.GetInfoRequest(); const res = await grpcRequest(Lightning.GetInfo, req, this._meta); return res.toObject(); } diff --git a/app/src/config.ts b/app/src/config.ts index 3851e46cd..d4265f493 100644 --- a/app/src/config.ts +++ b/app/src/config.ts @@ -1,3 +1,9 @@ +// flag to check if the app is running in a local development environment +export const IS_DEV = process.env.NODE_ENV === 'development'; + +// flag to check if the app is running in a a production environment +export const IS_PROD = process.env.NODE_ENV === 'production'; + // // temporary placeholder values. these will be supplied via the UI in the future // @@ -5,5 +11,8 @@ // macaroon to use for LND auth export const DEV_MACAROON = process.env.REACT_APP_DEV_MACAROON || ''; +// detect the host currently serving the app files +const { protocol, hostname, port = '' } = window.location; +const host = `${protocol}//${hostname}:${port}`; // the GRPC server to make requests to -export const DEV_HOST = process.env.REACT_APP_DEV_HOST || 'http://localhost:3000'; +export const DEV_HOST = process.env.REACT_APP_DEV_HOST || host; diff --git a/app/src/i18n/index.ts b/app/src/i18n/index.ts index 0c4cb2b8e..1953f8f5f 100644 --- a/app/src/i18n/index.ts +++ b/app/src/i18n/index.ts @@ -43,6 +43,9 @@ const config: InitOptions = { interpolation: { escapeValue: false, }, + detection: { + lookupLocalStorage: 'lang', + }, }; i18n.use(LanguageDetector).use(initReactI18next).init(config); diff --git a/app/src/index.tsx b/app/src/index.tsx index 066754f1e..bcab28470 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -3,8 +3,11 @@ import ReactDOM from 'react-dom'; import 'mobx-react/batchingForReactDom'; import './i18n'; import './index.css'; +import { log } from 'util/log'; import App from './App'; +log.info('Rendering the App'); + ReactDOM.render( diff --git a/app/src/util/log.ts b/app/src/util/log.ts new file mode 100644 index 000000000..9b1a3f1e5 --- /dev/null +++ b/app/src/util/log.ts @@ -0,0 +1,99 @@ +import { IS_DEV } from 'config'; +import debug, { Debugger } from 'debug'; + +enum LogLevel { + debug = 1, + info = 2, + warn = 3, + error = 4, + none = 5, +} + +/** + * A logger class with support for multiple namespaces and log levels. + */ +class Logger { + private _levelToOutput: LogLevel; + private _logger: Debugger; + + constructor(levelToOutput: LogLevel, namespace: string) { + this._levelToOutput = levelToOutput; + this._logger = debug(namespace); + } + + /** + * creates a new Logger instance by inspecting the executing environment + */ + static fromEnv(namespace: string): Logger { + // by default, log everything in development and nothing in production + let level = IS_DEV ? LogLevel.debug : LogLevel.none; + + if (localStorage.getItem('debug')) { + // if a 'debug' key is found in localStorage, use the level in storage or 'debug' by default + const storageLevel = localStorage.getItem('debug-level') || 'debug'; + level = LogLevel[storageLevel as keyof typeof LogLevel]; + } else if (IS_DEV) { + // if running in development with no localStorage key, use debug + level = LogLevel.debug; + // set the keys so they can be easily changed in the browser DevTools + localStorage.setItem('debug', '*'); + localStorage.setItem('debug-level', 'debug'); + } + + return new Logger(level, namespace); + } + + /** + * log a debug message + */ + debug = (message: string, ...args: any[]) => this._log(LogLevel.debug, message, args); + + /** + * log an info message + */ + info = (message: string, ...args: any[]) => this._log(LogLevel.info, message, args); + + /** + * log a warn message + */ + warn = (message: string, ...args: any[]) => this._log(LogLevel.warn, message, args); + + /** + * log an error message + */ + error = (message: string, ...args: any[]) => this._log(LogLevel.error, message, args); + + /** + * A shared logging function which will only output logs based on the level of this Logger instance + * @param level the level of the message being logged + * @param message the message to log + * @param args optional additional arguments to log + */ + private _log(level: LogLevel, message: string, args: any[]) { + // don't log if the level to output is greater than the level of this message + if (this._levelToOutput > level) return; + + // convert the provided log level number to the string name + const prefix = Object.keys(LogLevel).reduce( + (prev, curr) => (level === LogLevel[curr as keyof typeof LogLevel] ? curr : prev), + '??', + ); + + this._logger(`[${prefix}] ${message}`, ...args); + } +} + +/** + * the main logger for the app + */ +export const log = Logger.fromEnv('main'); + +/** + * the logger for GRPC requests and responses + */ +export const grpcLog = Logger.fromEnv('grpc'); + +/** + * the logger for state updates via mobx actions + */ +export const actionLog = Logger.fromEnv('action'); diff --git a/app/yarn.lock b/app/yarn.lock index 11900a257..46082513a 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -1355,6 +1355,11 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/debug@4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" + integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -3463,6 +3468,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" +debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debug@^3.0.0, debug@^3.1.1, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -3470,13 +3482,6 @@ debug@^3.0.0, debug@^3.1.1, debug@^3.2.5: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
Pubkey{l('pubkey')} {store.info.identityPubkey}
Alias{l('alias')} {store.info.alias}
Version{l('version')} {store.info.version}
# Channels{l('numChannels')} {store.info.numActiveChannels}