From 16a84dda1f97941e37b59ea91e9688797d1c7c49 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 7 Feb 2018 16:02:00 +0200 Subject: [PATCH 01/13] Better JS API --- package.json | 4 +- src/App.js | 82 +++++++++---------- src/api/Invitation.js | 25 +++--- src/api/Swarm.js | 35 ++++++++ src/api/User.js | 40 ++++++---- src/api/UserRegistry.js | 46 ++++++----- src/api/WhisperSocket.js | 168 ++++++++++++++++++++++++--------------- src/api/index.js | 17 ++++ src/api/swarm.js | 27 ------- src/utils/getWeb3.js | 13 +-- src/utils/promisify.js | 16 ++-- truffle.js | 11 +++ 12 files changed, 287 insertions(+), 197 deletions(-) create mode 100644 src/api/Swarm.js create mode 100644 src/api/index.js delete mode 100644 src/api/swarm.js diff --git a/package.json b/package.json index 6cf553a..db466d1 100644 --- a/package.json +++ b/package.json @@ -41,9 +41,11 @@ "recursive-readdir": "2.1.0", "strip-ansi": "3.0.1", "style-loader": "0.13.1", - "truffle-contract": "^1.1.8", + "truffle": "^4.0.6", + "truffle-contract": "3.0.2", "truffle-solidity-loader": "0.0.8", "url-loader": "0.5.7", + "web3": "^0.20.4", "webpack": "1.14.0", "webpack-dev-server": "1.16.2", "webpack-manifest-plugin": "1.1.0", diff --git a/src/App.js b/src/App.js index 87b674d..96cbf76 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,5 @@ import React, { Component } from 'react' -import SimpleStorageContract from '../build/contracts/SimpleStorage.json' +import API from './api' import getWeb3 from './utils/getWeb3' import './css/oswald.css' @@ -12,57 +12,59 @@ class App extends Component { super(props) this.state = { - storageValue: 0, - web3: null + web3: null, + userRegistry: null, + user: null, + chatSocket: null, + userId: "" } } componentWillMount() { // Get network provider and web3 instance. // See utils/getWeb3 for more info. + getWeb3.then((web3) => { - getWeb3 - .then(results => { - this.setState({ - web3: results.web3 + let api = API(web3) + this.setState(() => { + return { + web3: web3, + userRegistry: api.Registry.instance() + } }) - // Instantiate contract once web3 provided. - this.instantiateContract() - }) - .catch(() => { - console.log('Error finding web3.') - }) - } - - instantiateContract() { - /* - * SMART CONTRACT EXAMPLE - * - * Normally these functions would be called in the context of a - * state management library, but for convenience I've placed them here. - */ + web3.eth.getAccounts((err, accounts) => { + if (err) throw err + if (!web3.eth.defaultAccount) { + web3.eth.defaultAccount = accounts[0] + } - const contract = require('truffle-contract') - const simpleStorage = contract(SimpleStorageContract) - simpleStorage.setProvider(this.state.web3.currentProvider) + console.log("Accounts", accounts) + + api.Registry.instance().meOrRegister().then((user) => { + console.log("User", user) + let socket = new api.Whisper(user) + this.setState(() => { + return { + user: user, + chatSocket: socket, + userId: user.id + } + }) - // Declaring this for later so we can chain functions on SimpleStorage. - var simpleStorageInstance + socket.on('error', (err) => { + console.error('Socket error', err) + }) - // Get accounts. - this.state.web3.eth.getAccounts((error, accounts) => { - simpleStorage.deployed().then((instance) => { - simpleStorageInstance = instance + socket.on('message', (message) => { + console.log('New message', message) + }) - // Stores a given value, 5 by default. - return simpleStorageInstance.set(5, {from: accounts[0]}) - }).then((result) => { - // Get the value from the contract to prove it worked. - return simpleStorageInstance.get.call(accounts[0]) - }).then((result) => { - // Update state with the result. - return this.setState({ storageValue: result.c[0] }) + socket.start() + }) + .catch((err) => { + console.error("Error", err) + }) }) }) } @@ -82,7 +84,7 @@ class App extends Component {

Smart Contract Example

If your contracts compiled and migrated successfully, below will show a stored value of 5 (by default).

Try changing the value stored on line 59 of App.js.

-

The stored value is: {this.state.storageValue}

+

The stored value is: {this.state.userId}

diff --git a/src/api/Invitation.js b/src/api/Invitation.js index 124f7f6..24b17b4 100644 --- a/src/api/Invitation.js +++ b/src/api/Invitation.js @@ -1,11 +1,8 @@ import contract from 'truffle-contract' import InvitationContract from '../../build/contracts/Invitation.json' -import getWeb3 from '../utils/getWeb3' - -const invitationContract = contract(InvitationContract) class Invitation { - constructor(invitationId, fromMe) { + constructor(invitationId, fromMe, invitationContract) { this.id = invitationId this._fromMe = fromMe this._invitationContract = invitationContract.at(invitationId) @@ -26,11 +23,17 @@ class Invitation { } } -export default (invitationId, fromMe) => { - return getWeb3().then((result) => { - if (invitationContract.getProvider() !== result.web3.currentProvider) { - invitationContract.setProvider(result.web3.currentProvider); +Invitation.bootstrap = function(web3) { + const invitationContract = contract(InvitationContract) + invitationContract.setProvider(web3.currentProvider) + + class InvitationBootstrapped extends this { + constructor(invitationId, fromMe) { + super(invitationId, fromMe, invitationContract) } - return new Invitation(invitationId, fromMe) - }) -} \ No newline at end of file + } + + return InvitationBootstrapped +} + +export default Invitation \ No newline at end of file diff --git a/src/api/Swarm.js b/src/api/Swarm.js new file mode 100644 index 0000000..79f244e --- /dev/null +++ b/src/api/Swarm.js @@ -0,0 +1,35 @@ + +import promisify from '../utils/promisify' + +class Swarm { + constructor(bzz) { + this._bzz = bzz + } + + uploadFile() { + return promisify(this._bzz, 'upload')({ pick: 'file' }) + } + + download(hash) { + return promisify(this._bzz, 'download')(hash) + } +} + +Swarm.bootstrap = function(web3) { + class SwarmBootstrapped extends this { + constructor() { + super(web3.bzz) + } + } + + var instance = null + + SwarmBootstrapped.instance = function() { + if (instance) return instance + return instance = new this() + } + + return SwarmBootstrapped +} + + export default Swarm diff --git a/src/api/User.js b/src/api/User.js index ab1167b..772db1b 100644 --- a/src/api/User.js +++ b/src/api/User.js @@ -1,27 +1,27 @@ import contract from 'truffle-contract' import UserContract from '../../build/contracts/User.json' -import getWeb3 from '../utils/getWeb3' -import Invitation from './Invitation' +import EventEmitter from 'events' -const userContract = contract(UserContract) +class User extends EventEmitter { + constructor (userId, userContract, invitation) { + super() -class User { - constructor (userId) { this._userContract = userContract.at(userId) + this._Invitation = invitation this.id = userId this._userContract.then((user) => { user.UserProfileUpdated().watch((err, response) => { this._profile = null + this.emit("profileUpdated") }) user.WhisperInfoUpdated().watch((err, response) => { this._pubKey = null this._whisperInfo = null - if (this.whisperInfoUpdated) this.whisperInfoUpdated() + this.emit("whisperInfoUpdated") }) }) this._sentInvitations = null this._inboxInvitations = null - this.whisperInfoUpdated = null this._pubKey = null this._profile = null this._whisperInfo = null @@ -64,29 +64,35 @@ class User { if (this._sentInvitations) return this._sentInvitations return this._sentInvitations = this._userContract .then((user) => user.getSentInvitations()) - .then((invitations) => invitations.map((i) => Invitation(i, true))) + .then((invitations) => invitations.map((i) => new this._Invitation(i, true))) } getInboxInvitations() { if (this._inboxInvitations) return this._inboxInvitations return this._inboxInvitations = this._userContract .then((user) => user.getInboxInvitations()) - .then((invitations) => invitations.map((i) => Invitation(i, false))) + .then((invitations) => invitations.map((i) => new this._Invitation(i, false))) } getContacts() { if (this._contacts) return this._contacts return this._contacts = this._userContract .then((user) => user.getContacts()) - .then((contacts) => contacts.map((c) => new User(c))) + .then((contacts) => contacts.map((c) => new this.constructor(c))) } } -export default (userId) => { - return getWeb3().then((result) => { - if (userContract.getProvider() !== result.web3.currentProvider) { - userContract.setProvider(result.web3.currentProvider) +User.bootstrap = function(web3, Invitation) { + const userContract = contract(UserContract) + userContract.setProvider(web3.currentProvider) + + class UserBootstrapped extends this { + constructor(userId) { + super(userId, userContract, Invitation) } - return new User(userId) - }) -} \ No newline at end of file + } + + return UserBootstrapped +} + +export default User \ No newline at end of file diff --git a/src/api/UserRegistry.js b/src/api/UserRegistry.js index 6f8c4fb..9dbc5fb 100644 --- a/src/api/UserRegistry.js +++ b/src/api/UserRegistry.js @@ -1,43 +1,51 @@ import contract from 'truffle-contract' import UserRegistryContract from '../../build/contracts/UserRegistry.json' -import getWeb3 from '../utils/getWeb3' -import User from './User' -const userRegistryContract = contract(UserRegistryContract) - -var userRegistry = null; +const NULL_ID = '0x0000000000000000000000000000000000000000' class UserRegistry { - constructor(userRegistryContract) { + constructor(userRegistryContract, User) { this._userRegistryContract = userRegistryContract this._me = null + this._User = User } meOrRegister() { if (this._me) return this._me return this._me = this._userRegistryContract .then((registry) => registry.me().then((me) => [me, registry])) - .then(([me, registry]) => (me === '0x00000000000000000000') ? registry.register().then(() => registry.me()) : me) - .then((me) => User(me)) + .then(([me, registry]) => (me === NULL_ID) ? registry.register().then(() => registry.me()) : me) + .then((me) => new this._User(me)) } getUser(walletId) { return this._userRegistryContract .then((registry) => registry.getUser(walletId)) .then((userId) => { - if (userId === '0x00000000000000000000') throw new Error('User not found') - return User(userId) + if (userId === NULL_ID) throw new Error('User not found') + return new this._User(userId) }) } } -export default () => { - if (userRegistry) return Promise.resolve(userRegistry) - return getWeb3().then((result) => { - if (userRegistryContract.getProvider() !== result.web3.currentProvider) { - userRegistryContract.setProvider(result.web3.currentProvider) +UserRegistry.bootstrap = function(web3, User) { + const userRegistryContract = contract(UserRegistryContract) + userRegistryContract.setProvider(web3.currentProvider) + + class UserRegistryBootstrapped extends this { + constructor() { + super(userRegistryContract.deployed(), User) } - userRegistry = new UserRegistry(userRegistryContract.deployed()) - return userRegistry - }) -} \ No newline at end of file + } + + var instance = null + + UserRegistryBootstrapped.instance = function() { + if (instance) return instance + return instance = new this() + } + + return UserRegistryBootstrapped +} + +export default UserRegistry \ No newline at end of file diff --git a/src/api/WhisperSocket.js b/src/api/WhisperSocket.js index f5f9495..0c497f1 100644 --- a/src/api/WhisperSocket.js +++ b/src/api/WhisperSocket.js @@ -1,95 +1,133 @@ -import getWeb3 from '../utils/getWeb3' +import EventEmitter from 'events' import promisify from '../utils/promisify' -class WhisperSocket { - constructor(user) { - this.user = user; - this.onmessage = null; - this.onerror = null; - this._listening = null; +class WhisperSocket extends EventEmitter { + constructor(user, shh) { + super() - this.user.whisperInfoUpdated = () => { - this.initialize().catch((err) => { - console.error(err) - if (this.onerror) this.onerror(err) - }) + this._user = user + this._shh = shh + this._filter = null + this._timer = null + + user.on('whisperInfoUpdated', () => { + this._infoUpdated() + }) + } + + start() { + if (this._timer) { + this.emit("error", new Error("Already started")) + return } + this._updateIdentity() + .then(([pubKey, key]) => this._listen(pubKey, key)) + .then(() => { + this.emit("started") + }) + .catch((err) => { + this.emit("error", err) + }) } - initialize() { - return this._updateIdentity() - .then(([pubKey, key]) => { - return this._listen(pubKey, key).then(() => this) - }) + stop() { + if (this._timer) { + clearInterval(this._timer) + this.emit("stopped") + } + this._timer = null } - _listen(pubKey, key) { - return this._getShh().then((shh) => { - if (this._listening) this._listening.stopWatching() - this._listening = shh.filter({ to: key }) - this._listening.watch((err, message) => { + _infoUpdated() { + if (this._filter) { + this.stop() + this._shh.deleteMessageFilter(this._filter, (err) => { if (err) { - console.error(err) - if (this.onerror) this.onerror(err) - } else { - console.debug('Message arrived', message) - if (this.onmessage) { - this.onmessage({ - from: message.from, - message: message.payload, - sent: message.sent - }) - } + this.emit('error', err) } + this._filter = null + this.start() }) - }) + } else if (this._timer) { + this.stop() + this.start() + } } - _updateIdentity() { - this.user.getWhisperInfo() - .then((info) => this._getShh().then((shh) => [info, shh])) - .then(([info, shh]) => { - return info.key ? promisify(shh.hasIdentity)(info.key).then((has) => [info.key, has, shh]) : [info.key, false, shh] - }) - .then(([key, has, shh]) => { - return has ? [key, false, shh] : promisify(shh.newIdentity)().then((id) => [id, true, shh]) + _listen(pubKey, key) { + console.log("Listen") + if (this._timer) return Promise.reject(new Error("Already listening" )) + + var filter = this._filter ? Promise.resolve(this._filter) : promisify(this._shh, 'newMessageFilter')({ + key: key, + sig: pubKey }) - .then(([id, created, shh]) => { - // This part for new web3 1.0 API - // shh.getPublicKey(id).then((pubKey) => { - // if (created) { - // return this.user.setWhisperInfo(pubKey, id).then(() => [pubKey, id]) - // } - // return [pubKey, id] - // }) - if (created) { - return this.user.setWhisperInfo(id, id).then(() => [id, id]) - } - return [id, id] + + return filter.then((filterId) => { + this._filter = filterId + + this._timer = setInterval(() => { + this._shh.getFilterMessages(filterId, (err, messages) => { + if (err) { + console.error(err) + this.emit("error", err) + } else { + console.debug('New messages arrived', messages) + messages.forEach((message) => { + this.emit("message", { + from: message.sig, + message: message.payload, + sent: new Date(message.timestamp) + }) + }) + } + }) + }, 2000) }) } - _getShh() { - return getWeb3().then((result) => result.web3.shh); + _updateIdentity() { + return this._user.getWhisperInfo() + .then(([pubKey, key]) => { + return key !== '' ? promisify(this._shh, 'hasKeyPair')(key).then((has) => [key, pubKey, has]) : [key, pubKey, false] + }) + .then(([key, pubKey, has]) => { + return has ? [key, pubKey, false] : promisify(this._shh, 'newKeyPair')().then((id) => [id, pubKey, true]) + }) + .then(([id, pubKey, created]) => { + if (created) { + return promisify(this._shh, 'getPublicKey')(id).then((pubKey) => { + return this._user.setWhisperInfo(pubKey, id).then(() => [pubKey, id]) + }) + } + return [pubKey, id] + }) } sendMessage(to, message) { return to.getPubKey().then((pubKey) => { - return this.user.getWhisperInfo().then((info) => [info.key, pubKey]) + return this._user.getWhisperInfo().then((info) => [info.key, pubKey]) }).then(([key, pubKey]) => { - return this._getShh().then((shh) => { - return promisify(shh.post)({ - from: key, + return promisify(this._shh, 'post')({ + sig: key, + pubKey: pubKey, payload: message, ttl: 100, - workToProve: 10 + powTarget: 0.5, + powTime: 2 }) }) - }) } } -export default (user) => { - return (new WhisperSocket(user)).initialize() -} \ No newline at end of file +WhisperSocket.bootstrap = function(web3) { + class WhisperSocketBootstrapped extends this { + constructor(user) { + super(user, web3.shh) + } + } + return WhisperSocketBootstrapped +} + +export default WhisperSocket \ No newline at end of file diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..deacd0a --- /dev/null +++ b/src/api/index.js @@ -0,0 +1,17 @@ +import UserRegistry from './UserRegistry' +import User from './User' +import Invitation from './Invitation' +import Swarm from './Swarm' +import Whisper from './WhisperSocket' + +export default (web3) => { + let InvitationBootstrapped = Invitation.bootstrap(web3) + let UserBootstrapped = User.bootstrap(web3, InvitationBootstrapped) + return { + Registry: UserRegistry.bootstrap(web3, UserBootstrapped), + User: UserBootstrapped, + Invitation: InvitationBootstrapped, + Swarm: Swarm.bootstrap(web3), + Whisper: Whisper.bootstrap(web3) + } +} \ No newline at end of file diff --git a/src/api/swarm.js b/src/api/swarm.js deleted file mode 100644 index 524417f..0000000 --- a/src/api/swarm.js +++ /dev/null @@ -1,27 +0,0 @@ - -import getWeb3 from '../utils/getWeb3' -import promisify from '../utils/promisify' - -class Swarm { - constructor(bzz) { - this._bzz = bzz - } - - uploadFile() { - return promisify(this._bzz.upload)({ pick: 'file' }) - } - - download(hash) { - return promisify(this._bzz.download)(hash) - } -} - -var swarm = null - -export default () => { - if (swarm) return Promise.resolve(swarm) - return getWeb3().then((result) => { - swarm = new Swarm(result.web3.bzz) - return swarm - }) -} diff --git a/src/utils/getWeb3.js b/src/utils/getWeb3.js index 4a25eae..8569d89 100644 --- a/src/utils/getWeb3.js +++ b/src/utils/getWeb3.js @@ -3,7 +3,6 @@ import Web3 from 'web3' let getWeb3 = new Promise(function(resolve, reject) { // Wait for loading completion to avoid race conditions with web3 injection timing. window.addEventListener('load', function() { - var results var web3 = window.web3 // Checking if Web3 has been injected by the browser (Mist/MetaMask) @@ -11,13 +10,9 @@ let getWeb3 = new Promise(function(resolve, reject) { // Use Mist/MetaMask's provider. web3 = new Web3(web3.currentProvider) - results = { - web3: web3 - } - console.log('Injected web3 detected.'); - resolve(results) + resolve(web3) } else { // Fallback to localhost if no web3 injection. We've configured this to // use the development console's port by default. @@ -25,13 +20,9 @@ let getWeb3 = new Promise(function(resolve, reject) { web3 = new Web3(provider) - results = { - web3: web3 - } - console.log('No web3 instance injected, using Local web3.'); - resolve(results) + resolve(web3) } }) }) diff --git a/src/utils/promisify.js b/src/utils/promisify.js index 6d40149..155bbdf 100644 --- a/src/utils/promisify.js +++ b/src/utils/promisify.js @@ -1,14 +1,18 @@ -export default (func) => { +export default (func_or_obj, funcName) => { return (...args) => { return new Promise((resolve, reject) => { - let newArgs = args.concat([(...results) => { - if (results[0]) { - reject(results[0]) + let newArgs = args.concat([(err, result) => { + if (err) { + reject(err) } else { - resolve(results.slice(1)) + resolve(result) } }]) - func(...newArgs) + if (funcName) { + func_or_obj[funcName](...newArgs) + } else { + func_or_obj(...newArgs) + } }) } } \ No newline at end of file diff --git a/truffle.js b/truffle.js index a6330d6..afe2605 100644 --- a/truffle.js +++ b/truffle.js @@ -1,4 +1,15 @@ module.exports = { // See // to customize your Truffle configuration! + networks: { + "rinkeby": { + network_id: 4, + host: "127.0.0.1", + port: 8546 + } + }, + rpc: { + host: "127.0.0.1", + port: 8545 + } }; From 3ba05bb56ae8b9fca52b0c8aee5cb049a86b26da Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 7 Feb 2018 17:14:27 +0200 Subject: [PATCH 02/13] Better Whisper socket --- contracts/User.sol | 6 ++-- src/App.js | 5 +++ src/api/WhisperSocket.js | 70 ++++++++++++++++------------------------ 3 files changed, 36 insertions(+), 45 deletions(-) diff --git a/contracts/User.sol b/contracts/User.sol index 6c589d8..4b13a55 100644 --- a/contracts/User.sol +++ b/contracts/User.sol @@ -32,12 +32,12 @@ contract User { _ ; } - modifier iscontact() { + modifier iscontactOrOwner() { var found = false; for (uint i = 0; i < _contacts.length && !found; i++) { found = _contacts[i] == msg.sender; } - require(found); + require(found || msg.sender == _owner); _ ; } @@ -68,7 +68,7 @@ contract User { registry.ownerUpdated(oldOwner, newOwner); } - function getWhisperPubKey() public view iscontact returns (string) { + function getWhisperPubKey() public view iscontactOrOwner returns (string) { return _whisperInfo.pubKey; } diff --git a/src/App.js b/src/App.js index 96cbf76..c8938da 100644 --- a/src/App.js +++ b/src/App.js @@ -60,6 +60,11 @@ class App extends Component { console.log('New message', message) }) + socket.on('started', () => { + console.log('Socket started') + socket.sendMessage(user, "TEST MESSAGE") + }) + socket.start() }) .catch((err) => { diff --git a/src/api/WhisperSocket.js b/src/api/WhisperSocket.js index 0c497f1..4968763 100644 --- a/src/api/WhisperSocket.js +++ b/src/api/WhisperSocket.js @@ -3,13 +3,13 @@ import EventEmitter from 'events' import promisify from '../utils/promisify' class WhisperSocket extends EventEmitter { - constructor(user, shh) { + constructor(user, web3) { super() this._user = user - this._shh = shh + this._shh = web3.shh + this._web3 = web3 this._filter = null - this._timer = null user.on('whisperInfoUpdated', () => { this._infoUpdated() @@ -32,24 +32,19 @@ class WhisperSocket extends EventEmitter { } stop() { - if (this._timer) { - clearInterval(this._timer) - this.emit("stopped") - } - this._timer = null - } - - _infoUpdated() { if (this._filter) { - this.stop() - this._shh.deleteMessageFilter(this._filter, (err) => { + this._filter.stopWatching((err) => { if (err) { this.emit('error', err) } - this._filter = null - this.start() + this.emit('stopped') }) - } else if (this._timer) { + this._filter = null + } + } + + _infoUpdated() { + if (this._filter) { this.stop() this.start() } @@ -57,33 +52,24 @@ class WhisperSocket extends EventEmitter { _listen(pubKey, key) { console.log("Listen") - if (this._timer) return Promise.reject(new Error("Already listening" )) + if (this._filter) throw new Error("Already listening" ) - var filter = this._filter ? Promise.resolve(this._filter) : promisify(this._shh, 'newMessageFilter')({ - key: key, - sig: pubKey + this._filter = this._shh.newMessageFilter({ + privateKeyID: key }) - return filter.then((filterId) => { - this._filter = filterId - - this._timer = setInterval(() => { - this._shh.getFilterMessages(filterId, (err, messages) => { - if (err) { - console.error(err) - this.emit("error", err) - } else { - console.debug('New messages arrived', messages) - messages.forEach((message) => { - this.emit("message", { - from: message.sig, - message: message.payload, - sent: new Date(message.timestamp) - }) - }) - } + this._filter.watch((err, message) => { + if (err) { + console.error(err) + this.emit("error", err) + } else { + console.debug('New messages arrived', message) + this.emit("message", { + from: message.sig, + message: this._web3.toUtf8(message.payload), + sent: new Date(message.timestamp) }) - }, 2000) + } }) } @@ -107,12 +93,12 @@ class WhisperSocket extends EventEmitter { sendMessage(to, message) { return to.getPubKey().then((pubKey) => { - return this._user.getWhisperInfo().then((info) => [info.key, pubKey]) + return this._user.getWhisperInfo().then(([ownPubKey, ownKey]) => [ownKey, pubKey]) }).then(([key, pubKey]) => { return promisify(this._shh, 'post')({ sig: key, pubKey: pubKey, - payload: message, + payload: this._web3.fromUtf8(message), ttl: 100, powTarget: 0.5, powTime: 2 @@ -124,7 +110,7 @@ class WhisperSocket extends EventEmitter { WhisperSocket.bootstrap = function(web3) { class WhisperSocketBootstrapped extends this { constructor(user) { - super(user, web3.shh) + super(user, web3) } } return WhisperSocketBootstrapped From 0fdde54bf805dfaee981f235883437852040926d Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 7 Feb 2018 17:29:35 +0200 Subject: [PATCH 03/13] Invitation public fromMe --- src/api/Invitation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/Invitation.js b/src/api/Invitation.js index 24b17b4..4c694b0 100644 --- a/src/api/Invitation.js +++ b/src/api/Invitation.js @@ -4,14 +4,14 @@ import InvitationContract from '../../build/contracts/Invitation.json' class Invitation { constructor(invitationId, fromMe, invitationContract) { this.id = invitationId - this._fromMe = fromMe + this.fromMe = fromMe this._invitationContract = invitationContract.at(invitationId) this._user = null } getUser() { if (this._user) return this._user - return this._user = this._invitationContract.then((invitation) => this._fromMe ? invitation.invitee() : invitation.inviter()) + return this._user = this._invitationContract.then((invitation) => this.fromMe ? invitation.invitee() : invitation.inviter()) } accept() { From 66e6c2c718ab1b59706b356678ee1816ebdbffbb Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Wed, 7 Feb 2018 19:19:03 +0200 Subject: [PATCH 04/13] Better Accounts handling --- src/App.js | 36 +++++++++++++++++++-------------- src/api/Accounts.js | 44 +++++++++++++++++++++++++++++++++++++++++ src/api/UserRegistry.js | 22 +++++++++++++++------ src/api/index.js | 7 +++++-- 4 files changed, 86 insertions(+), 23 deletions(-) create mode 100644 src/api/Accounts.js diff --git a/src/App.js b/src/App.js index c8938da..d199526 100644 --- a/src/App.js +++ b/src/App.js @@ -33,22 +33,29 @@ class App extends Component { } }) - web3.eth.getAccounts((err, accounts) => { - if (err) throw err - if (!web3.eth.defaultAccount) { - web3.eth.defaultAccount = accounts[0] - } - - console.log("Accounts", accounts) - - api.Registry.instance().meOrRegister().then((user) => { - console.log("User", user) - let socket = new api.Whisper(user) + api.Accounts.instance().getAccounts() + .then((accounts) => { // Search for account with registered user. If not found - register with 0 account + let found = accounts.find((value) => value.user != null) + if (found) { + api.Accounts.instance().currentAccount = found.id + return found + } else { + let account = accounts[0].id + api.Accounts.instance().currentAccount = account.id + return api.Registry.instance().register().then((user) => { + account.user = user + return account + }) + } + }) + .then((account) => { + console.log("Account", account) + let socket = new api.Whisper(account.user) this.setState(() => { return { - user: user, + user: account.user, chatSocket: socket, - userId: user.id + userId: account.user.id } }) @@ -62,7 +69,7 @@ class App extends Component { socket.on('started', () => { console.log('Socket started') - socket.sendMessage(user, "TEST MESSAGE") + socket.sendMessage(account.user, "TEST MESSAGE") }) socket.start() @@ -70,7 +77,6 @@ class App extends Component { .catch((err) => { console.error("Error", err) }) - }) }) } diff --git a/src/api/Accounts.js b/src/api/Accounts.js new file mode 100644 index 0000000..b5248cc --- /dev/null +++ b/src/api/Accounts.js @@ -0,0 +1,44 @@ + +import promisify from '../utils/promisify' + +class Accounts { + constructor(web3, userRegistry) { + this._web3 = web3 + this._userRegistry = userRegistry + } + + getAccounts() { + return promisify(this._web3.eth, 'getAccounts')().then((accounts) => { + return Promise.all(accounts.map((account) => { + return this._userRegistry.me(account).then((user) => { return { id: account, user: user } }) + })) + }) + } + + get currentAccount() { + return this._web3.eth.defaultAccount + } + + set currentAccount(walledAccountId) { + this._web3.eth.defaultAccount = walledAccountId; + } +} + +Accounts.bootstrap = function(web3, userRegistry) { + class AccountsBootstrapped extends this { + constructor() { + super(web3, userRegistry) + } + } + + var instance = null + + AccountsBootstrapped.instance = function() { + if (instance) return instance + return instance = new this() + } + + return AccountsBootstrapped +} + +export default Accounts \ No newline at end of file diff --git a/src/api/UserRegistry.js b/src/api/UserRegistry.js index 9dbc5fb..8fb9929 100644 --- a/src/api/UserRegistry.js +++ b/src/api/UserRegistry.js @@ -6,16 +6,26 @@ const NULL_ID = '0x0000000000000000000000000000000000000000' class UserRegistry { constructor(userRegistryContract, User) { this._userRegistryContract = userRegistryContract - this._me = null this._User = User } meOrRegister() { - if (this._me) return this._me - return this._me = this._userRegistryContract - .then((registry) => registry.me().then((me) => [me, registry])) - .then(([me, registry]) => (me === NULL_ID) ? registry.register().then(() => registry.me()) : me) - .then((me) => new this._User(me)) + return this.me().then((user) => user ? user : this.register()) + } + + me(walletId) { + return this._userRegistryContract + .then((registry) => registry.me(walletId ? { from: walletId } : undefined)) + .then((userId) => (userId === NULL_ID) ? null : new this._User(userId)) + } + + register() { + return this._userRegistryContract + .then((registry) => registry.register().then(() => registry.me())) + .then((userId) => { + if (userId === NULL_ID) throw new Error("Registration failed") + return new this._User(userId) + }) } getUser(walletId) { diff --git a/src/api/index.js b/src/api/index.js index deacd0a..8050227 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -3,15 +3,18 @@ import User from './User' import Invitation from './Invitation' import Swarm from './Swarm' import Whisper from './WhisperSocket' +import Accounts from './Accounts' export default (web3) => { let InvitationBootstrapped = Invitation.bootstrap(web3) let UserBootstrapped = User.bootstrap(web3, InvitationBootstrapped) + let UserRegistryBootstrapped = UserRegistry.bootstrap(web3, UserBootstrapped) return { - Registry: UserRegistry.bootstrap(web3, UserBootstrapped), + Registry: UserRegistryBootstrapped, User: UserBootstrapped, Invitation: InvitationBootstrapped, Swarm: Swarm.bootstrap(web3), - Whisper: Whisper.bootstrap(web3) + Whisper: Whisper.bootstrap(web3), + Accounts: Accounts.bootstrap(web3, UserRegistryBootstrapped.instance()) } } \ No newline at end of file From 5f047faa3a6bf10b4ed67660ae082dac30969a57 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Thu, 8 Feb 2018 01:41:09 +0200 Subject: [PATCH 05/13] better accounts handling --- package.json | 2 +- src/App.js | 3 +-- src/api/Accounts.js | 9 +++++++++ src/api/WhisperSocket.js | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index db466d1..8a58ff8 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "strip-ansi": "3.0.1", "style-loader": "0.13.1", "truffle": "^4.0.6", - "truffle-contract": "3.0.2", + "truffle-contract": "^3.0.3", "truffle-solidity-loader": "0.0.8", "url-loader": "0.5.7", "web3": "^0.20.4", diff --git a/src/App.js b/src/App.js index d199526..7df0387 100644 --- a/src/App.js +++ b/src/App.js @@ -40,8 +40,7 @@ class App extends Component { api.Accounts.instance().currentAccount = found.id return found } else { - let account = accounts[0].id - api.Accounts.instance().currentAccount = account.id + let account = accounts[0] return api.Registry.instance().register().then((user) => { account.user = user return account diff --git a/src/api/Accounts.js b/src/api/Accounts.js index b5248cc..d5845d5 100644 --- a/src/api/Accounts.js +++ b/src/api/Accounts.js @@ -7,8 +7,17 @@ class Accounts { this._userRegistry = userRegistry } + _checkDefault(accounts) { + if (!this.currentAccount) { + if (accounts.length === 0) throw new Error("NO_ACCOUNT") + this.currentAccount = accounts[0] + } + } + getAccounts() { return promisify(this._web3.eth, 'getAccounts')().then((accounts) => { + console.log("Accounts", accounts) + this._checkDefault(accounts) return Promise.all(accounts.map((account) => { return this._userRegistry.me(account).then((user) => { return { id: account, user: user } }) })) diff --git a/src/api/WhisperSocket.js b/src/api/WhisperSocket.js index 4968763..8ec8dd9 100644 --- a/src/api/WhisperSocket.js +++ b/src/api/WhisperSocket.js @@ -67,7 +67,7 @@ class WhisperSocket extends EventEmitter { this.emit("message", { from: message.sig, message: this._web3.toUtf8(message.payload), - sent: new Date(message.timestamp) + sent: new Date(message.timestamp*1000) }) } }) From 39822c79ebacc4c9018290d2ae59fe5b84c84a97 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Thu, 8 Feb 2018 21:51:50 +0200 Subject: [PATCH 06/13] Better DI for API. --- src/App.js | 4 ++-- src/api/Accounts.js | 19 ++------------- src/api/Invitation.js | 26 ++++++++++----------- src/api/Swarm.js | 23 ++++-------------- src/api/User.js | 23 ++++++++---------- src/api/UserRegistry.js | 29 ++++++++--------------- src/api/WhisperSocket.js | 13 +++-------- src/api/index.js | 23 +++++++++--------- src/utils/di.js | 50 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 103 insertions(+), 107 deletions(-) create mode 100644 src/utils/di.js diff --git a/src/App.js b/src/App.js index 7df0387..007ffa5 100644 --- a/src/App.js +++ b/src/App.js @@ -29,7 +29,7 @@ class App extends Component { this.setState(() => { return { web3: web3, - userRegistry: api.Registry.instance() + userRegistry: api.UserRegistry.instance() } }) @@ -41,7 +41,7 @@ class App extends Component { return found } else { let account = accounts[0] - return api.Registry.instance().register().then((user) => { + return api.UserRegistry.instance().register().then((user) => { account.user = user return account }) diff --git a/src/api/Accounts.js b/src/api/Accounts.js index d5845d5..fb6c858 100644 --- a/src/api/Accounts.js +++ b/src/api/Accounts.js @@ -2,6 +2,8 @@ import promisify from '../utils/promisify' class Accounts { + static $inject = ['Web3()', 'UserRegistry()'] + constructor(web3, userRegistry) { this._web3 = web3 this._userRegistry = userRegistry @@ -33,21 +35,4 @@ class Accounts { } } -Accounts.bootstrap = function(web3, userRegistry) { - class AccountsBootstrapped extends this { - constructor() { - super(web3, userRegistry) - } - } - - var instance = null - - AccountsBootstrapped.instance = function() { - if (instance) return instance - return instance = new this() - } - - return AccountsBootstrapped -} - export default Accounts \ No newline at end of file diff --git a/src/api/Invitation.js b/src/api/Invitation.js index 4c694b0..c4cdc6b 100644 --- a/src/api/Invitation.js +++ b/src/api/Invitation.js @@ -2,16 +2,21 @@ import contract from 'truffle-contract' import InvitationContract from '../../build/contracts/Invitation.json' class Invitation { - constructor(invitationId, fromMe, invitationContract) { + static $inject = ['User', '_InvitationContract()'] + + constructor(User, Contract, invitationId, fromMe) { this.id = invitationId this.fromMe = fromMe - this._invitationContract = invitationContract.at(invitationId) + this._invitationContract = Contract.at(invitationId) + this._User = User this._user = null } getUser() { if (this._user) return this._user - return this._user = this._invitationContract.then((invitation) => this.fromMe ? invitation.invitee() : invitation.inviter()) + return this._user = this._invitationContract + .then((invitation) => this.fromMe ? invitation.invitee() : invitation.inviter()) + .then((userId) => new this._User(userId)) } accept() { @@ -21,19 +26,12 @@ class Invitation { reject() { return this._invitationContract.then((invitation) => invitation.reject()) } -} -Invitation.bootstrap = function(web3) { - const invitationContract = contract(InvitationContract) - invitationContract.setProvider(web3.currentProvider) - - class InvitationBootstrapped extends this { - constructor(invitationId, fromMe) { - super(invitationId, fromMe, invitationContract) - } + static injected(context) { + const invitationContract = contract(InvitationContract) + invitationContract.setProvider(this.context.injected('Web3()').currentProvider) + this.context.addSingletonObject('_InvitationContract', invitationContract) } - - return InvitationBootstrapped } export default Invitation \ No newline at end of file diff --git a/src/api/Swarm.js b/src/api/Swarm.js index 79f244e..e061cc1 100644 --- a/src/api/Swarm.js +++ b/src/api/Swarm.js @@ -2,8 +2,10 @@ import promisify from '../utils/promisify' class Swarm { - constructor(bzz) { - this._bzz = bzz + static $inject = ['Web3()'] + + constructor(web3) { + this._bzz = web3.bzz } uploadFile() { @@ -15,21 +17,4 @@ class Swarm { } } -Swarm.bootstrap = function(web3) { - class SwarmBootstrapped extends this { - constructor() { - super(web3.bzz) - } - } - - var instance = null - - SwarmBootstrapped.instance = function() { - if (instance) return instance - return instance = new this() - } - - return SwarmBootstrapped -} - export default Swarm diff --git a/src/api/User.js b/src/api/User.js index 772db1b..89e17b9 100644 --- a/src/api/User.js +++ b/src/api/User.js @@ -3,11 +3,13 @@ import UserContract from '../../build/contracts/User.json' import EventEmitter from 'events' class User extends EventEmitter { - constructor (userId, userContract, invitation) { + static $inject = ['Invitation', '_UserContract()'] + + constructor (Invitation, UserContract, userId) { super() - this._userContract = userContract.at(userId) - this._Invitation = invitation + this._userContract = UserContract.at(userId) + this._Invitation = Invitation this.id = userId this._userContract.then((user) => { user.UserProfileUpdated().watch((err, response) => { @@ -80,19 +82,12 @@ class User extends EventEmitter { .then((user) => user.getContacts()) .then((contacts) => contacts.map((c) => new this.constructor(c))) } -} -User.bootstrap = function(web3, Invitation) { - const userContract = contract(UserContract) - userContract.setProvider(web3.currentProvider) - - class UserBootstrapped extends this { - constructor(userId) { - super(userId, userContract, Invitation) - } + static injected() { + const userContract = contract(UserContract) + userContract.setProvider(this.context.injected('Web3()').currentProvider) + this.context.addSingletonObject('_UserContract', userContract) } - - return UserBootstrapped } export default User \ No newline at end of file diff --git a/src/api/UserRegistry.js b/src/api/UserRegistry.js index 8fb9929..4d29ad2 100644 --- a/src/api/UserRegistry.js +++ b/src/api/UserRegistry.js @@ -4,8 +4,11 @@ import UserRegistryContract from '../../build/contracts/UserRegistry.json' const NULL_ID = '0x0000000000000000000000000000000000000000' class UserRegistry { - constructor(userRegistryContract, User) { - this._userRegistryContract = userRegistryContract + + static $inject = ['User', '_UserRegistryContract()'] + + constructor(User, userRegistryContract) { + this._userRegistryContract = userRegistryContract.deployed() this._User = User } @@ -36,26 +39,12 @@ class UserRegistry { return new this._User(userId) }) } -} - -UserRegistry.bootstrap = function(web3, User) { - const userRegistryContract = contract(UserRegistryContract) - userRegistryContract.setProvider(web3.currentProvider) - - class UserRegistryBootstrapped extends this { - constructor() { - super(userRegistryContract.deployed(), User) - } - } - var instance = null - - UserRegistryBootstrapped.instance = function() { - if (instance) return instance - return instance = new this() + static injected(context) { + const userRegistryContract = contract(UserRegistryContract) + userRegistryContract.setProvider(this.context.injected('Web3()').currentProvider) + this.context.addSingletonObject('_UserRegistryContract', userRegistryContract) } - - return UserRegistryBootstrapped } export default UserRegistry \ No newline at end of file diff --git a/src/api/WhisperSocket.js b/src/api/WhisperSocket.js index 8ec8dd9..ea6ab15 100644 --- a/src/api/WhisperSocket.js +++ b/src/api/WhisperSocket.js @@ -3,7 +3,9 @@ import EventEmitter from 'events' import promisify from '../utils/promisify' class WhisperSocket extends EventEmitter { - constructor(user, web3) { + static $inject = ['Web3()'] + + constructor(web3, user) { super() this._user = user @@ -107,13 +109,4 @@ class WhisperSocket extends EventEmitter { } } -WhisperSocket.bootstrap = function(web3) { - class WhisperSocketBootstrapped extends this { - constructor(user) { - super(user, web3) - } - } - return WhisperSocketBootstrapped -} - export default WhisperSocket \ No newline at end of file diff --git a/src/api/index.js b/src/api/index.js index 8050227..bf97f27 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -4,17 +4,18 @@ import Invitation from './Invitation' import Swarm from './Swarm' import Whisper from './WhisperSocket' import Accounts from './Accounts' +import Context from '../utils/di' export default (web3) => { - let InvitationBootstrapped = Invitation.bootstrap(web3) - let UserBootstrapped = User.bootstrap(web3, InvitationBootstrapped) - let UserRegistryBootstrapped = UserRegistry.bootstrap(web3, UserBootstrapped) - return { - Registry: UserRegistryBootstrapped, - User: UserBootstrapped, - Invitation: InvitationBootstrapped, - Swarm: Swarm.bootstrap(web3), - Whisper: Whisper.bootstrap(web3), - Accounts: Accounts.bootstrap(web3, UserRegistryBootstrapped.instance()) - } + let context = new Context() + context.addSingletonObject('Web3', web3) + + context.addClass('Invitation', Invitation) + context.addClass('User', User) + context.addClass('Accounts', Accounts, true) + context.addClass('UserRegistry', UserRegistry, true) + context.addClass('Swarm', Swarm, true) + context.addClass('Whisper', Whisper) + + return context.storage } \ No newline at end of file diff --git a/src/utils/di.js b/src/utils/di.js new file mode 100644 index 0000000..5f90ed1 --- /dev/null +++ b/src/utils/di.js @@ -0,0 +1,50 @@ + +class Context { + constructor() { + this.storage = {} + } + + addClass(name, injectable, singleton) { + const _context = this + const injected = class Injected extends injectable { + static context = _context + + constructor(...args) { + let injects = (injectable.$inject || []).map((inj) => _context.injected(inj)) + super(...(injects.concat(args))) + } + } + if (singleton) { + let _instance = null + + injected.instance = function() { + if (_instance) return _instance + return _instance = new this() + } + } + this.storage[name] = injected + if (typeof injected.injected === 'function') { + injected.injected() + } + } + + addConstant(name, value) { + this.storage[name] = value + } + + addSingletonObject(name, object) { + this.storage[name] = { instance: () => object } + } + + injected(name) { + var object = false + if (name.slice(-2) === '()') { + name = name.slice(0, -2) + object = true + } + var injectable = this.storage[name] + return object ? (typeof injectable.instance === 'function' ? injectable.instance() : new injectable()) : injectable + } +} + +export default Context \ No newline at end of file From 459aea97c36c069417df41c61449bb83009f3e9a Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Fri, 9 Feb 2018 16:15:26 +0200 Subject: [PATCH 07/13] Events in the contracts. Default GAS value. --- contracts/Invitation.sol | 5 +++ contracts/Migrations.sol | 4 +-- contracts/User.sol | 18 +++++++--- contracts/UserRegistry.sol | 9 +++-- migrations/1_initial_migration.js | 2 +- migrations/2_deploy_contracts.js | 19 +++++----- src/api/Accounts.js | 2 +- src/api/Invitation.js | 29 ++++++++++----- src/api/User.js | 59 ++++++++++++++++++++++++------- src/api/UserRegistry.js | 9 +++-- src/api/index.js | 1 + truffle.js | 7 +++- 12 files changed, 120 insertions(+), 44 deletions(-) diff --git a/contracts/Invitation.sol b/contracts/Invitation.sol index c33eb76..01a51a4 100644 --- a/contracts/Invitation.sol +++ b/contracts/Invitation.sol @@ -6,6 +6,9 @@ contract Invitation { User public inviter; User public invitee; + event InvitationAccepted(); + event InvitationRejected(); + modifier inviteeOnly() { require(msg.sender == address(invitee)); _ ; @@ -19,9 +22,11 @@ contract Invitation { function accept() public inviteeOnly { inviter.invitationAccepted(); + InvitationAccepted(); } function reject() public inviteeOnly { inviter.invitationRejected(); + InvitationRejected(); } } \ No newline at end of file diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol index 0a2399e..37bbf00 100644 --- a/contracts/Migrations.sol +++ b/contracts/Migrations.sol @@ -18,8 +18,8 @@ contract Migrations { last_completed_migration = completed; } - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); + function upgrade(address newAddress) public restricted { + Migrations upgraded = Migrations(newAddress); upgraded.setCompleted(last_completed_migration); } } diff --git a/contracts/User.sol b/contracts/User.sol index 4b13a55..5869c60 100644 --- a/contracts/User.sol +++ b/contracts/User.sol @@ -16,6 +16,11 @@ contract User { event UserProfileUpdated(); event WhisperInfoUpdated(); + event InvitationSent(Invitation invitation, User to); + event InvitationReceived(Invitation invitation, User from); + event ContactAdded(User contact); + event ContactRemoved(User contact); + event OwnerChanged(address from, address to); Profile private _profile; address private _owner; @@ -32,7 +37,7 @@ contract User { _ ; } - modifier iscontactOrOwner() { + modifier isContactOrOwner() { var found = false; for (uint i = 0; i < _contacts.length && !found; i++) { found = _contacts[i] == msg.sender; @@ -66,9 +71,10 @@ contract User { var oldOwner = _owner; _owner = newOwner; registry.ownerUpdated(oldOwner, newOwner); + OwnerChanged(oldOwner, newOwner); } - function getWhisperPubKey() public view iscontactOrOwner returns (string) { + function getWhisperPubKey() public view isContactOrOwner returns (string) { return _whisperInfo.pubKey; } @@ -102,6 +108,7 @@ contract User { } _contacts.push(contact); + ContactAdded(contact); } function removeContactPrivate(User contact) private { @@ -122,6 +129,7 @@ contract User { function removeContact(User contact) external onlyowner { removeContactPrivate(contact); contact.removeMe(); + ContactRemoved(contact); } function removeMe() public { @@ -129,9 +137,10 @@ contract User { User contact = User(msg.sender); removeContactPrivate(contact); + ContactRemoved(contact); } - function sendInvitation(User invitee) external onlyowner returns(Invitation) { + function sendInvitation(User invitee) external onlyowner { require(address(invitee) != 0x0); require(this != invitee); @@ -145,7 +154,7 @@ contract User { invitee.receiveInvitation(invitation); - return invitation; + InvitationSent(invitation, invitee); } function receiveInvitation(Invitation invitation) public { @@ -159,6 +168,7 @@ contract User { } _inbox.push(invitation); + InvitationReceived(invitation, invitation.inviter()); } //TODO: withdrawInvitation diff --git a/contracts/UserRegistry.sol b/contracts/UserRegistry.sol index afe3db1..4716c4d 100644 --- a/contracts/UserRegistry.sol +++ b/contracts/UserRegistry.sol @@ -5,15 +5,19 @@ import "./User.sol"; contract UserRegistry { mapping(address => User) private users; + event UserRegistered(User user); + event UserOwnerUpdated(User user, address newOwner); + function me() view external returns (User) { return users[msg.sender]; } - function register() external returns (User) { + function register() external { + require(msg.sender != 0x0); require(address(users[msg.sender]) == 0x0); var user = new User(msg.sender); users[msg.sender] = user; - return user; + UserRegistered(user); } function getUser(address walletId) public view returns (User) { @@ -28,5 +32,6 @@ contract UserRegistry { require(user == msg.sender); users[oldWalletId] = User(0x0); users[newWalletId] = user; + UserOwnerUpdated(user, newWalletId); } } \ No newline at end of file diff --git a/migrations/1_initial_migration.js b/migrations/1_initial_migration.js index 4d5f3f9..6e0a9f1 100644 --- a/migrations/1_initial_migration.js +++ b/migrations/1_initial_migration.js @@ -1,4 +1,4 @@ -var Migrations = artifacts.require("./Migrations.sol"); +var Migrations = artifacts.require("Migrations"); module.exports = function(deployer) { deployer.deploy(Migrations); diff --git a/migrations/2_deploy_contracts.js b/migrations/2_deploy_contracts.js index e25b8be..a6d7978 100644 --- a/migrations/2_deploy_contracts.js +++ b/migrations/2_deploy_contracts.js @@ -1,14 +1,13 @@ -var User = artifacts.require("./User.sol"); -var UserRegistry = artifacts.require("./UserRegistry.sol"); -var Invitation = artifacts.require("./Invitation.sol"); +var User = artifacts.require("User"); +var UserRegistry = artifacts.require("UserRegistry"); +var Invitation = artifacts.require("Invitation"); module.exports = function(deployer, network, accounts) { - return deployer.deploy(UserRegistry) - .then(function() { - return deployer.deploy(User, accounts[0]); - }) - .then(function() { - return deployer.deploy(Invitation, User.address, User.address); - }); + return deployer.deploy([ + [User, accounts[0]], + UserRegistry + ]).then(function() { + return deployer.deploy(Invitation, User.address, User.address) + }); }; diff --git a/src/api/Accounts.js b/src/api/Accounts.js index fb6c858..fcca172 100644 --- a/src/api/Accounts.js +++ b/src/api/Accounts.js @@ -31,7 +31,7 @@ class Accounts { } set currentAccount(walledAccountId) { - this._web3.eth.defaultAccount = walledAccountId; + this._web3.eth.defaultAccount = walledAccountId } } diff --git a/src/api/Invitation.js b/src/api/Invitation.js index c4cdc6b..de56466 100644 --- a/src/api/Invitation.js +++ b/src/api/Invitation.js @@ -1,15 +1,29 @@ import contract from 'truffle-contract' import InvitationContract from '../../build/contracts/Invitation.json' +import EventEmitter from 'events' -class Invitation { +class Invitation extends EventEmitter { static $inject = ['User', '_InvitationContract()'] constructor(User, Contract, invitationId, fromMe) { + super() this.id = invitationId this.fromMe = fromMe this._invitationContract = Contract.at(invitationId) this._User = User this._user = null + this.__setupEvents() + } + + __setupEvents() { + this._invitationContract.then((inv) => { + inv.InvitationAccepted().watch((err, response) => { + this.emit('accepted') + }) + inv.InvitationRejected().watch((err, response) => { + this.emit('rejected') + }) + }) } getUser() { @@ -19,17 +33,14 @@ class Invitation { .then((userId) => new this._User(userId)) } - accept() { - return this._invitationContract.then((invitation) => invitation.accept()) - } - - reject() { - return this._invitationContract.then((invitation) => invitation.reject()) - } - static injected(context) { const invitationContract = contract(InvitationContract) invitationContract.setProvider(this.context.injected('Web3()').currentProvider) + let defaults = invitationContract.defaults() + if (!defaults.gas) { + defaults.gas = this.context.injected('GAS') + invitationContract.defaults(defaults) + } this.context.addSingletonObject('_InvitationContract', invitationContract) } } diff --git a/src/api/User.js b/src/api/User.js index 89e17b9..2d19c9f 100644 --- a/src/api/User.js +++ b/src/api/User.js @@ -2,32 +2,49 @@ import contract from 'truffle-contract' import UserContract from '../../build/contracts/User.json' import EventEmitter from 'events' +const NULL_ID = '0x0000000000000000000000000000000000000000' + class User extends EventEmitter { static $inject = ['Invitation', '_UserContract()'] constructor (Invitation, UserContract, userId) { super() - this._userContract = UserContract.at(userId) this._Invitation = Invitation this.id = userId + this._sentInvitations = null + this._inboxInvitations = null + this._pubKey = null + this._profile = null + this._whisperInfo = null + this._contacts = null + this.__setupEvents() + } + + __setupEvents() { this._userContract.then((user) => { - user.UserProfileUpdated().watch((err, response) => { + user.UserProfileUpdated().watch((err, event) => { this._profile = null this.emit("profileUpdated") }) - user.WhisperInfoUpdated().watch((err, response) => { + user.WhisperInfoUpdated().watch((err, event) => { this._pubKey = null this._whisperInfo = null this.emit("whisperInfoUpdated") }) + user.ContactAdded().watch((err, event) => { + this.emit('contactAdded', new this.constructor(event.args.contact)) + }) + user.ContactRemoved().watch((err, event) => { + this.emit('contactRemoved', new this.constructor(event.args.contact)) + }) + user.OwnerChanged().watch((err, event) => { + this.emit('ownerChanged', event.to) + }) + user.InvitationReceived().watch((err, event) => { + this.emit('invitationReceived', new this._Invitation(event.invitation, false)) + }) }) - this._sentInvitations = null - this._inboxInvitations = null - this._pubKey = null - this._profile = null - this._whisperInfo = null - this._contacts = null } getWhisperInfo() { @@ -55,13 +72,26 @@ class User extends EventEmitter { invite(user) { return this._userContract - .then((user) => user.sendInvitation(user.id)) - .then((result) => { + .then((user) => user.sendInvitation(user.id).then((result) => !result.logs[0] ? null: result.logs[0].args.invitation)) + .then((invitationId) => { + if (!invitationId || invitationId === NULL_ID) throw new Error("Invitation failed") this._sentInvitations = null - return result + return new this._Invitation(invitationId, true) }) } + acceptInvitation(invitation) { + return this._userContract + .then((user) => user.acceptInvitation(invitation.id)) + .then(() => undefined) + } + + rejectInvitation(invitation) { + return this._userContract + .then((user) => user.rejectInvitation(invitation.id)) + .then(() => undefined) + } + getSentInvitations() { if (this._sentInvitations) return this._sentInvitations return this._sentInvitations = this._userContract @@ -86,6 +116,11 @@ class User extends EventEmitter { static injected() { const userContract = contract(UserContract) userContract.setProvider(this.context.injected('Web3()').currentProvider) + let defaults = userContract.defaults() + if (!defaults.gas) { + defaults.gas = this.context.injected('GAS') + userContract.defaults(defaults) + } this.context.addSingletonObject('_UserContract', userContract) } } diff --git a/src/api/UserRegistry.js b/src/api/UserRegistry.js index 4d29ad2..b7c52f6 100644 --- a/src/api/UserRegistry.js +++ b/src/api/UserRegistry.js @@ -24,9 +24,9 @@ class UserRegistry { register() { return this._userRegistryContract - .then((registry) => registry.register().then(() => registry.me())) + .then((registry) => registry.register()).then((result) => (!result.logs[0]) ? null : result.logs[0].args.user) .then((userId) => { - if (userId === NULL_ID) throw new Error("Registration failed") + if (!userId || userId === NULL_ID) throw new Error("Registration failed") return new this._User(userId) }) } @@ -43,6 +43,11 @@ class UserRegistry { static injected(context) { const userRegistryContract = contract(UserRegistryContract) userRegistryContract.setProvider(this.context.injected('Web3()').currentProvider) + let defaults = userRegistryContract.defaults() + if (!defaults.gas) { + defaults.gas = this.context.injected('GAS') + userRegistryContract.defaults(defaults) + } this.context.addSingletonObject('_UserRegistryContract', userRegistryContract) } } diff --git a/src/api/index.js b/src/api/index.js index bf97f27..fc3eb79 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -8,6 +8,7 @@ import Context from '../utils/di' export default (web3) => { let context = new Context() + context.addConstant('GAS', 3141592) context.addSingletonObject('Web3', web3) context.addClass('Invitation', Invitation) diff --git a/truffle.js b/truffle.js index afe2605..c9cc9a6 100644 --- a/truffle.js +++ b/truffle.js @@ -6,7 +6,12 @@ module.exports = { network_id: 4, host: "127.0.0.1", port: 8546 - } + }, + "development": { + host: "localhost", + port: 9545, + network_id: "*" // Match any network id + } }, rpc: { host: "127.0.0.1", From 553d07101e018753bda227921022fb03d3d410aa Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Fri, 9 Feb 2018 19:00:48 +0200 Subject: [PATCH 08/13] Better storage types --- contracts/User.sol | 16 ++++++++-------- src/api/User.js | 3 ++- src/api/WhisperSocket.js | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/contracts/User.sol b/contracts/User.sol index 5869c60..1a15c27 100644 --- a/contracts/User.sol +++ b/contracts/User.sol @@ -6,12 +6,12 @@ import "./Invitation.sol"; contract User { struct Profile { string name; - string avatar; + bytes32 avatar; //Swarm hash } struct WhisperInfo { - string pubKey; - string key; + bytes pubKey; + bytes key; } event UserProfileUpdated(); @@ -52,11 +52,11 @@ contract User { registry = UserRegistry(msg.sender); } - function getProfileInfo() public view returns (string name, string avatar) { + function getProfileInfo() public view returns (string name, bytes32 avatar) { return (_profile.name, _profile.avatar); } - function setProfileInfo(string name, string avatar) public onlyowner { + function setProfileInfo(string name, bytes32 avatar) public onlyowner { _profile.name = name; _profile.avatar = avatar; UserProfileUpdated(); @@ -74,15 +74,15 @@ contract User { OwnerChanged(oldOwner, newOwner); } - function getWhisperPubKey() public view isContactOrOwner returns (string) { + function getWhisperPubKey() public view isContactOrOwner returns (bytes) { return _whisperInfo.pubKey; } - function getWhisperInfo() public view onlyowner returns (string pubKey, string key) { + function getWhisperInfo() public view onlyowner returns (bytes pubKey, bytes key) { return (_whisperInfo.pubKey, _whisperInfo.key); } - function setWhisperInfo(string pubKey, string key) public onlyowner { + function setWhisperInfo(bytes pubKey, bytes key) public onlyowner { _whisperInfo.key = key; _whisperInfo.pubKey = pubKey; WhisperInfoUpdated(); diff --git a/src/api/User.js b/src/api/User.js index 2d19c9f..90b73e3 100644 --- a/src/api/User.js +++ b/src/api/User.js @@ -64,10 +64,11 @@ class User extends EventEmitter { getProfile() { if (this._profile) return this._profile return this._profile = this._userContract.then((user) => user.getProfileInfo()) + .then(([name, avatar]) => [name, avatar.slice(2)]) } setProfile(name, avatar) { - return this._userContract.then((user) => user.setProfileInfo(name, avatar)) + return this._userContract.then((user) => user.setProfileInfo(name, (avatar && avatar !== '') ? '0x'+avatar : avatar)) } invite(user) { diff --git a/src/api/WhisperSocket.js b/src/api/WhisperSocket.js index ea6ab15..36baefb 100644 --- a/src/api/WhisperSocket.js +++ b/src/api/WhisperSocket.js @@ -52,8 +52,16 @@ class WhisperSocket extends EventEmitter { } } + _encodeKey(key) { + return Promise.resolve((key && key !== '') ? '0x' + key : key) + } + + _decodeKey(key) { + return Promise.resolve(key.slice(2)) + } + _listen(pubKey, key) { - console.log("Listen") + console.log("Listen", pubKey, key) if (this._filter) throw new Error("Already listening" ) this._filter = this._shh.newMessageFilter({ @@ -77,6 +85,7 @@ class WhisperSocket extends EventEmitter { _updateIdentity() { return this._user.getWhisperInfo() + .then(([pubKey, key]) => this._decodeKey(key).then((decoded) => [pubKey, decoded])) .then(([pubKey, key]) => { return key !== '' ? promisify(this._shh, 'hasKeyPair')(key).then((has) => [key, pubKey, has]) : [key, pubKey, false] }) @@ -86,7 +95,7 @@ class WhisperSocket extends EventEmitter { .then(([id, pubKey, created]) => { if (created) { return promisify(this._shh, 'getPublicKey')(id).then((pubKey) => { - return this._user.setWhisperInfo(pubKey, id).then(() => [pubKey, id]) + return this._encodeKey(id).then((encoded) => this._user.setWhisperInfo(pubKey, encoded).then(() => [pubKey, id])) }) } return [pubKey, id] @@ -95,7 +104,8 @@ class WhisperSocket extends EventEmitter { sendMessage(to, message) { return to.getPubKey().then((pubKey) => { - return this._user.getWhisperInfo().then(([ownPubKey, ownKey]) => [ownKey, pubKey]) + return this._user.getWhisperInfo() + .then(([ownPubKey, ownKey]) => this._decodeKey(ownKey).then((decoded) => [decoded, pubKey])) }).then(([key, pubKey]) => { return promisify(this._shh, 'post')({ sig: key, From 12aea12814e0ed99c4f9ec272e3aef4d62c6e861 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Fri, 9 Feb 2018 19:09:34 +0200 Subject: [PATCH 09/13] Added binary data upload to swarm --- src/api/Swarm.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/api/Swarm.js b/src/api/Swarm.js index e061cc1..f1d6cac 100644 --- a/src/api/Swarm.js +++ b/src/api/Swarm.js @@ -12,6 +12,10 @@ class Swarm { return promisify(this._bzz, 'upload')({ pick: 'file' }) } + upload(data) { + return promisify(this._bzz, 'upload')(data) + } + download(hash) { return promisify(this._bzz, 'download')(hash) } From acc877b397e76fd3b9ddff2dfb8eb81d5e8675fa Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Sat, 10 Feb 2018 18:44:06 +0200 Subject: [PATCH 10/13] Added swarm emulation --- package.json | 5 ++++- src/api/Swarm.js | 19 +++++++++++++------ src/api/index.js | 4 +++- src/utils/getWeb3.js | 1 + 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 8a58ff8..f11551c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "recursive-readdir": "2.1.0", "strip-ansi": "3.0.1", "style-loader": "0.13.1", + "swarm-js": "^0.1.37", "truffle": "^4.0.6", "truffle-contract": "^3.0.3", "truffle-solidity-loader": "0.0.8", @@ -60,7 +61,9 @@ "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js --env=jsdom", - "truffle": "truffle" + "truffle": "truffle", + "swarm": "~/Library/Application\\ Support/Mist/swarmjs/bin/swarm -datadir ~/Library/Ethereum/rinkeby --ens-api ~/Library/Ethereum/rinkeby/geth.ipc --corsdomain '*' --bzzaccount $ACCOUNT", + "geth": "~/Library/Application\\ Support/Mist/binaries/Geth/unpacked/geth --rpc --shh --rpcport 9545 --rpccorsdomain '*' --rinkeby --rpcapi eth,web3,shh,net,swarm --unlock $ACCOUNT" }, "jest": { "collectCoverageFrom": [ diff --git a/src/api/Swarm.js b/src/api/Swarm.js index f1d6cac..481725b 100644 --- a/src/api/Swarm.js +++ b/src/api/Swarm.js @@ -1,23 +1,30 @@ import promisify from '../utils/promisify' +import swarm from 'swarm-js' class Swarm { - static $inject = ['Web3()'] + static $inject = ['Web3()', 'SWARM_API_URL'] - constructor(web3) { - this._bzz = web3.bzz + constructor(web3, url) { + if (web3.bzz) { + this._bzz = web3.bzz + this._emulated = false + } else { + this._bzz = swarm.at(url) + this._emulated = true + } } uploadFile() { - return promisify(this._bzz, 'upload')({ pick: 'file' }) + return this._emulated ? this._bzz.upload({ pick: 'file' }) : promisify(this._bzz, 'upload')({ pick: 'file' }) } upload(data) { - return promisify(this._bzz, 'upload')(data) + return this._emulated ? this._bzz.upload(data) : promisify(this._bzz, 'upload')(data) } download(hash) { - return promisify(this._bzz, 'download')(hash) + return this._emulated ? this._bzz.download(hash) : promisify(this._bzz, 'download')(hash) } } diff --git a/src/api/index.js b/src/api/index.js index fc3eb79..a6112f2 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -6,9 +6,11 @@ import Whisper from './WhisperSocket' import Accounts from './Accounts' import Context from '../utils/di' -export default (web3) => { +export default (web3, swarm_api_url = 'http://swarm-gateways.net') => { let context = new Context() context.addConstant('GAS', 3141592) + context.addConstant('SWARM_API_URL', swarm_api_url) + context.addSingletonObject('Web3', web3) context.addClass('Invitation', Invitation) diff --git a/src/utils/getWeb3.js b/src/utils/getWeb3.js index 8569d89..d24cc0b 100644 --- a/src/utils/getWeb3.js +++ b/src/utils/getWeb3.js @@ -19,6 +19,7 @@ let getWeb3 = new Promise(function(resolve, reject) { var provider = new Web3.providers.HttpProvider('http://127.0.0.1:9545') web3 = new Web3(provider) + web3.bzz = null // We can't work with fallback bzz in this version console.log('No web3 instance injected, using Local web3.'); From eb0bded6ee659b2c3d61b2825070f544bc6b5a4c Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Sat, 10 Feb 2018 18:49:01 +0200 Subject: [PATCH 11/13] removed not needed api --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f11551c..07ebaf9 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "test": "node scripts/test.js --env=jsdom", "truffle": "truffle", "swarm": "~/Library/Application\\ Support/Mist/swarmjs/bin/swarm -datadir ~/Library/Ethereum/rinkeby --ens-api ~/Library/Ethereum/rinkeby/geth.ipc --corsdomain '*' --bzzaccount $ACCOUNT", - "geth": "~/Library/Application\\ Support/Mist/binaries/Geth/unpacked/geth --rpc --shh --rpcport 9545 --rpccorsdomain '*' --rinkeby --rpcapi eth,web3,shh,net,swarm --unlock $ACCOUNT" + "geth": "~/Library/Application\\ Support/Mist/binaries/Geth/unpacked/geth --rpc --shh --rpcport 9545 --rpccorsdomain '*' --rinkeby --rpcapi eth,web3,shh,net --unlock $ACCOUNT" }, "jest": { "collectCoverageFrom": [ From 5c5417683b6f3d5a2c6bd14d772b152ed81237be Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Sun, 11 Feb 2018 13:56:04 +0200 Subject: [PATCH 12/13] attempt to fixuser bug --- src/api/UserRegistry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/UserRegistry.js b/src/api/UserRegistry.js index b7c52f6..e83c667 100644 --- a/src/api/UserRegistry.js +++ b/src/api/UserRegistry.js @@ -35,7 +35,7 @@ class UserRegistry { return this._userRegistryContract .then((registry) => registry.getUser(walletId)) .then((userId) => { - if (userId === NULL_ID) throw new Error('User not found') + if (userId === NULL_ID || userId === '0x') throw new Error('User not found') return new this._User(userId) }) } From 005e22cf4db860a02de3cbecf2f5339fb62c0620 Mon Sep 17 00:00:00 2001 From: Yehor Popovych Date: Tue, 13 Feb 2018 15:35:04 +0200 Subject: [PATCH 13/13] Quick fix --- src/api/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/User.js b/src/api/User.js index 90b73e3..b48a84c 100644 --- a/src/api/User.js +++ b/src/api/User.js @@ -73,7 +73,7 @@ class User extends EventEmitter { invite(user) { return this._userContract - .then((user) => user.sendInvitation(user.id).then((result) => !result.logs[0] ? null: result.logs[0].args.invitation)) + .then((contract) => contract.sendInvitation(user.id).then((result) => !result.logs[0] ? null: result.logs[0].args.invitation)) .then((invitationId) => { if (!invitationId || invitationId === NULL_ID) throw new Error("Invitation failed") this._sentInvitations = null