mirror of
https://github.com/zhigang1992/connect.git
synced 2026-03-26 10:14:16 +08:00
feat: add ability to view secret key
This commit is contained in:
committed by
Hank Stoever
parent
9cc52c2bf9
commit
440c3e5420
35
.github/workflows/bundle-sizes.yml
vendored
Normal file
35
.github/workflows/bundle-sizes.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Compressed Size
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set Node Version
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.16.1
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v1
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Monorepo Dependencies
|
||||
run: yarn
|
||||
- name: Lerna Bootstrap
|
||||
run: yarn lerna bootstrap
|
||||
- uses: preactjs/compressed-size-action@v1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
build-script: "lerna run build --stream --scope @blockstack/connect"
|
||||
pattern: '**/connect/dist/**/*.js'
|
||||
|
||||
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
- name: Typecheck
|
||||
run: yarn lerna run typecheck --parallel
|
||||
run: yarn typecheck
|
||||
|
||||
publish:
|
||||
needs: [test_keychain, codecheck]
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"typescript": "^3.8.2"
|
||||
},
|
||||
"scripts": {
|
||||
"typecheck": "lerna run typecheck",
|
||||
"typecheck": "lerna run typecheck --parallel",
|
||||
"dev": "yarn lerna exec --parallel 'yarn dev' --scope test-app --scope @blockstack/app",
|
||||
"bootstrap": "yarn lerna exec --parallel 'yarn'",
|
||||
"build:libs": "yarn build:ui && yarn build:keychain && yarn build:connect",
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
working_directory: ~/blockstack-app
|
||||
docker:
|
||||
- image: circleci/node:10.16.3-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
# https://circleci.com/docs/2.0/caching/
|
||||
key: yarn-packages-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
key: yarn-packages-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- ./.cache/yarn
|
||||
# TODO - get this cached so that you don't have to pull down the binaries each time
|
||||
- run:
|
||||
name: Update apt-get
|
||||
working_directory: /
|
||||
command: |
|
||||
sudo apt-get update -y
|
||||
- run:
|
||||
name: Install Chrome headless dependencies
|
||||
working_directory: /
|
||||
command: |
|
||||
sudo apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 \
|
||||
libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 \
|
||||
libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 \
|
||||
libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
|
||||
ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||
- run:
|
||||
name: Lint Prettier
|
||||
command: yarn lint:prettier
|
||||
- run:
|
||||
name: Lint ESLint
|
||||
command: yarn lint:eslint
|
||||
- run:
|
||||
name: typecheck
|
||||
command: yarn typecheck
|
||||
- run:
|
||||
name: test server
|
||||
command: yarn dev
|
||||
background: true
|
||||
- run:
|
||||
name: Create .env file
|
||||
working_directory: ./tests/test-app/
|
||||
command: echo "SKIP_PREFLIGHT_CHECK=true" >> .env
|
||||
- run:
|
||||
name: Install test deps
|
||||
working_directory: ./tests/test-app/
|
||||
command: yarn
|
||||
- run:
|
||||
name: Run test app
|
||||
working_directory: ./tests/test-app/
|
||||
command: yarn start
|
||||
background: true
|
||||
- run:
|
||||
name: test
|
||||
command: yarn test
|
||||
- run:
|
||||
name: Build extension
|
||||
command: yarn prod:ext && zip -r extension.zip dist
|
||||
# https://circleci.com/docs/2.0/artifacts/
|
||||
- store_artifacts:
|
||||
path: extension.zip
|
||||
@@ -26,7 +26,7 @@
|
||||
"@blockstack/connect": "^2.1.0",
|
||||
"@blockstack/keychain": "^0.3.1",
|
||||
"@blockstack/prettier-config": "^0.0.5",
|
||||
"@blockstack/stats": "^0.5.4",
|
||||
"@blockstack/stats": "^0.7.0",
|
||||
"@blockstack/ui": "^1.0.1",
|
||||
"@rehooks/document-title": "^1.0.1",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
|
||||
@@ -7,14 +7,30 @@ import {
|
||||
selectIsSignedIn,
|
||||
} from '@store/wallet/selectors';
|
||||
import { selectSecretKey } from '@store/onboarding/selectors';
|
||||
import { decrypt } from '@blockstack/keychain';
|
||||
import { DEFAULT_PASSWORD } from '@store/onboarding/types';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export const useWallet = () => {
|
||||
const identities = useSelector(selectIdentities);
|
||||
const firstIdentity = useSelector(selectFirstIdentity);
|
||||
const wallet = useSelector(selectCurrentWallet);
|
||||
const secretKey = useSelector(selectSecretKey);
|
||||
const onboardingSecretKey = useSelector(selectSecretKey);
|
||||
const isRestoringWallet = useSelector(selectIsRestoringWallet);
|
||||
const isSignedIn = useSelector(selectIsSignedIn);
|
||||
const [secretKey, setSecretKey] = useState(onboardingSecretKey);
|
||||
|
||||
const fetchSecretKey = async () => {
|
||||
if (!secretKey && wallet) {
|
||||
const decryptedKey = await decrypt(wallet?.encryptedBackupPhrase, DEFAULT_PASSWORD);
|
||||
setSecretKey(decryptedKey);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
fetchSecretKey();
|
||||
}, [onboardingSecretKey]);
|
||||
|
||||
return { identities, firstIdentity, wallet, secretKey, isRestoringWallet, isSignedIn };
|
||||
};
|
||||
|
||||
@@ -33,6 +33,8 @@ export const pageTrackingNameMap = {
|
||||
[ScreenPaths.RECOVERY_CODE]: 'Magic Recovery Code',
|
||||
[ScreenPaths.ADD_ACCOUNT]: ' Select Username',
|
||||
[ScreenPaths.REGISTRY_ERROR]: 'Username Registry Error',
|
||||
[ScreenPaths.SETTINGS_KEY]: 'Settings: Secret Key',
|
||||
[ScreenPaths.HOME]: 'App Home',
|
||||
};
|
||||
|
||||
export const titleNameMap = {
|
||||
@@ -45,28 +47,37 @@ export const titleNameMap = {
|
||||
[ScreenPaths.RECOVERY_CODE]: 'Enter your password',
|
||||
[ScreenPaths.ADD_ACCOUNT]: ' Select Username',
|
||||
[ScreenPaths.REGISTRY_ERROR]: 'Failed to register username',
|
||||
[ScreenPaths.SETTINGS_KEY]: 'View your Secret Key',
|
||||
[ScreenPaths.HOME]: 'Secret Key',
|
||||
};
|
||||
|
||||
export const doTrackScreenChange = (screen: ScreenPaths, decodedAuthRequest: DecodedAuthRequest | undefined) => {
|
||||
document.title = titleNameMap[screen];
|
||||
const appURL = decodedAuthRequest ? new URL(decodedAuthRequest?.redirect_uri) : null;
|
||||
page({
|
||||
name: pageTrackingNameMap[screen],
|
||||
appName: decodedAuthRequest?.appDetails?.name,
|
||||
appDomain: appURL?.host,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
setTimeout(async () => {
|
||||
await page({
|
||||
name: pageTrackingNameMap[screen],
|
||||
appName: decodedAuthRequest?.appDetails?.name,
|
||||
appDomain: appURL?.host,
|
||||
});
|
||||
}, 1);
|
||||
};
|
||||
|
||||
export const doTrack = (type: string, payload?: object) => {
|
||||
console.log('Tracking:', { type, payload });
|
||||
event({
|
||||
name: type,
|
||||
...payload,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
setTimeout(async () => {
|
||||
await event({
|
||||
name: type,
|
||||
...payload,
|
||||
});
|
||||
}, 1);
|
||||
};
|
||||
|
||||
export const setStatsConfig = () => {
|
||||
setConfig({
|
||||
useHash: true,
|
||||
host: STATS_URL,
|
||||
providers: [
|
||||
{
|
||||
|
||||
@@ -17,7 +17,6 @@ import { authenticationInit } from '@common/utils';
|
||||
import { useAnalytics } from '@common/hooks/use-analytics';
|
||||
import { useWallet } from '@common/hooks/use-wallet';
|
||||
import { useOnboardingState } from '@common/hooks/use-onboarding-state';
|
||||
|
||||
import { Routes as RoutesDom, Route, Navigate } from 'react-router-dom';
|
||||
|
||||
export const Routes: React.FC = () => {
|
||||
@@ -102,8 +101,9 @@ export const Routes: React.FC = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
;{/*Error/Misc*/}
|
||||
{/*Error/Misc*/}
|
||||
<Route path="/username-error" element={<UsernameRegistryError />} />
|
||||
<Route path="/settings/secret-key" element={<SecretKey next={() => doChangeScreen(ScreenPaths.HOME)} />} />
|
||||
</RoutesDom>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,33 +1,43 @@
|
||||
import React from 'react';
|
||||
import { Screen, ScreenBody, Title, PoweredBy, ScreenFooter } from '@blockstack/connect';
|
||||
import { Box } from '@blockstack/ui';
|
||||
import { PoweredBy, Screen, ScreenBody, ScreenFooter, Title } from '@blockstack/connect';
|
||||
import { Box, PseudoBox, Text } from '@blockstack/ui';
|
||||
import { ScreenHeader } from '@components/connected-screen-header';
|
||||
import { Accounts } from '@components/accounts';
|
||||
import { AppIcon } from '@components/app-icon';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from '@store';
|
||||
import { selectAppName } from '@store/onboarding/selectors';
|
||||
import { AppState, store } from '@store';
|
||||
import { selectAppName, selectDecodedAuthRequest } from '@store/onboarding/selectors';
|
||||
import { Drawer } from '@components/drawer';
|
||||
import { selectDecodedAuthRequest } from '@store/onboarding/selectors';
|
||||
import { store } from '@store';
|
||||
import { selectIdentities, selectCurrentWallet } from '@store/wallet/selectors';
|
||||
import { selectCurrentWallet, selectIdentities } from '@store/wallet/selectors';
|
||||
import { ConfigApp } from '@blockstack/keychain/wallet';
|
||||
import { Wallet } from '@blockstack/keychain';
|
||||
import { gaiaUrl } from '@common/constants';
|
||||
import {
|
||||
CHOOSE_ACCOUNT_CHOSEN,
|
||||
CHOOSE_ACCOUNT_REUSE_WARNING,
|
||||
CHOOSE_ACCOUNT_REUSE_WARNING_BACK,
|
||||
CHOOSE_ACCOUNT_REUSE_WARNING_CONTINUE,
|
||||
CHOOSE_ACCOUNT_REUSE_WARNING_DISABLED,
|
||||
CHOOSE_ACCOUNT_REUSE_WARNING_BACK,
|
||||
CHOOSE_ACCOUNT_CHOSEN,
|
||||
} from '@common/track';
|
||||
import { useAnalytics } from '@common/hooks/use-analytics';
|
||||
import { ScreenPaths } from '@store/onboarding/types';
|
||||
|
||||
interface ChooseAccountProps {
|
||||
next: (identityIndex: number) => void;
|
||||
back?: () => void;
|
||||
}
|
||||
|
||||
const SettingsButton = () => {
|
||||
const { doChangeScreen } = useAnalytics();
|
||||
return (
|
||||
<PseudoBox _hover={{ cursor: 'pointer' }} onClick={() => doChangeScreen(ScreenPaths.HOME)}>
|
||||
<Text color="blue" fontWeight={500} textStyle="body.small.medium" fontSize="12px">
|
||||
Settings
|
||||
</Text>
|
||||
</PseudoBox>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChooseAccount: React.FC<ChooseAccountProps> = ({ next }) => {
|
||||
const { appName, identities, wallet } = useSelector((state: AppState) => ({
|
||||
appName: selectAppName(state),
|
||||
@@ -100,7 +110,7 @@ export const ChooseAccount: React.FC<ChooseAccountProps> = ({ next }) => {
|
||||
}}
|
||||
/>
|
||||
<Screen textAlign="center">
|
||||
<ScreenHeader hideIcon title="Continue with Secret Key" />
|
||||
<ScreenHeader hideIcon title="Continue with Secret Key" rightContent={<SettingsButton />} />
|
||||
<AppIcon mt={10} mb={4} size="72px" />
|
||||
<ScreenBody
|
||||
body={[
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex, Text } from '@blockstack/ui';
|
||||
import { Box, PseudoBox, Flex, Text } from '@blockstack/ui';
|
||||
import { LogoWithName } from '@components/logo-with-name';
|
||||
import { SignOut } from '@components/sign-out';
|
||||
|
||||
import { useAnalytics } from '@common/hooks/use-analytics';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { ScreenPaths } from '@store/onboarding/types';
|
||||
import { useWallet } from '@common/hooks/use-wallet';
|
||||
import { doSignOut } from '@store/wallet';
|
||||
|
||||
@@ -29,6 +30,17 @@ const SignedOut = () => (
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const SecretKeyButton = () => {
|
||||
const { doChangeScreen } = useAnalytics();
|
||||
return (
|
||||
<PseudoBox _hover={{ cursor: 'pointer' }} onClick={() => doChangeScreen(ScreenPaths.SETTINGS_KEY)}>
|
||||
<Text color="blue" fontWeight={500} textStyle="body.small.medium" fontSize="12px">
|
||||
View Secret Key
|
||||
</Text>
|
||||
</PseudoBox>
|
||||
);
|
||||
};
|
||||
|
||||
export const Home = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { identities } = useWallet();
|
||||
@@ -36,7 +48,10 @@ export const Home = () => {
|
||||
|
||||
return (
|
||||
<Flex wrap="wrap" py={5} px={4} flexDirection="column" height="100vh">
|
||||
<LogoWithName />
|
||||
<Flex justifyContent="space-between" align="center">
|
||||
<LogoWithName />
|
||||
<SecretKeyButton />
|
||||
</Flex>
|
||||
<Flex flex={1} mt={10} justifyContent={[null, 'center']}>
|
||||
{isSignedIn ? (
|
||||
<SignOut
|
||||
|
||||
@@ -23,6 +23,7 @@ export enum ScreenName {
|
||||
export enum ScreenPaths {
|
||||
GENERATION = '/sign-up',
|
||||
SECRET_KEY = '/sign-up/secret-key',
|
||||
SETTINGS_KEY = '/settings/secret-key',
|
||||
SAVE_KEY = '/sign-up/save-secret-key',
|
||||
USERNAME = '/sign-up/username',
|
||||
SIGN_IN = '/sign-in',
|
||||
@@ -30,6 +31,7 @@ export enum ScreenPaths {
|
||||
ADD_ACCOUNT = '/sign-in/add-account',
|
||||
CHOOSE_ACCOUNT = '/connect/choose-account',
|
||||
REGISTRY_ERROR = '/username-error',
|
||||
HOME = '/',
|
||||
}
|
||||
|
||||
// TODO: clarify usage of password for local key encryption
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
version: 2 # use CircleCI 2.0
|
||||
jobs: # a collection of steps
|
||||
build: # runs not using Workflows must have a `build` job as entry point
|
||||
working_directory: ~/connect # directory where steps will run
|
||||
docker: # run the steps with Docker
|
||||
- image: circleci/node:10.16.3
|
||||
steps: # a collection of executable commands
|
||||
- checkout # special step to check out source code to working directory
|
||||
- restore_cache: # special step to restore the dependency cache
|
||||
# Read about caching dependencies: https://circleci.com/docs/2.0/caching/
|
||||
key: yarn-packages-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache: # special step to save the dependency cache
|
||||
key: yarn-packages-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- ./.cache/yarn
|
||||
# - run: # run tests
|
||||
# name: test
|
||||
# command: yarn test
|
||||
# - run: # run coverage report
|
||||
# name: code-coverage
|
||||
# command: './node_modules/.bin/nyc report --reporter=text-lcov'
|
||||
- run: # run lint
|
||||
name: lint
|
||||
command: yarn lint
|
||||
- run: # run lint
|
||||
name: type check
|
||||
command: yarn typecheck
|
||||
14
packages/connect/.github/workflows/main.yml
vendored
14
packages/connect/.github/workflows/main.yml
vendored
@@ -1,14 +0,0 @@
|
||||
name: Compressed Size
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: preactjs/compressed-size-action@v1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -24,6 +24,7 @@ export interface ScreenHeaderProps {
|
||||
icon: string;
|
||||
};
|
||||
title?: string | JSX.Element;
|
||||
rightContent?: React.FC | JSX.Element;
|
||||
close?: () => void;
|
||||
hideIcon?: boolean;
|
||||
hideLogo?: boolean;
|
||||
@@ -34,6 +35,7 @@ export const ScreenHeader = ({
|
||||
title = 'Secret Key',
|
||||
hideIcon = false,
|
||||
hideLogo = false,
|
||||
rightContent,
|
||||
...rest
|
||||
}: ScreenHeaderProps) => {
|
||||
const { name, icon } = useAppDetails();
|
||||
@@ -59,14 +61,17 @@ export const ScreenHeader = ({
|
||||
justify="space-between"
|
||||
{...rest}
|
||||
>
|
||||
<Flex align="center">
|
||||
{!hideIcon ? <AppIcon src={appIcon} alt={appName || 'loading'} /> : null}
|
||||
{!hideIcon ? (
|
||||
<Box mx="extra-tight" color="ink.300">
|
||||
<ChevronIcon direction="right" />
|
||||
</Box>
|
||||
) : null}
|
||||
<HeaderTitle hideLogo={hideLogo} title={title} />
|
||||
<Flex width="100%" align="center" justifyContent="space-between">
|
||||
<Flex align="center">
|
||||
{!hideIcon ? <AppIcon src={appIcon} alt={appName || 'loading'} /> : null}
|
||||
{!hideIcon ? (
|
||||
<Box mx={1} color="ink.300">
|
||||
<ChevronIcon direction="right" />
|
||||
</Box>
|
||||
) : null}
|
||||
<HeaderTitle hideLogo={hideLogo} title={title} />
|
||||
</Flex>
|
||||
{rightContent ? rightContent : null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:10.16.3
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
- run:
|
||||
name: Formatted with Prettier
|
||||
command: yarn lint:formatting
|
||||
14
packages/ui/.github/workflows/main.yml
vendored
14
packages/ui/.github/workflows/main.yml
vendored
@@ -1,14 +0,0 @@
|
||||
name: Compressed Size
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: preactjs/compressed-size-action@v1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -1230,10 +1230,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/prettier-config/-/prettier-config-0.0.5.tgz#871bd2a38b5bc9aabb1cefeeba6199cc64098957"
|
||||
integrity sha512-A+wfdVwD1Sxd/D3PPJI67Evo7q2fp15hCOFLB5jmzcS1MdN8BQdFm6j51Sti8xLN4qHmuYkicbFBUluGx2h63g==
|
||||
|
||||
"@blockstack/stats@^0.5.4":
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/stats/-/stats-0.5.4.tgz#092ee64de324412688d227073f768907083cb6c4"
|
||||
integrity sha512-tUHfEeXYF5QwVp3xZOPQzfH0Lr5GBc2tr/6M3/CH6GU/cHLRJ63cwiLpUj/3cnb+sYE+OYS00MtOfMsMUtVzug==
|
||||
"@blockstack/stats@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@blockstack/stats/-/stats-0.7.0.tgz#be39d3e76c2a16c1cd1283aa702d1e20b5e04645"
|
||||
integrity sha512-AS/UdJH/cJ7K8la3/TbQziEMlRrNI/4VEHSfYUsEzU7Nx892G6qE4uu/XvW9ycS0Jg2/TW2bjTnRijsyecaEJA==
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.4"
|
||||
|
||||
Reference in New Issue
Block a user