diff --git a/package.json b/package.json
index fd1002f..3931009 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,7 @@
"formik": "^2.2.9",
"git-rev-sync": "^3.0.1",
"gmail-send": "^1.8.10",
- "golos-lib-js": "^0.9.54",
+ "golos-lib-js": "^0.9.60",
"iron-session": "6.0.4",
"jspdf": "^2.3.0",
"lodash": "^4.17.11",
diff --git a/public/icons/dropdown-arrow.svg b/public/icons/dropdown-arrow.svg
new file mode 100644
index 0000000..8828aac
--- /dev/null
+++ b/public/icons/dropdown-arrow.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/images/nft.png b/public/images/nft.png
new file mode 100644
index 0000000..db3321a
Binary files /dev/null and b/public/images/nft.png differ
diff --git a/src/App.scss b/src/App.scss
index 74dd489..410e3af 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -6,9 +6,14 @@
@include foundation-everything(true);
@import "./elements/AccountMenu";
+@import "./elements/DropdownMenu";
@import "./elements/GeneratedPasswordInput";
@import "./elements/Expandable";
@import "./elements/LoadingIndicator";
+@import "./elements/nft/NFTSmallIcon";
+@import "./elements/nft/NFTTokens";
+@import "./elements/PagedDropdownMenu";
+@import "./elements/VerticalMenu";
@import "./modules/Header";
@import "./modules/LoginForm";
@@ -21,7 +26,7 @@
@import "./pages/login";
@import "./pages/register";
@import "./pages/sign/transfer";
-@import "./pages/sign/transfer";
+@import "./pages/sign/transfer_nft";
@import "./pages/oauth/[client]/[perms]";
@import "./foundation-overrides";
diff --git a/src/elements/DropdownMenu.jsx b/src/elements/DropdownMenu.jsx
new file mode 100644
index 0000000..5b247d4
--- /dev/null
+++ b/src/elements/DropdownMenu.jsx
@@ -0,0 +1,73 @@
+import React from 'react'
+
+import VerticalMenu from '@/elements/VerticalMenu'
+import { findParent } from '@/utils/DomUtils'
+
+export default class DropdownMenu extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ shown: false,
+ selected: props.selected
+ };
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('click', this.hide);
+ }
+
+ toggle = (e) => {
+ const {shown} = this.state
+ if(shown) this.hide(e)
+ else this.show(e)
+ }
+
+ show = (e) => {
+ e.preventDefault();
+ this.setState({shown: true});
+ setTimeout(() => {
+ document.addEventListener('click', this.hide)
+ }, 1)
+ };
+
+ hide = (e) => {
+ // Do not hide the dropdown if there was a click within it.
+ const inside_dropdown = !!findParent(e.target, 'VerticalMenu');
+ if (inside_dropdown) return;
+
+ e.preventDefault();
+ this.setState({shown: false});
+ document.removeEventListener('click', this.hide);
+ };
+
+ navigate = (e) => {
+ const a = e.target.nodeName.toLowerCase() === 'a' ? e.target : e.target.parentNode;
+ this.setState({show: false});
+ if (a.host !== window.location.host) return;
+ e.preventDefault();
+ window.location.href = a.pathname + a.search
+ };
+
+ getSelectedLabel = (items, selected) => {
+ const selectedEntry = items.find(i => i.value === selected)
+ const selectedLabel = selectedEntry && selectedEntry.label ? selectedEntry.label : selected
+ return selectedLabel
+ }
+
+ render() {
+ const {el, items, selected, children, className, title, href, onClick, noArrow, hideSelected} = this.props;
+ const hasDropdown = items.length > 0
+
+ let entry = children ||
+ {this.getSelectedLabel(items, selected)}
+ {hasDropdown && !noArrow && }
+
+
+ if(hasDropdown) entry = { onClick(e); this.toggle(e) } : this.toggle}>{entry}
+
+ const menu = ;
+ const cls = 'DropdownMenu' + (this.state.shown ? ' show' : '') + (className ? ` ${className}` : '')
+ return React.createElement(el, {className: cls}, [entry, menu]);
+ }
+}
+
diff --git a/src/elements/DropdownMenu.scss b/src/elements/DropdownMenu.scss
new file mode 100644
index 0000000..429ef04
--- /dev/null
+++ b/src/elements/DropdownMenu.scss
@@ -0,0 +1,53 @@
+.DropdownMenu {
+ position: relative;
+ display: inline-block;
+
+ .Icon.dropdown-arrow {
+ top: 2px;
+ margin-right: 0;
+ }
+
+ > .VerticalMenu {
+ visibility: hidden;
+ // min-width: 145px;
+ min-width: 232px;
+ z-index: 1000;
+
+ background-color: white;
+
+ display: block;
+ border: 1px solid $medium-gray;
+ border-radius: $global-radius;
+ opacity: 0;
+ position: absolute;
+ top: 100%;
+
+ // width: auto;
+ transform: translateY(10%);
+ transition: all 0.3s ease 0s, visibility 0s linear 0.3s;
+ box-shadow: 1px 1px 5px 0px rgba(50, 50, 50, 0.75);
+ }
+
+ &.show > .VerticalMenu {
+ visibility: visible;
+ opacity: 1;
+ transform: translateX(0%);
+ transition-delay: 0s;
+ }
+
+ .DropdownMenu.move-left {
+ .VerticalMenu {
+ left: -50%;
+ }
+ }
+
+ &.above > .VerticalMenu {
+ bottom: 100%;
+ top: auto;
+ }
+
+ &.top-most > .VerticalMenu {
+ position: fixed;
+ top: auto;
+ }
+}
diff --git a/src/elements/PagedDropdownMenu.jsx b/src/elements/PagedDropdownMenu.jsx
new file mode 100644
index 0000000..fe931b3
--- /dev/null
+++ b/src/elements/PagedDropdownMenu.jsx
@@ -0,0 +1,152 @@
+import React from 'react'
+import cloneDeep from 'lodash/cloneDeep'
+import isEqual from 'lodash/isEqual'
+import tt from 'counterpart'
+
+import DropdownMenu from '@/elements/DropdownMenu'
+import LoadingIndicator from '@/elements/LoadingIndicator'
+
+const hideLastItem = true;
+
+export default class PagedDropdownMenu extends React.Component {
+ static defaultProps = {
+ page: 1
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ items: [],
+ page: props.page,
+ loading: false,
+ };
+ }
+
+ componentDidMount() {
+ const { items, page, } = this.props
+ this.initItems(this.sliceItems(items, page))
+ this.setState({ page })
+ }
+
+ componentDidUpdate(prevProps) {
+ const { items, page, } = this.props
+ if (items && (!prevProps.items || !isEqual(items, prevProps.items))) {
+ const sliced = this.sliceItems(items, 1)
+ this.initItems(sliced)
+ this.setState({ page: 1 })
+ } else if (page && prevProps.page !== page) {
+ this.setState({ page })
+ }
+ }
+
+ sliceItems = (items, page) => {
+ const { onLoadMore, perPage } = this.props
+ if (onLoadMore) {
+ return items
+ }
+ const startIdx = perPage * (page - 1)
+ const endIdx = startIdx + perPage + 1
+ const sliced = items.slice(startIdx, endIdx)
+ return sliced
+ }
+
+ initItems = (items) => {
+ if (!items || !items.length)
+ return;
+ this.setState({
+ items: cloneDeep(items),
+ });
+ };
+
+ loadMore = async (newPage) => {
+ const { items, page, } = this.state;
+ const { onLoadMore, } = this.props;
+ if (!onLoadMore) {
+ setTimeout(async () => {
+ this.setState({
+ page: newPage
+ }, () => {
+ this.initItems(this.sliceItems(this.props.items, newPage))
+ })
+ }, 10);
+ return
+ }
+ setTimeout(async () => {
+ this.setState({
+ page: newPage,
+ loading: true,
+ });
+ if (onLoadMore) {
+ const res = await onLoadMore({ page, newPage, items, });
+ this.setState({
+ loading: false,
+ });
+ this.initItems(res);
+ }
+ }, 10);
+ };
+
+ nextPage = () => {
+ const { page, } = this.state;
+ this.loadMore(page + 1);
+ };
+
+ prevPage = () => {
+ if (this.state.page === 1) return;
+ const { page, } = this.state;
+ this.loadMore(page - 1);
+ };
+
+ _renderPaginator = () => {
+ const { perPage, } = this.props;
+ const { items, page, } = this.state;
+ const hasMore = items.length > perPage;
+ if (page === 1 && !hasMore) {
+ return null;
+ }
+ const hasPrev = page > 1
+ return {
+ value:
+
+ {hasPrev ? '< ' + tt('g.back') : ''}
+
+ {hasMore ? tt('g.more_list') + ' >' : ''}
+ ,
+ };
+ };
+
+ render() {
+ const { el, selected, children, className, title, href, noArrow, perPage, renderItem, hideSelected, } = this.props
+ const { items, loading, } = this.state;
+
+ let itemsWithPaginator = [];
+ if (!loading) {
+ for (let i = 0; i < items.length; ++i) {
+ const rendered = renderItem(items[i])
+ itemsWithPaginator.push(rendered)
+ }
+ if (items.length > perPage && hideLastItem) {
+ itemsWithPaginator.pop();
+ }
+ const paginator = this._renderPaginator();
+ if (paginator) {
+ itemsWithPaginator.push(paginator);
+ }
+ } else {
+ itemsWithPaginator = [{value:
+
+ }];
+ }
+
+ return ()
+ }
+};
\ No newline at end of file
diff --git a/src/elements/PagedDropdownMenu.scss b/src/elements/PagedDropdownMenu.scss
new file mode 100644
index 0000000..a6c88ee
--- /dev/null
+++ b/src/elements/PagedDropdownMenu.scss
@@ -0,0 +1,16 @@
+.PagedDropdownMenu__paginator {
+ display: inline-block !important;
+ text-align: center;
+ color: #0078C4 !important;
+ font-weight: normal !important;
+ font-size: 100% !important;
+ user-select: none;
+ cursor: pointer;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ width: 50%;
+
+ &.disabled {
+ cursor: auto;
+ }
+}
diff --git a/src/elements/VerticalMenu.jsx b/src/elements/VerticalMenu.jsx
new file mode 100644
index 0000000..1cc3923
--- /dev/null
+++ b/src/elements/VerticalMenu.jsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+import LinkEx from '@/utils/LinkEx'
+
+export default class VerticalMenu extends React.Component {
+ closeMenu = (e) => {
+ // If this was not a left click, or if CTRL or CMD were held, do not close the menu.
+ if(e.button !== 0 || e.ctrlKey || e.metaKey) return;
+
+ // Simulate clicking of document body which will close any open menus
+ document.body.click();
+ }
+
+ render() {
+ const {items, title, description, className, hideValue} = this.props;
+ return
+ {title && - {title}
}
+ {description && - {description}
}
+ {items.map((i, k) => {
+ if(i.value === hideValue) return null
+ const target = i.target
+ return -
+ {i.link ?
+ {i.icon}{i.label ? i.label : i.value}
+ {i.data && {i.data}}
+ {i.addon}
+ :
+
+ {i.icon}{i.label ? i.label : i.value}
+
+ }
+
+ })}
+
;
+ }
+}
diff --git a/src/elements/VerticalMenu.scss b/src/elements/VerticalMenu.scss
new file mode 100644
index 0000000..e8cbc65
--- /dev/null
+++ b/src/elements/VerticalMenu.scss
@@ -0,0 +1,74 @@
+.VerticalMenu {
+
+ width: 200px;
+
+ .Icon {
+ padding-left: 0.1rem;
+ margin-right: 1.3rem;
+ top: 0;
+ fill: $dark-gray;
+ }
+
+ > li > a {
+ display: flex;
+ align-items: center !important;
+ line-height: 1rem;
+ position: relative;
+ padding: 0.7rem 1rem;
+
+ font-size: 14px;
+ letter-spacing: 0.4px;
+ }
+
+ > li > a:hover {
+ background-color: #f0f0f0;
+ }
+
+ > li.title {
+ padding: 0.56rem;
+ font-size: 14px;
+ font-weight: 500;
+ letter-spacing: 0.4px;
+ line-height: 16px;
+ text-align: center;
+ border-bottom: 1px solid $light-gray;
+ }
+
+ > li.description {
+ padding: 0.1rem;
+ font-size: 87.5%;
+ text-align: center;
+ border-bottom: 1px solid $light-gray;
+ }
+
+ &_nav-profile {
+ width: 262px;
+
+ & > li > a {
+ padding: 0 1rem 0 3.8rem;
+ line-height: 50px;
+
+ .Icon {
+ }
+
+ &:hover {
+ .Icon {
+ fill: #3F46AD;
+ }
+ }
+ }
+
+ }
+
+ &_nav-additional {
+ width: 260px;
+ margin: 10px 0;
+
+ & > li > a {
+ padding: 0 20px;
+ line-height: 50px;
+ }
+ }
+
+}
+
diff --git a/src/elements/nft/NFTSmallIcon.jsx b/src/elements/nft/NFTSmallIcon.jsx
new file mode 100644
index 0000000..c3dba89
--- /dev/null
+++ b/src/elements/nft/NFTSmallIcon.jsx
@@ -0,0 +1,12 @@
+import React, { Component, } from 'react'
+
+class NFTSmallIcon extends Component {
+ render() {
+ const { image, ...rest } = this.props
+
+ return
+ }
+}
+
+export default NFTSmallIcon
diff --git a/src/elements/nft/NFTSmallIcon.scss b/src/elements/nft/NFTSmallIcon.scss
new file mode 100644
index 0000000..0ee5941
--- /dev/null
+++ b/src/elements/nft/NFTSmallIcon.scss
@@ -0,0 +1,11 @@
+.NFTSmallIcon {
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ border-radius: 50%;
+
+ width: 3rem;
+ height: 3rem;
+ display: inline-block;
+ vertical-align: top;
+}
diff --git a/src/elements/nft/NFTTokens.jsx b/src/elements/nft/NFTTokens.jsx
new file mode 100644
index 0000000..1fa9c1c
--- /dev/null
+++ b/src/elements/nft/NFTTokens.jsx
@@ -0,0 +1,90 @@
+import React from 'react'
+import tt from 'counterpart'
+
+import NFTSmallIcon from '@/elements/nft/NFTSmallIcon'
+import PagedDropdownMenu from '@/elements/PagedDropdownMenu'
+import DropdownMenu from '@/elements/DropdownMenu'
+
+export function NFTImageStub() {
+ return '/images/nft.png'
+}
+
+export function parseNFTImage(json_metadata, useStub = true) {
+ if (json_metadata) {
+ const meta = JSON.parse(json_metadata)
+ if (meta && meta.image) return meta.image
+ }
+ if (!useStub) return null
+ return NFTImageStub()
+}
+
+class NFTTokens extends React.Component {
+ render() {
+ let { tokens, selected } = this.props
+
+ let selectedItem
+
+ const items = []
+ let i = 0
+ for (const token of tokens) {
+ items.push({
+ key: i,
+ link: '#',
+ value: token.token_id,
+ })
+
+ if (selected.toString() === token.token_id.toString()) {
+ const image = parseNFTImage(token.json_metadata)
+
+ let data = {}
+ try {
+ data = JSON.parse(token.json_metadata)
+ } catch (err) {}
+
+ selectedItem =
+
+
+
+ {data.title || '#' + token.token_id}
+
+
+ }
+
+ ++i
+ }
+
+ return {
+ const token = tokens[item.key]
+
+ const image = parseNFTImage(token.json_metadata)
+
+ let data = {}
+ try {
+ data = JSON.parse(token.json_metadata)
+ } catch (err) {}
+
+ return {
+ ...item,
+ label:
+
+
+ {data.title || '#' + token.token_id}
+ ,
+ addon: {token.name},
+ onClick: (e) => {
+ if (this.props.onItemClick)
+ this.props.onItemClick(e, token)
+ }
+ }
+ }}
+ selected={selected}
+ perPage={10}
+ hideSelected={tokens.length > 1}>
+ {selectedItem}
+
+
+ }
+}
+
+export default NFTTokens
diff --git a/src/elements/nft/NFTTokens.scss b/src/elements/nft/NFTTokens.scss
new file mode 100644
index 0000000..6ee4480
--- /dev/null
+++ b/src/elements/nft/NFTTokens.scss
@@ -0,0 +1,17 @@
+.NFTTokens {
+ .NFTSmallIcon {
+ margin-top: 0.25rem;
+ margin-right: 0.25rem;
+ margin-bottom: 0.25rem;
+ width: 2rem;
+ height: 2rem;
+ }
+
+ .VerticalMenu {
+ a {
+ padding: 5px !important;
+ padding-left: 8px !important;
+ color: black !important;
+ }
+ }
+}
diff --git a/src/locales/en.json b/src/locales/en.json
index 20572ff..04df70c 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -172,6 +172,7 @@
"transfer": "Transfer tokens",
"donate": "Donate to user",
"delegate_vs": "Delegate vesting shares",
+ "transfer_nft": "Transfer NFT-token",
"unfreeze": "Unfreeze account",
"apps_title": "Apps",
"apps_empty": "You are not authorized in any app.",
@@ -190,6 +191,11 @@
"memo_is_public": "This memo is public.",
"submit": "Transfer"
},
+ "oauth_transfer_nft": {
+ "no_tokens": "You have not yet any NFT-tokens.",
+ "token_not_exist": "No such token in your wallet. You can transfer any of these tokens:",
+ "token_is_not_your": "This token is not your. You can transfer any of these tokens:"
+ },
"oauth_donate": {
"submit": "Donate",
"balance": "TIP-balance: "
@@ -251,7 +257,15 @@
"worker_request_vote": "Voting for worker requests",
"proposal_create": "Creating transaction proposals",
"proposal_delete": "Removing transaction proposals",
- "proposal_update": "Voting in transaction proposals"
+ "proposal_update": "Voting in transaction proposals",
+ "proposal_update_active": "Voting in transaction proposals with active authorities",
+ "paid_subscription_create": "Creating, deleting, updating paid subscriptions",
+ "paid_subscription_transfer": "Buying paid subscriptions",
+ "paid_subscription_cancel": "Canceling of paid subscriptions",
+ "nft_collection": "Creating and deleting NFT-collections",
+ "nft_issue": "Issueing of NFT-tokens",
+ "nft_transfer": "Transferring of NFT-tokens",
+ "nft_orders": "Selling, buying NFT-tokens"
},
"recovery": {
"change_title": "Change recovery account",
diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json
index 2b771f2..b6b56eb 100644
--- a/src/locales/ru-RU.json
+++ b/src/locales/ru-RU.json
@@ -172,6 +172,7 @@
"transfer": "Перевести токены",
"donate": "Отблагодарить пользователя",
"delegate_vs": "Делегировать Силу Голоса",
+ "transfer_nft": "Передать NFT-токен",
"unfreeze": "Активировать аккаунт",
"apps_title": "Приложения",
"apps_empty": "Вы пока не авторизованы ни в одном приложении.",
@@ -190,6 +191,11 @@
"memo_is_public": "Эта заметка является публичной.",
"submit": "Перевести"
},
+ "oauth_transfer_nft": {
+ "no_tokens": "У вас пока еще нет NFT-токенов.",
+ "token_not_exist": "Выбранного токена не существует (или он не принадлежит вам). Вы можете передать любой из следующих токенов:",
+ "token_is_not_your": "Выбранный токен вам не принадлежит. Вы можете передать любой из следующих токенов:"
+ },
"oauth_donate": {
"submit": "Передать",
"balance": "TIP-баланс: "
@@ -251,7 +257,15 @@
"worker_request_vote": "Голосование за заявки на работу (воркеры)",
"proposal_create": "Создание пропозалов",
"proposal_delete": "Удаление пропозалов",
- "proposal_update": "Голосование в пропозалах"
+ "proposal_update": "Голосование в пропозалах",
+ "proposal_update_active": "Голосование в пропозалах с активным ключом",
+ "paid_subscription_create": "Создание, удаление и редактирование платных подписок",
+ "paid_subscription_transfer": "Покупка платных подписок",
+ "paid_subscription_cancel": "Отмена покупки платных подписок",
+ "nft_collection": "Создание и удаление коллекций NFT-токенов",
+ "nft_issue": "Выпуск NFT-токенов",
+ "nft_transfer": "Перевод NFT-токенов",
+ "nft_orders": "Покупка, продажа NFT-токенов"
},
"recovery": {
"change_title": "Задать аккаунт для восстановления",
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index 0ae1fcf..7615d55 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -54,13 +54,11 @@ class Index extends React.Component {
const { service_account, sign_endpoint, } = oauthCfg;
let actions = [];
for (let action of [
- 'transfer', 'donate', 'delegate_vs']) {
+ 'transfer', 'donate', 'delegate_vs', 'transfer_nft']) {
actions.push(
-
-
-
+
);
}
let clientList = [];
diff --git a/src/pages/sign/transfer_nft.jsx b/src/pages/sign/transfer_nft.jsx
new file mode 100644
index 0000000..c322582
--- /dev/null
+++ b/src/pages/sign/transfer_nft.jsx
@@ -0,0 +1,300 @@
+import React from 'react';
+import tt from 'counterpart';
+import golos from 'golos-lib-js';
+import { Asset, } from 'golos-lib-js/lib/utils';
+import { Formik, Field, ErrorMessage, } from 'formik';
+import Head from 'next/head';
+
+import LoadingIndicator from '@/elements/LoadingIndicator'
+import LoginForm from '@/modules/LoginForm';
+import NFTTokens from '@/elements/nft/NFTTokens'
+import Header from '@/modules/Header'
+import { getOAuthCfg, getChainData, } from '@/server/oauth';
+import { getOAuthSession, } from '@/server/oauthSession';
+import { withSecureHeadersSSR, } from '@/server/security';
+import { callApi, } from '@/utils/OAuthClient';
+import validate_account_name from '@/utils/validate_account_name';
+
+const uncompose = (query, initial) => {
+ initial.to = query.to || initial.to
+ if (query.token_id) initial.token_id = query.token_id
+ initial.memo = query.memo || initial.memo
+};
+
+export const getServerSideProps = withSecureHeadersSSR(async ({ req, res, resolvedUrl, query, }) => {
+ const action = resolvedUrl.split('?')[0].split('/')[2];
+ let chainData = null;
+ const holder = await getOAuthSession(req, res);
+ if (!holder.oauthEnabled) {
+ return await holder.clearAndRedirect();
+ }
+ const session = holder.session();
+ let initial = null;
+ if (session.account) {
+ chainData = await getChainData(session.account, action);
+ if (chainData.frozen) {
+ return await holder.freeze(session.account)
+ }
+ initial = {
+ from: session.account,
+ to: '',
+ memo: '',
+ };
+ uncompose(query, initial);
+ }
+ return {
+ props: {
+ action,
+ oauthCfg: getOAuthCfg(),
+ session,
+ chainData,
+ initial,
+ },
+ };
+})
+
+class TransferNFT extends React.Component {
+ static propTypes = {
+ };
+
+ state = {
+ };
+
+ componentDidMount() {
+ const { initial, chainData } = this.props
+ if (initial) {
+ if (initial.token_id) {
+ const { nft_tokens } = chainData
+ let exists = false
+ let not_your = false
+ let first
+ for (const token of nft_tokens) {
+ first = first || token.token_id
+ if (token.token_id.toString() === initial.token_id.toString()) {
+ exists = true
+ if (token.owner !== initial.from) {
+ not_your = true
+ }
+ break
+ }
+ }
+ this.setState({
+ selected: exists ? initial.token_id : first,
+ query_token_error: !exists ? tt('oauth_transfer_nft.token_not_exist') : not_your ? tt('oauth_transfer_nft.token_is_not_your') : null
+ })
+ } else {
+ const { nft_tokens } = chainData
+ if (nft_tokens.length) {
+ this.setState({
+ selected: nft_tokens[0].token_id
+ })
+ }
+ }
+ }
+ }
+
+ normalizeChainData = () => {
+ const { chainData, } = this.props;
+ if (!chainData || !golos.isNativeLibLoaded()) {
+ return;
+ }
+ return chainData
+ }
+
+ onToChange = (e, handle) => {
+ let value = e.target.value.trim().toLowerCase();
+ e.target.value = value;
+ return handle(e);
+ };
+
+ validate = (values) => {
+ const errors = {};
+ if (!values.to) {
+ errors.to = tt('g.required');
+ } else {
+ const err = validate_account_name(values.to);
+ if (err) errors.to = err;
+ }
+
+ return errors;
+ };
+
+ _onSubmit = (values, { setSubmitting, }) => {
+ this.setState({
+ done: false,
+ })
+
+ const { action, session, oauthCfg, } = this.props;
+ const { sign_endpoint, } = oauthCfg;
+ const { from, to, } = values;
+ const { selected } = this.state
+
+ let memo = values.memo || '';
+
+ golos.config.set('websocket', sign_endpoint);
+ golos.config.set('credentials', 'include');
+ const callback = (err, res) => {
+ setSubmitting(false);
+ if (err) {
+ alert(err);
+ return;
+ }
+ window.close();
+ this.setState({
+ done: true,
+ })
+ };
+ golos.broadcast.nftTransfer('', parseInt(selected), from, to, memo, callback);
+ };
+
+ compose = (values) => {
+ if (!$GLS_IsBrowser) {
+ return;
+ }
+
+ let url = window.location.href.split('?')[0];
+
+ const chainData = this.normalizeChainData()
+ const { to, memo, } = values;
+
+ if (!golos.isNativeLibLoaded() || !chainData)
+ return '...';
+
+ url += '?';
+ url += 'to=' + (to || '');
+ if (this.state.selected) {
+ url += '&token_id=' + this.state.selected
+ }
+ url += '&memo=' + (memo || '');
+
+ return (
+ {tt('oauth_main_jsx.link')}
+ {url}
+ );
+ };
+
+ render() {
+ const { state, } = this;
+ const { done, selected, query_token_error } = state;
+ const chainData = this.normalizeChainData()
+
+ const { action, oauthCfg, session, initial, } = this.props;
+ const { account, } = session;
+
+ if (account === null) {
+ return (
+
+
+
{tt('oauth_main_jsx.' + action)} | {tt('oauth_main_jsx.title')}
+
+
+
+ );
+ }
+
+ if (!chainData)
+ return null
+
+ const { nft_tokens } = chainData
+
+ let form = null;
+ if (initial && chainData) form = (
+ {({
+ handleSubmit, isSubmitting, isValid, dirty, errors, touched, values, handleChange, setFieldValue,
+ }) => (
+
+ )}
+ );
+
+ return (
+
+
+
{tt('oauth_main_jsx.' + action)} | {tt('oauth_main_jsx.title')}
+
+
+
+
+
{tt('oauth_main_jsx.' + action)}
+ {(!account || !form) && }
+ {form}
+
+
+
);
+ }
+};
+
+export default TransferNFT
diff --git a/src/pages/sign/transfer_nft.scss b/src/pages/sign/transfer_nft.scss
new file mode 100644
index 0000000..baec762
--- /dev/null
+++ b/src/pages/sign/transfer_nft.scss
@@ -0,0 +1,14 @@
+.TransferNFT {
+ .error {
+ margin-bottom: 1rem;
+ }
+ .button {
+ margin-bottom: 0rem;
+ }
+ .done {
+ margin-left: 0.5rem;
+ }
+ .page-link {
+ margin-top: 1rem;
+ }
+}
diff --git a/src/server/oauth.js b/src/server/oauth.js
index 498ecab..6bf9635 100644
--- a/src/server/oauth.js
+++ b/src/server/oauth.js
@@ -159,6 +159,15 @@ async function getChainData(account, action = 'transfer') {
if (data.balances['GBG'])
data.balances['GBG'] = acc.sbd_balance;
+ if (action === 'transfer_nft') {
+ data.nft_tokens = await golos.api.getNftTokensAsync({
+ owner: account,
+ state: 'not_selling_only',
+ limit: 100,
+ })
+ return data;
+ }
+
let uia = await golos.api.getAccountsBalancesAsync([account]);
if (!uia[0]) {
return data;
diff --git a/src/utils/DomUtils.js b/src/utils/DomUtils.js
new file mode 100644
index 0000000..36021a8
--- /dev/null
+++ b/src/utils/DomUtils.js
@@ -0,0 +1,5 @@
+export function findParent(el, class_name) {
+ if (el.className && el.className.indexOf && el.className.indexOf(class_name) !== -1) return el;
+ if (el.parentNode) return findParent(el.parentNode, class_name);
+ return null;
+}
diff --git a/src/utils/LinkEx.jsx b/src/utils/LinkEx.jsx
new file mode 100644
index 0000000..62fd5f7
--- /dev/null
+++ b/src/utils/LinkEx.jsx
@@ -0,0 +1,20 @@
+import React from 'react'
+import Link from 'next/link'
+
+const isExternal = (url) => {
+ return /^https?:\/\//.test(url)
+}
+
+class LinkEx extends React.Component {
+ render() {
+ const { props } = this
+ const { href, children } = props
+ if (isExternal(href) || href === '#') {
+ const rel = props.rel || 'noopener noreferrer'
+ return {children}
+ }
+ return {children}
+ }
+}
+
+export default LinkEx
diff --git a/src/utils/oauthPermissions.js b/src/utils/oauthPermissions.js
index b96c2de..7ee8ec1 100644
--- a/src/utils/oauthPermissions.js
+++ b/src/utils/oauthPermissions.js
@@ -413,6 +413,70 @@ let permissions = {
},
internal: true,
},
+ paid_subscription_create: {
+ ops: [
+ 'paid_subscription_create',
+ 'paid_subscription_update',
+ 'paid_subscription_delete',
+ ],
+ cond: (op) => {
+ return [POSTING, op.author];
+ },
+ },
+ paid_subscription_transfer: {
+ ops: [
+ 'paid_subscription_transfer',
+ ],
+ cond: (op) => {
+ if (op.from_tip) {
+ return [POSTING, op.from]
+ }
+ return [ACTIVE, op.from]
+ },
+ },
+ paid_subscription_cancel: {
+ ops: [
+ 'paid_subscription_cancel',
+ ],
+ cond: (op) => {
+ return [POSTING, op.subscriber]
+ },
+ },
+ nft_collection: {
+ ops: [
+ 'nft_collection',
+ 'nft_collection_delete',
+ ],
+ cond: (op) => {
+ return [ACTIVE, op.creator]
+ },
+ },
+ nft_issue: {
+ ops: [
+ 'nft_issue',
+ ],
+ cond: (op) => {
+ return [ACTIVE, op.creator]
+ },
+ },
+ nft_transfer: {
+ ops: [
+ 'nft_transfer',
+ ],
+ cond: (op) => {
+ return [ACTIVE, op.from]
+ },
+ },
+ nft_orders: {
+ ops: [
+ 'nft_sell',
+ 'nft_buy',
+ 'nft_cancel_order',
+ ],
+ cond: (op) => {
+ return [ACTIVE, op.seller || op.owner || op.buyer]
+ },
+ },
};
module.exports = {
diff --git a/yarn.lock b/yarn.lock
index ca30db4..03de80c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1072,7 +1072,7 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
-assert@2.0.0, assert@^2.0.0:
+assert@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/assert/-/assert-2.0.0.tgz#95fc1c616d48713510680f2eaf2d10dd22e02d32"
integrity sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==
@@ -1082,6 +1082,17 @@ assert@2.0.0, assert@^2.0.0:
object-is "^1.0.1"
util "^0.12.0"
+assert@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/assert/-/assert-2.1.0.tgz#6d92a238d05dc02e7427c881fb8be81c8448b2dd"
+ integrity sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==
+ dependencies:
+ call-bind "^1.0.2"
+ is-nan "^1.3.2"
+ object-is "^1.1.5"
+ object.assign "^4.1.4"
+ util "^0.12.5"
+
ast-types-flow@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
@@ -1706,9 +1717,9 @@ core-js@^2.4.0:
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-js@^3.17.3:
- version "3.32.0"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.0.tgz#7643d353d899747ab1f8b03d2803b0312a0fb3b6"
- integrity sha512-rd4rYZNlF3WuoYuRIDEmbR/ga9CeuWX9U05umAvgrrZoHY4Z++cp/xwPQMvUpBB4Ag6J8KfD80G0zwCyaSxDww==
+ version "3.33.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.33.1.tgz#ef3766cfa382482d0a2c2bc5cb52c6d88805da52"
+ integrity sha512-qVSq3s+d4+GsqN0teRCJtM6tdEEXyWxjzbhVrCHmBS5ZTM0FS2MOS0D13dUXAWDUN6a+lHI/N1hF9Ytz6iLl9Q==
core-js@^3.6.0:
version "3.16.4"
@@ -2005,6 +2016,15 @@ deepmerge@^2.1.1:
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
+define-data-property@^1.0.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3"
+ integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==
+ dependencies:
+ get-intrinsic "^1.2.1"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.0"
+
define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -2012,6 +2032,15 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
+define-properties@^1.1.4:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
+ integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
+ dependencies:
+ define-data-property "^1.0.1"
+ has-property-descriptors "^1.0.0"
+ object-keys "^1.1.1"
+
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -2743,6 +2772,11 @@ function-bind@^1.1.1:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@@ -2783,6 +2817,16 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
+ integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==
+ dependencies:
+ function-bind "^1.1.2"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ hasown "^2.0.0"
+
get-orientation@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/get-orientation/-/get-orientation-1.1.2.tgz#20507928951814f8a91ded0a0e67b29dfab98947"
@@ -2924,10 +2968,10 @@ gmail-send@^1.8.10:
lodash "^4.17.15"
nodemailer "^6.3.0"
-golos-lib-js@^0.9.54:
- version "0.9.54"
- resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.54.tgz#87399d038c948f5d447e457df768c5d3fb26c8b2"
- integrity sha512-YyvWp3QL5jH+1PbYl23lCIIVz0znNWytCZPAfzk4SKbGv3PRYXRB/Jp8ZZNjmJsXMby5+ZB5imRLFZo+ggfXXg==
+golos-lib-js@^0.9.60:
+ version "0.9.60"
+ resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.60.tgz#abf7e88499954312c817ebc38532e8e8d0451cbd"
+ integrity sha512-9UlH8OLTZj70godojecYTHIJ/6X+YW80zDVTYEq4qGDjZFlIAyczqu4UHlwZIxOtlZoyFMFtxMonWEXkw3nnPg==
dependencies:
abort-controller "^3.0.0"
assert "^2.0.0"
@@ -2951,6 +2995,13 @@ golos-lib-js@^0.9.54:
stream-browserify "^3.0.0"
ws "^8.2.3"
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
graceful-fs@4.1.15:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
@@ -3001,11 +3052,28 @@ has-flag@^4.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+has-property-descriptors@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340"
+ integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==
+ dependencies:
+ get-intrinsic "^1.2.2"
+
+has-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+ integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
has-symbols@^1.0.1, has-symbols@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
has-tostringtag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
@@ -3042,6 +3110,13 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
+hasown@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
+ integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
+ dependencies:
+ function-bind "^1.1.2"
+
he@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
@@ -3369,7 +3444,7 @@ is-installed-globally@~0.4.0:
global-dirs "^3.0.0"
is-path-inside "^3.0.2"
-is-nan@^1.2.1:
+is-nan@^1.2.1, is-nan@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.2.tgz#043a54adea31748b55b6cd4e09aadafa69bd9e1d"
integrity sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==
@@ -4038,9 +4113,9 @@ node-fetch@2.6.1:
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^2.6.12:
- version "2.6.12"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.12.tgz#02eb8e22074018e3d5a83016649d04df0e348fba"
- integrity sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+ integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
@@ -4172,7 +4247,7 @@ object-inspect@^1.11.0, object-inspect@^1.9.0:
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
-object-is@^1.0.1:
+object-is@^1.0.1, object-is@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
@@ -4195,6 +4270,16 @@ object.assign@^4.1.2:
has-symbols "^1.0.1"
object-keys "^1.1.1"
+object.assign@^4.1.4:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
+ integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.1.4"
+ has-symbols "^1.0.3"
+ object-keys "^1.1.1"
+
object.entries@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861"
@@ -5903,6 +5988,17 @@ util@0.12.4, util@^0.12.0:
safe-buffer "^5.1.2"
which-typed-array "^1.1.2"
+util@^0.12.5:
+ version "0.12.5"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc"
+ integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==
+ dependencies:
+ inherits "^2.0.3"
+ is-arguments "^1.0.4"
+ is-generator-function "^1.0.7"
+ is-typed-array "^1.1.3"
+ which-typed-array "^1.1.2"
+
utils-merge@1.x.x:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
@@ -6098,9 +6194,9 @@ wrappy@1:
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
ws@^8.2.3:
- version "8.13.0"
- resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"
- integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==
+ version "8.14.2"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
+ integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
xtend@^4.0.2:
version "4.0.2"