Sharing of user types (#4210)

* Sharing of user types

* url encoding/decoding

* assert non-array

* Developer page URL encoding

* Cleanup saving/loading encoding

* Move fflate

* CopyButton
This commit is contained in:
Jaco Greeff 2020-12-15 10:54:40 +01:00 committed by GitHub
parent de747bca9d
commit fe31bc522d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 9 deletions

View File

@ -321,7 +321,7 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
onChange={_selectSeedType}
options={seedOpt}
/>
< CopyButton
<CopyButton
className='copyMoved'
type={seedType === 'bip' ? t<string>('mnemonic') : seedType === 'raw' ? isEthereum ? t<string>('private key') : 'seed' : t<string>('raw seed')}
value={seed}

View File

@ -9,7 +9,8 @@ import store from 'store';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { Button, Editor, InputFile } from '@polkadot/react-components';
import { decodeUrlTypes, encodeUrlTypes } from '@polkadot/react-api/urlTypes';
import { Button, CopyButton, Editor, InputFile } from '@polkadot/react-components';
import { isJsonObject, stringToU8a, u8aToString } from '@polkadot/util';
import { useTranslation } from './translate';
@ -37,14 +38,16 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
const [isTypesValid, setIsTypesValid] = useState(true);
const [types, setTypes] = useState<Record<string, any>>(EMPTY_TYPES);
const [typesPlaceholder, setTypesPlaceholder] = useState<string | null>(null);
const [sharedUrl, setSharedUrl] = useState<string | null>(null);
useEffect((): void => {
const types = store.get('types') as Record<string, unknown> || {};
const types = decodeUrlTypes() || store.get('types') as Record<string, unknown> || {};
if (Object.keys(types).length) {
setCode(JSON.stringify(types, null, 2));
setTypes({});
setTypesPlaceholder(Object.keys(types).join(', '));
setSharedUrl(encodeUrlTypes(types));
}
}, []);
@ -125,6 +128,8 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
const _saveDeveloper = useCallback(
(): void => {
let url = null;
try {
registry.register(types);
store.set('types', types);
@ -133,6 +138,12 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
action: t<string>('Your custom types have been added'),
status: 'success'
});
if (Object.keys(types).length) {
url = encodeUrlTypes(types);
console.log(url);
}
} catch (error) {
console.error(error);
setIsTypesValid(false);
@ -141,6 +152,8 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
status: 'error'
});
}
setSharedUrl(url);
},
[onStatusChange, t, types]
);
@ -180,6 +193,11 @@ function Developer ({ className = '', onStatusChange }: Props): React.ReactEleme
</div>
</div>
<Button.Group>
<CopyButton
label={t<string>('Share')}
type={t<string>('url')}
value={sharedUrl}
/>
<Button
icon='sync'
label={t<string>('Reset')}

View File

@ -22,6 +22,7 @@
"@babel/runtime": "^7.12.5",
"@polkadot/api": "^3.0.1",
"@polkadot/extension-dapp": "^0.35.2-12",
"fflate": "^0.4.2",
"rxjs-compat": "^6.6.3"
}
}

View File

@ -25,6 +25,7 @@ import { defaults as addressDefaults } from '@polkadot/util-crypto/address/defau
import ApiContext from './ApiContext';
import registry from './typeRegistry';
import { decodeUrlTypes } from './urlTypes';
interface Props {
children: React.ReactNode;
@ -66,7 +67,7 @@ function isKeyringLoaded () {
}
function getDevTypes (): Record<string, Record<string, string>> {
const types = store.get('types', {}) as Record<string, Record<string, string>>;
const types = decodeUrlTypes() || store.get('types', {}) as Record<string, Record<string, string>>;
const names = Object.keys(types);
names.length && console.log('Injected types:', names.join(', '));

View File

@ -0,0 +1,37 @@
// Copyright 2017-2020 @polkadot/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { unzlibSync, zlibSync } from 'fflate';
import queryString from 'query-string';
import uiSettings from '@polkadot/ui-settings';
import { assert, stringToU8a, u8aToString } from '@polkadot/util';
import { base64Decode, base64Encode } from '@polkadot/util-crypto';
export function decodeUrlTypes (): Record<string, any> | null {
const urlOptions = queryString.parse(location.href.split('?')[1]);
if (urlOptions.types) {
try {
assert(!Array.isArray(urlOptions.types), 'Expected a single type specification');
const parts = urlOptions.types.split('#');
const compressed = base64Decode(decodeURIComponent(parts[0]));
const uncompressed = unzlibSync(compressed);
return JSON.parse(u8aToString(uncompressed)) as Record<string, any>;
} catch (error) {
console.error(error);
}
}
return null;
}
export function encodeUrlTypes (types: Record<string, any>): string {
const jsonU8a = stringToU8a(JSON.stringify(types));
const compressed = zlibSync(jsonU8a, { level: 9 });
const encoded = base64Encode(compressed);
return `${window.location.origin}${window.location.pathname}?rpc=${encodeURIComponent(uiSettings.apiUrl)}&types=${encodeURIComponent(encoded)}`;
}

View File

@ -30,4 +30,8 @@ export default React.memo(styled(ButtonGroup)`
.ui--Button {
margin: 0 0.25rem;
}
.ui--CopyButton {
display: inline-block;
}
`);

View File

@ -15,6 +15,7 @@ interface Props {
children?: React.ReactNode;
className?: string;
icon?: IconName;
label?: React.ReactNode;
type?: string;
isMnemonic?: boolean;
value: string;
@ -22,20 +23,19 @@ interface Props {
const NOOP = () => undefined;
function CopyButton ({ children, className = '', icon = 'copy', type, value }: Props): React.ReactElement<Props> {
function CopyButton ({ children, className = '', icon = 'copy', label, type, value }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { queueAction } = useContext(StatusContext);
const _onCopy = useCallback(
(): void => {
(type !== 'other') && queueAction && queueAction({
account: type !== 'mnemonic' ? value : undefined,
queueAction && queueAction({
action: t<string>('clipboard'),
message: t<string>('{{type}} copied', { replace: { type: type || t<string>('other') } }),
message: t<string>('{{type}} copied', { replace: { type: type || t<string>('value') } }),
status: 'queued'
});
},
[type, queueAction, t, value]
[type, queueAction, t]
);
return (
@ -50,6 +50,8 @@ function CopyButton ({ children, className = '', icon = 'copy', type, value }: P
<Button
className='icon-button show-on-hover'
icon={icon}
isDisabled={!value}
label={label}
onClick={NOOP}
/>
</span>

View File

@ -2504,6 +2504,7 @@ __metadata:
"@babel/runtime": ^7.12.5
"@polkadot/api": ^3.0.1
"@polkadot/extension-dapp": ^0.35.2-12
fflate: ^0.4.2
rxjs-compat: ^6.6.3
languageName: unknown
linkType: soft