Initial new UI source code import (#2607)

* address -> name mappings

* expanding, loading all coin details

* send use only actual BasicCoin tokens registered (any reg)

* sending token & accounts

* form styling updates

* send form layout in place

* coin send working as expected

* api subscriptions on multiple addresses

* bring in events

* simplify

* basic events display in-place, functionally complete

* basic functionality in-place

* fix horrible event address issue

* rwork display of events slightly

* test TLA availability

* table for owner -> tokens

* fix signature lookup address

* fix signature lookup address

* basic overview styling

* txhash links

* page layout adjustments

* background import

* adjust colors

* no global registration, simplify color selection

* updated styling

* connection dialog for "busy connecting"

* initial token connection - WIP

* init token updates take place

* basic test for manual token

* rework connection display

* allow updates of the secure token

* first stab at making the build build

* update runner tags

* fix linting issues

* skip tests requiring network (should be e2e, TODO)

* re-enable javascript tag/runner

* release push does the trick

* push to any branch, CI name

* javscript-test runner as well

* swap dependencies build requires test

* revert stages swap

* retrieve images associated with tokens

* remove js build deps order

* null image when hash = 0x0

* 6x64 images (hashes for registries)

* don't pass tokens as prop to IdentityIcon

* check images against content hash pictures

* cleanup signer after connection changes

* fix naming typo

* display unknownImages for balances (not available as content hash)

* unknownImage for transfer dialog

* basic githubhint layout

* single input for commit/filename

* ethcore_hashContent call

* lookup hash

* registration in place

* fixes

* events is using a proper table

* pass value through as-is

* stop wrongly using main app IdentityIcon

* NEVER export class instance functions

* alignment back to normal

* typo  in definition

* set & get images working (mostly)

* show content retrieval info

* set exitcode via ||

* use javascript:latest images

* disable npm progress bar

* rename phase I

* rename phase II

* only send build output to GitHub on major branches

* also run the build step as part of the test (until comprehensive)

* ci-specific build (no webpack progress)

* allow for account creation via recovery phrase

* display account uuid (where available), closes #2546

* connection dialog now shows up in dapps as well, closes #2538

* token images show up as expected

* IdentityName component added and deployed

* fix padding tests

* adjust tests to map to stricter 0x-prefixed hex

* render names via common component for the address -> name

* split lint into seperate script (early exit)

* test phases changed to lint, test & pack

* pack part of test phase

* remove files marked for deletion (cleanup)

* Signer cleanups, start moving in the direction of the rest

* add personal signer methods

* basic signer request subscription

* don't poll blockNumber when not connected

* missing return, creating massive ws queue backlogs

* ΞTH -> ETH

* fix failing tests

* registry uses setAddress to actually set addresses now

* bytes mapping operates on lowerCase hex strings

* sha3 ids for each application

* add dappreg to list of contracts

* adjust alignment of queries

* show gas estimation log

* abi with payable for register function

* add key as required

* image retrieval from dappreg

* use proper Image urls

* embed and link apps from Parity, retrieved via /api/apps

* filter apps that has been replaced

* proxy entry for parity-utils

* add basiccoin abi

* add support for fallback abi type

* capture constructor paramaters

* merge master into js

* move images to assets/images/

* add font assets

* import fonts as part of build

* don't inline woff files

* Revert "merge master into js"

This reverts commit cfcfa81bd26f1b3cbc748d3afa1eb5c670b363fe.

* remove unused npm packages

* information on gas estimates (like almost everywhere else)

* don't pass gas & gasPrice to estimation

* display account passwordhint when available

* signer subscriptions based on polling & function trapping

* pending requests retrieved via jsapi

* update signer middleware

* remove all web3 instances

* remove web3 package

* last web3 dependencies removed

* no need to toChecksumAddress - api takes care of it

* expand description for personal_confirmRequest

* Signer conversion from web3 -> parity.js completed

* explicit in no return

* green circle background

* remove generated background

* convert /api/* paths to localhost:8080/api/* paths (hard-coded, temporary)

* change dapps to load from localhost:8080/ui/*

* remove dangling web3 files

* update manager test for signer

* /api/ping -> /

* additional token images

* additional token images

* add missing styles.css for 8180 error pages

* cater for txhash returning null/empty object

* adjust output directories

* Release merge with origin with ours strategy

* additional token images

* cater for development server

* s/localhost/127.0.0.1/ (cater for origin)

* Fix address selection for contract deployment

* Adjust z-index for error overlay

* better text on unique background pattern

* fix signer rejections

* Don't allow gavcoin transfer with no balance

* fix txhash rendering in signer

* remove unnecessary ParityBackground

* script to update js-precompiled

* Redirect from :8080 to :8180

* Remove extra return

* Dapp logo images
This commit is contained in:
Jaco Greeff
2016-10-18 11:52:56 +02:00
committed by Gav Wood
parent 6c7af57529
commit 1e6a2cb378
969 changed files with 57315 additions and 0 deletions

139
js/src/api/format/input.js Normal file
View File

@@ -0,0 +1,139 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { isArray, isHex, isInstanceOf, isString } from '../util/types';
export function inAddress (address) {
// TODO: address validation if we have upper-lower addresses
return inHex(address);
}
export function inBlockNumber (blockNumber) {
if (isString(blockNumber)) {
switch (blockNumber) {
case 'earliest':
case 'latest':
case 'pending':
return blockNumber;
}
}
return inNumber16(blockNumber);
}
export function inData (data) {
if (data && data.length && !isHex(data)) {
data = data.split('').map((chr) => {
return `0${chr.charCodeAt(0).toString(16)}`.slice(-2);
}).join('');
}
return inHex(data);
}
export function inTopics (_topics) {
let topics = (_topics || [])
.filter((topic) => topic)
.map(inHex);
while (topics.length < 4) {
topics.push(null);
}
return topics;
}
export function inFilter (options) {
if (options) {
Object.keys(options).forEach((key) => {
switch (key) {
case 'address':
if (isArray(options[key])) {
options[key] = options[key].map(inAddress);
} else {
options[key] = inAddress(options[key]);
}
break;
case 'fromBlock':
case 'toBlock':
options[key] = inBlockNumber(options[key]);
break;
case 'limit':
options[key] = inNumber10(options[key]);
break;
case 'topics':
options[key] = inTopics(options[key]);
}
});
}
return options;
}
export function inHex (str) {
if (str && str.substr(0, 2) === '0x') {
return str.toLowerCase();
}
return `0x${(str || '').toLowerCase()}`;
}
export function inNumber10 (number) {
if (isInstanceOf(number, BigNumber)) {
return number.toNumber();
}
return (new BigNumber(number || 0)).toNumber();
}
export function inNumber16 (number) {
if (isInstanceOf(number, BigNumber)) {
return inHex(number.toString(16));
}
return inHex((new BigNumber(number || 0)).toString(16));
}
export function inOptions (options) {
if (options) {
Object.keys(options).forEach((key) => {
switch (key) {
case 'from':
case 'to':
options[key] = inAddress(options[key]);
break;
case 'gas':
case 'gasPrice':
case 'value':
case 'nonce':
options[key] = inNumber16(options[key]);
break;
case 'data':
options[key] = inData(options[key]);
break;
}
});
}
return options;
}

View File

@@ -0,0 +1,245 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { inAddress, inBlockNumber, inData, inFilter, inHex, inNumber10, inNumber16, inOptions } from './input';
import { isAddress } from '../../../test/types';
describe('api/format/input', () => {
const address = '0x63cf90d3f0410092fc0fca41846f596223979195';
describe('inAddress', () => {
const address = '63cf90d3f0410092fc0fca41846f596223979195';
it('adds the leading 0x as required', () => {
expect(inAddress(address)).to.equal(`0x${address}`);
});
it('returns verified addresses as-is', () => {
expect(inAddress(`0x${address}`)).to.equal(`0x${address}`);
});
it('returns lowercase equivalents', () => {
expect(inAddress(address.toUpperCase())).to.equal(`0x${address}`);
});
it('returns 0x on null addresses', () => {
expect(inAddress()).to.equal('0x');
});
});
describe('inBlockNumber()', () => {
it('returns earliest as-is', () => {
expect(inBlockNumber('earliest')).to.equal('earliest');
});
it('returns latest as-is', () => {
expect(inBlockNumber('latest')).to.equal('latest');
});
it('returns pending as-is', () => {
expect(inBlockNumber('pending')).to.equal('pending');
});
it('formats existing BigNumber into hex', () => {
expect(inBlockNumber(new BigNumber(0x123456))).to.equal('0x123456');
});
it('formats hex strings into hex', () => {
expect(inBlockNumber('0x123456')).to.equal('0x123456');
});
it('formats numbers into hex', () => {
expect(inBlockNumber(0x123456)).to.equal('0x123456');
});
});
describe('inData', () => {
it('formats to hex', () => {
expect(inData('123456')).to.equal('0x123456');
});
it('converts a string to a hex representation', () => {
expect(inData('jaco')).to.equal('0x6a61636f');
});
});
describe('inHex', () => {
it('leaves leading 0x as-is', () => {
expect(inHex('0x123456')).to.equal('0x123456');
});
it('adds a leading 0x', () => {
expect(inHex('123456')).to.equal('0x123456');
});
it('returns uppercase as lowercase (leading 0x)', () => {
expect(inHex('0xABCDEF')).to.equal('0xabcdef');
});
it('returns uppercase as lowercase (no leading 0x)', () => {
expect(inHex('ABCDEF')).to.equal('0xabcdef');
});
it('handles empty & null', () => {
expect(inHex()).to.equal('0x');
expect(inHex('')).to.equal('0x');
});
});
describe('inFilter', () => {
['address'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = inFilter(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(address);
});
});
['fromBlock', 'toBlock'].forEach((input) => {
it(`formats ${input} number as blockNumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = inFilter(block)[input];
expect(formatted).to.equal('0x123');
});
});
it('ignores and passes through unknown keys', () => {
expect(inFilter({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats an filter options object with relevant entries converted', () => {
expect(
inFilter({
address: address,
fromBlock: 'latest',
toBlock: 0x101,
extraData: 'someExtraStuffInHere',
limit: 0x32
})
).to.deep.equal({
address: address,
fromBlock: 'latest',
toBlock: '0x101',
extraData: 'someExtraStuffInHere',
limit: 50
});
});
});
describe('inNumber10()', () => {
it('formats existing BigNumber into number', () => {
expect(inNumber10(new BigNumber(123))).to.equal(123);
});
it('formats hex strings into decimal', () => {
expect(inNumber10('0x0a')).to.equal(10);
});
it('formats numbers into number', () => {
expect(inNumber10(123)).to.equal(123);
});
it('formats undefined into 0', () => {
expect(inNumber10()).to.equal(0);
});
});
describe('inNumber16()', () => {
it('formats existing BigNumber into hex', () => {
expect(inNumber16(new BigNumber(0x123456))).to.equal('0x123456');
});
it('formats hex strings into hex', () => {
expect(inNumber16('0x123456')).to.equal('0x123456');
});
it('formats numbers into hex', () => {
expect(inNumber16(0x123456)).to.equal('0x123456');
});
it('formats undefined into 0', () => {
expect(inNumber16()).to.equal('0x0');
});
});
describe('inOptions', () => {
['data'].forEach((input) => {
it(`converts ${input} to hex data`, () => {
const block = {};
block[input] = '1234';
const formatted = inData(block[input]);
expect(formatted).to.equal('0x1234');
});
});
['from', 'to'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = inOptions(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(address);
});
});
['gas', 'gasPrice', 'value', 'nonce'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = inOptions(block)[input];
expect(formatted).to.equal('0x123');
});
});
it('ignores and passes through unknown keys', () => {
expect(inOptions({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats an options object with relevant entries converted', () => {
expect(
inOptions({
from: address,
to: address,
gas: new BigNumber('0x100'),
gasPrice: 0x101,
value: 258,
nonce: '0x104',
data: '0123456789',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
from: address,
to: address,
gas: '0x100',
gasPrice: '0x101',
value: '0x102',
nonce: '0x104',
data: '0x0123456789',
extraData: 'someExtraStuffInHere'
});
});
});
});

165
js/src/api/format/output.js Normal file
View File

@@ -0,0 +1,165 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { toChecksumAddress } from '../../abi/util/address';
export function outAccountInfo (infos) {
const ret = {};
Object.keys(infos).forEach((address) => {
const info = infos[address];
ret[outAddress(address)] = {
name: info.name,
uuid: info.uuid,
meta: JSON.parse(info.meta)
};
});
return ret;
}
export function outAddress (address) {
return toChecksumAddress(address);
}
export function outBlock (block) {
if (block) {
Object.keys(block).forEach((key) => {
switch (key) {
case 'author':
case 'miner':
block[key] = outAddress(block[key]);
break;
case 'difficulty':
case 'gasLimit':
case 'gasUsed':
case 'nonce':
case 'number':
case 'totalDifficulty':
block[key] = outNumber(block[key]);
break;
case 'timestamp':
block[key] = outDate(block[key]);
break;
}
});
}
return block;
}
export function outDate (date) {
return new Date(outNumber(date).toNumber() * 1000);
}
export function outLog (log) {
Object.keys(log).forEach((key) => {
switch (key) {
case 'blockNumber':
case 'logIndex':
case 'transactionIndex':
log[key] = outNumber(log[key]);
break;
case 'address':
log[key] = outAddress(log[key]);
break;
}
});
return log;
}
export function outNumber (number) {
return new BigNumber(number || 0);
}
export function outPeers (peers) {
return {
active: outNumber(peers.active),
connected: outNumber(peers.connected),
max: outNumber(peers.max)
};
}
export function outReceipt (receipt) {
if (receipt) {
Object.keys(receipt).forEach((key) => {
switch (key) {
case 'blockNumber':
case 'cumulativeGasUsed':
case 'gasUsed':
case 'transactionIndex':
receipt[key] = outNumber(receipt[key]);
break;
case 'contractAddress':
receipt[key] = outAddress(receipt[key]);
break;
}
});
}
return receipt;
}
export function outSignerRequest (request) {
if (request) {
Object.keys(request).forEach((key) => {
switch (key) {
case 'id':
request[key] = outNumber(request[key]);
break;
case 'payload':
request[key].transaction = outTransaction(request[key].transaction);
break;
}
});
}
return request;
}
export function outTransaction (tx) {
if (tx) {
Object.keys(tx).forEach((key) => {
switch (key) {
case 'blockNumber':
case 'gasPrice':
case 'gas':
case 'nonce':
case 'transactionIndex':
case 'value':
tx[key] = outNumber(tx[key]);
break;
case 'creates':
case 'from':
case 'to':
tx[key] = outAddress(tx[key]);
break;
}
});
}
return tx;
}

View File

@@ -0,0 +1,247 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { outBlock, outAccountInfo, outAddress, outDate, outNumber, outPeers, outReceipt, outTransaction } from './output';
import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types';
describe('api/format/output', () => {
const address = '0x63cf90d3f0410092fc0fca41846f596223979195';
const checksum = '0x63Cf90D3f0410092FC0fca41846f596223979195';
describe('outAccountInfo', () => {
it('returns meta objects parsed', () => {
expect(outAccountInfo(
{ '0x63cf90d3f0410092fc0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: '{"name":"456"}' }
}
)).to.deep.equal({
'0x63Cf90D3f0410092FC0fca41846f596223979195': {
name: 'name', uuid: 'uuid', meta: { name: '456' }
}
});
});
});
describe('outAddress', () => {
it('retuns the address as checksummed', () => {
expect(outAddress(address)).to.equal(checksum);
});
it('retuns the checksum as checksummed', () => {
expect(outAddress(checksum)).to.equal(checksum);
});
});
describe('outBlock', () => {
['author', 'miner'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = outBlock(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(checksum);
});
});
['difficulty', 'gasLimit', 'gasUsed', 'number', 'nonce', 'totalDifficulty'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = outBlock(block)[input];
expect(isInstanceOf(formatted, BigNumber)).to.be.true;
expect(formatted.toString(16)).to.equal('123');
});
});
['timestamp'].forEach((input) => {
it(`formats ${input} number as Date`, () => {
const block = {};
block[input] = 0x57513668;
const formatted = outBlock(block)[input];
expect(isInstanceOf(formatted, Date)).to.be.true;
expect(formatted.getTime()).to.equal(1464940136000);
});
});
it('ignores and passes through unknown keys', () => {
expect(outBlock({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats a block with all the info converted', () => {
expect(
outBlock({
author: address,
miner: address,
difficulty: '0x100',
gasLimit: '0x101',
gasUsed: '0x102',
number: '0x103',
nonce: '0x104',
totalDifficulty: '0x105',
timestamp: '0x57513668',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
author: checksum,
miner: checksum,
difficulty: new BigNumber('0x100'),
gasLimit: new BigNumber('0x101'),
gasUsed: new BigNumber('0x102'),
number: new BigNumber('0x103'),
nonce: new BigNumber('0x104'),
totalDifficulty: new BigNumber('0x105'),
timestamp: new Date('2016-06-03T07:48:56.000Z'),
extraData: 'someExtraStuffInHere'
});
});
});
describe('outDate', () => {
it('converts a second date in unix timestamp', () => {
expect(outDate(0x57513668)).to.deep.equal(new Date('2016-06-03T07:48:56.000Z'));
});
});
describe('outNumber', () => {
it('returns a BigNumber equalling the value', () => {
const bn = outNumber('0x123456');
expect(isBigNumber(bn)).to.be.true;
expect(bn.eq(0x123456)).to.be.true;
});
it('assumes 0 when ivalid input', () => {
expect(outNumber().eq(0)).to.be.true;
});
});
describe('outPeers', () => {
it('converts all internal numbers to BigNumbers', () => {
expect(outPeers({ active: 789, connected: '456', max: 0x7b })).to.deep.equal({
active: new BigNumber(789),
connected: new BigNumber(456),
max: new BigNumber(123)
});
});
});
describe('outReceipt', () => {
['contractAddress'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = outReceipt(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(checksum);
});
});
['blockNumber', 'cumulativeGasUsed', 'cumulativeGasUsed', 'gasUsed', 'transactionIndex'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = outReceipt(block)[input];
expect(isInstanceOf(formatted, BigNumber)).to.be.true;
expect(formatted.toString(16)).to.equal('123');
});
});
it('ignores and passes through unknown keys', () => {
expect(outReceipt({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats a receipt with all the info converted', () => {
expect(
outReceipt({
contractAddress: address,
blockNumber: '0x100',
cumulativeGasUsed: '0x101',
gasUsed: '0x102',
transactionIndex: '0x103',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
contractAddress: checksum,
blockNumber: new BigNumber('0x100'),
cumulativeGasUsed: new BigNumber('0x101'),
gasUsed: new BigNumber('0x102'),
transactionIndex: new BigNumber('0x103'),
extraData: 'someExtraStuffInHere'
});
});
});
describe('outTransaction', () => {
['from', 'to'].forEach((input) => {
it(`formats ${input} address as address`, () => {
const block = {};
block[input] = address;
const formatted = outTransaction(block)[input];
expect(isAddress(formatted)).to.be.true;
expect(formatted).to.equal(checksum);
});
});
['blockNumber', 'gasPrice', 'gas', 'nonce', 'transactionIndex', 'value'].forEach((input) => {
it(`formats ${input} number as hexnumber`, () => {
const block = {};
block[input] = 0x123;
const formatted = outTransaction(block)[input];
expect(isInstanceOf(formatted, BigNumber)).to.be.true;
expect(formatted.toString(16)).to.equal('123');
});
});
it('ignores and passes through unknown keys', () => {
expect(outTransaction({ someRandom: 'someRandom' })).to.deep.equal({ someRandom: 'someRandom' });
});
it('formats a transaction with all the info converted', () => {
expect(
outTransaction({
from: address,
to: address,
blockNumber: '0x100',
gasPrice: '0x101',
gas: '0x102',
nonce: '0x103',
transactionIndex: '0x104',
value: '0x105',
extraData: 'someExtraStuffInHere'
})
).to.deep.equal({
from: checksum,
to: checksum,
blockNumber: new BigNumber('0x100'),
gasPrice: new BigNumber('0x101'),
gas: new BigNumber('0x102'),
nonce: new BigNumber('0x103'),
transactionIndex: new BigNumber('0x104'),
value: new BigNumber('0x105'),
extraData: 'someExtraStuffInHere'
});
});
});
});