From 27c6e8f89278567f6833c20ec1740c6d695819a6 Mon Sep 17 00:00:00 2001 From: yamadapc <tacla.yamada@gmail.com> Date: Mon, 3 Oct 2016 09:09:08 -0300 Subject: [PATCH] Refactor the API - Add webpack build system - Send messages in queue - Assert that event listeners are cleaned-up (no memory-leaks) - Expose `Async` suffixed promise versions of the API - Use promises internally - Add tests --- .babelrc | 1 + .gitignore | 1 + lib/api-from-methods.js | 61 -- lib/api.js | 366 ++++++++---- lib/broadcast.js | 1169 ++++++++++++++++++++------------------- lib/browser.js | 2 +- lib/formatter.js | 63 ++- lib/util.js | 6 + package.json | 16 +- test/api.test.js | 139 ++++- test/test-post.json | 13 + test/test.html | 13 + webpack.config.js | 2 + webpack/makeConfig.js | 105 ++++ 14 files changed, 1159 insertions(+), 798 deletions(-) delete mode 100644 lib/api-from-methods.js create mode 100644 lib/util.js create mode 100644 test/test-post.json create mode 100644 test/test.html create mode 100644 webpack.config.js create mode 100644 webpack/makeConfig.js diff --git a/.babelrc b/.babelrc index 6b6a9ee..d70cb40 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,6 @@ { "presets": [ + "es2015", "es2017" ] } diff --git a/.gitignore b/.gitignore index f484e03..812a80e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ build/Release # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules .tern-port +dist diff --git a/lib/api-from-methods.js b/lib/api-from-methods.js deleted file mode 100644 index 8ce00d7..0000000 --- a/lib/api-from-methods.js +++ /dev/null @@ -1,61 +0,0 @@ -var methods = require('./methods.json'); - -var snakeCaseRe = /_([a-z])/g -function camelCase(str) { - return str.replace(snakeCaseRe, function (_m, l) { - return l.toUpperCase(); - }); -} - -exports = module.exports = function generateMethods(Steem) { - methods.reduce(function (memo, method) { - var methodName = camelCase(method.method); - - memo[methodName + 'With'] = - function Steem$specializedSendWith(options, callback) { - var params = method.params.map(function (param) { - return options[param]; - }); - var iterator = Steem.iterate(); - - return Steem.send(method.api, { - id: iterator, - method: method.method, - params: params, - }, function (err, data) { - if (err) return callback(err); - if (data && data.id === iterator) return callback(err, data.result); - // TODO - Do something here - }); - }; - - memo[methodName] = - function Steem$specializedSend() { - var args = arguments; - var options = method.params.reduce(function (memo, param, i) { - memo[param] = args[i]; - return memo; - }, {}); - var callback = args[method.params.length]; - memo[methodName + 'With'](options, callback); - }; - - return memo; - }, Steem); -}; - -/* - -console.log(exports); - -exports.getBlockWith({ - blockNum: 1, -}, function (err, operation) { - console.log(err, operation); -}); - -exports.getBlock(1, function (err, operation) { - console.log(err, operation); -}); - - */ diff --git a/lib/api.js b/lib/api.js index 131aaed..7fe31bd 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,151 +1,273 @@ -var isNode = require('detect-node'); -if (isNode) var WS = require('ws'); +import Debug from 'debug'; +import EventEmitter from 'events'; +import Promise from 'bluebird'; +import isNode from 'detect-node'; -var Steem = { +import methods from './methods'; +import {camelCase} from './util'; + +const debugEmitters = Debug('steemjs:emitters'); +const debugProtocol = Debug('steemjs:protocol'); +const debugSetup = Debug('steemjs:setup'); +const debugWs = Debug('steemjs:ws'); + +let WebSocket; +if (isNode) { + WebSocket = require('ws'); // eslint-disable-line global-require +} else if (typeof window !== 'undefined') { + WebSocket = window.WebSocket; +} else { + throw new Error('Couldn\'t decide on a `WebSocket` class'); +} + +const DEFAULTS = { url: 'wss://steemit.com/wspa', apiIds: { - 'database_api': 0, - 'login_api': 1, - 'follow_api': 2, - 'network_broadcast_api': 4 + database_api: 0, + login_api: 1, + follow_api: 2, + network_broadcast_api: 4 }, id: 0, - reqs: [], - isOpen: false, - isReady: false }; -Steem.setWebSocket = function(url) { - this.url = url; -}; +export class Steem extends EventEmitter { + constructor(options = {}) { + super(options); + Object.assign(options, DEFAULTS); + this.options = options; -Steem.init = function(callback) { - if (!this.isReady) { - if (isNode) { - this.ws = new WS(this.url); - this.ws.setMaxListeners(0); - } else { - this.ws = new WebSocket(this.url); - } - this.ws.addEventListener('close', function() { - this.ws.close(); - this.isReady = false; - this.isOpen = false; - }.bind(this)); - this.isReady = true; + this.id = 0; + this.currentP = Promise.fulfilled(); + this.apiIds = this.options.apiIds; + this.isOpen = false; + this.start(); } - if (!this.isOpen) { - this.ws.addEventListener('open', function() { - this.isOpen = true; - this.getApiByName('database_api', function() {}); - this.getApiByName('login_api', function() {}); - this.getApiByName('follow_api', function() {}); - this.getApiByName('network_broadcast_api', function() {}); - callback(); - }.bind(this)); - } else { - callback(); + + start() { + this.startP = new Promise((resolve /* , reject*/) => { + this.ws = new WebSocket(this.options.url); + this.releases = [ + this.listenTo(this.ws, 'open', () => { + debugWs('Opened WS connection with', this.options.url); + this.isOpen = true; + resolve(); + }), + this.listenTo(this.ws, 'close', () => { + debugWs('Closed WS connection with', this.options.url); + this.isOpen = false; + }), + this.listenTo(this.ws, 'message', (message) => { + debugWs('Received message', message.data); + this.emit('message', JSON.parse(message.data)); + }), + ]; + }); + this.apiIdsP = this.getApiIds(); + return this.startP; } -}; -Steem.iterate = function() { - this.id++; - var id = this.id; - this.reqs.push(id); - return id; -}; + stop() { + this.releases.forEach((release) => release()); + this.ws.removeEventListener(); + this.ws.close(); + delete this.ws; + delete this.releases; + } + + listenTo(target, eventName, callback) { + debugEmitters('Adding listener for', eventName, 'from', target.constructor.name); + if (target.addEventListener) target.addEventListener(eventName, callback); + else target.on(eventName, callback); -Steem.getApi = function(api, callback) { - if (this.apiIds[api] || this.apiIds[api] === 0) { - callback('', this.apiIds[api]); - } else { - this.getApiByName(api, function(err, result) { - this.apiIds[api] = result; - callback('', result); - }.bind(this)); + return () => { + debugEmitters('Removing listener for', eventName, 'from', target.constructor.name); + if (target.removeEventListener) target.removeEventListener(eventName, callback); + else target.removeListener(eventName, callback); + }; } -}; -Steem.send = function(api, data, callback) { - data.id = data.id || 0; - data.params = data.params || []; - this.init(function(){ - var call = {}; - call.id = data.id; - call.method = 'call'; - call.params = [this.apiIds[api], data.method, data.params]; - this.ws.send(JSON.stringify(call)); - }.bind(this)); - - this.ws.addEventListener('message', function(msg) { - var data = JSON.parse(msg.data); - var err = (data.error && data.error.data && data.error.data.stack)? data.error.data.stack : ''; - callback(err, data); - }.bind(this)); - - this.ws.addEventListener('error', function(error){ - callback(error, null); - }); -}; + getApiIds() { + return Promise.map(Object.keys(this.apiIds), (name) => { + debugSetup('Syncing API IDs', name); + return this.getApiByNameAsync(name).then((result) => { + this.apiIds[name] = result; + }); + }); + } + send(api, data, callback) { + const id = data.id || this.id++; + const currentP = this.currentP; + this.currentP = Promise.join(this.startP, currentP) + .then(() => new Promise((resolve, reject) => { + const payload = JSON.stringify({ + id, + method: 'call', + params: [ + this.apiIds[api], + data.method, + data.params, + ], + }); + + const release = this.listenTo(this, 'message', (message) => { + // We're still seeing old messages + if (message.id < id) { + debugProtocol('Old message was dropped', message); + return; + } + + release(); + + // We dropped a message + if (message.id !== id) { + debugProtocol('Response to RPC call was dropped', payload); + return; + } + + // Our message's response came back + const errorCause = data.error; + if (errorCause) { + const err = new Error(errorCause); + err.message = data; + reject(err); + return; + } + + debugProtocol('Resolved', id); + resolve(message.result); + }); -// [database_api] + debugWs('Sending message', payload); + this.ws.send(payload); + }) + .then( + (result) => callback(null, result), + (err) => callback(err) + )); -var generatedMethods = require('./api-from-methods'); -generatedMethods(Steem); + return this.currentP; + } + + streamBlockNumber(callback, ts = 200) { + let current = ''; + let running = true; -// [Stream] + const update = () => { + if (!running) return; -Steem.streamBlockNumber = function(callback) { - var current = ''; - var self = this; - setInterval(function() { - self.getDynamicGlobalProperties(function(err, result) { - var blockId = result.head_block_number; - if (blockId != current) { - current = blockId; - callback(null, current); + let result; + this.getDynamicGlobalPropertiesAsync() + .then((result) => { + const blockId = result.head_block_number; + if (blockId !== current) { + current = blockId; + callback(null, current); + } + + Promise.delay(ts).then(() => { + update(); + }); + }, (err) => { + callback(err); + }); + }; + + update(); + + return () => { + running = false; + }; + } + + streamBlock(callback) { + let current = ''; + let last = ''; + + const release = this.streamBlockNumber((err, id) => { + if (err) { + release(); + callback(err); + return; + } + + current = id; + if (current !== last) { + last = current; + this.getBlock(current, callback); } }); - }, 200); -}; -Steem.streamBlock = function(callback) { - var current = ''; - var last = ''; - var self = this; - this.streamBlockNumber(function(err, id) { - current = id; - if (current != last) { - last = current; - self.getBlock(current, function(err, result) { - callback(null, result); - }); - } - }); -}; + return release; + } + + streamTransactions(callback) { + const release = this.streamBlock((err, result) => { + if (err) { + release(); + callback(err); + return; + } -Steem.streamTransactions = function(callback) { - this.streamBlock(function(err, result) { - if (!!result) { - result.transactions.forEach(function(transaction) { + result.transactions.forEach((transaction) => { callback(null, transaction); }); - } - }) -}; + }); -Steem.streamOperations = function(callback) { - this.streamBlock(function(err, result) { - if (!!result) { - result.transactions.forEach(function(transaction) { - transaction.operations.forEach(function (operation) { - callback(null, operation); - }); + return release; + } + + streamOperations(callback) { + const release = this.streamTransactions((err, transaction) => { + if (err) { + release(); + callback(err); + return; + } + + transaction.operations.forEach(function (operation) { + callback(null, operation); }); - } - }) -}; + }); + + return release; + } +} + +// Generate Methods from methods.json +methods.reduce(function (memo, method) { + const methodName = camelCase(method.method); + const methodParams = method.params || []; + + memo[methodName + 'With'] = + function Steem$$specializedSendWith(options, callback) { + const params = methodParams.map(function (param) { + return options[param]; + }); + + return this.send(method.api, { + method: method.method, + params: params, + }, callback); + }; + + memo[methodName] = + function Steem$specializedSend(...args) { + const options = methodParams.reduce(function (memo, param, i) { + memo[param] = args[i]; + return memo; + }, {}); + const callback = args[methodParams.length]; + + return this[methodName + 'With'](options, callback); + }; + + return memo; +}, Steem.prototype); +Promise.promisifyAll(Steem.prototype); -module.exports = Steem; +// Export singleton instance +const steem = new Steem(); +export default steem; diff --git a/lib/broadcast.js b/lib/broadcast.js index 1b02bda..0c14b62 100644 --- a/lib/broadcast.js +++ b/lib/broadcast.js @@ -1,569 +1,608 @@ -var steemAuth = require('steemauth'), - steemApi = require('./api'); - formatter = require('./formatter'); +var steemAuth = require('steemauth'); +var steemApi = require('./api'); +var formatter = require('./formatter'); module.exports = { - send: function(tx, privKeys, callback) { - steemApi.login('', '', function() { - steemApi.getDynamicGlobalProperties(function(err, result) { - var seconds = 1000; - result.timestamp = result.timestamp || Date.now() - var expiration = new Date(result.timestamp + 15 * seconds); - tx.expiration = expiration.toISOString().replace('Z', ''); - tx.ref_block_num = result.head_block_number & 0xFFFF; - tx.ref_block_prefix = new Buffer(result.head_block_id, 'hex').readUInt32LE(4); - var signedTransaction = steemAuth.signTransaction(tx, privKeys); - steemApi.broadcastTransactionWithCallback(function(){}, signedTransaction, function(err, result) { - callback(err, result); - }); - }); - }); - }, - vote: function(wif, voter, author, permlink, weight, callback) { - var tx = { - extensions: [], - operations: [['vote', { - voter: voter, - author: author, - permlink: permlink, - weight: weight - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result); - }) - }, - upvote: function(wif, voter, author, permlink, weight, callback) { - weight = weight || 10000; - vote(wif, author, permlink, weight, function(err, result) { - callback(err, result); - }) - }, - downvote: function(wif, voter, author, permlink, weight, callback) { - weight = weight || 10000; - vote(wif, author, permlink, -Math.abs(weight), function(err, result) { - callback(err, result); - }) - }, - comment: function(wif, parentAuthor, parentPermlink, author, permlink, title, body, jsonMetadata, callback) { - permlink = permlink || formatter.commentPermlink(parentAuthor, parentPermlink); - var tx = { - extensions: [], - operations: [['comment', { - parent_author: parentAuthor, - parent_permlink: parentPermlink, - author: author, - permlink: permlink, - title: title, - body: body, - json_metadata: JSON.stringify(jsonMetadata) - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result); - }) - }, - transfer: function(wif, from, to, amount, memo, callback) { - var tx = { - extensions: [], - operations: [['transfer', { - from: from, - to: to, - amount: amount, - memo: memo - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result); - }) - }, - transferToVesting: function(wif, from, to, amount, callback) { - var tx = { - extensions: [], - operations: [['transfer_to_vesting', { - from: from, - to: to, - amount: amount - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result); - }) - }, - withdrawVesting: function(wif, account, vestingShares, callback) { - var tx = { - extensions: [], - operations: [['withdraw_vesting', { - account: account, - vesting_shares: vestingShares - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result); - }) - }, - limitOrderCreate: function(wif, owner, orderid, amountToSell, minToReceive, fillOrKill, expiration, callback) { - var tx = { - extensions: [], - operations:[['limit_order_create', { - owner: owner, - orderid: orderid, - amount_to_sell: amountToSell, - min_to_receive: minToReceive, - fill_or_kill: fillOrKill, - expiration: expiration - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - limitOrderCancel: function(wif, owner, orderid, callback) { - var tx = { - extensions: [], - operations:[['limit_order_cancel', { - owner: owner, - orderid: orderid - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - feedPublish: function(wif, publisher, exchangeRate, callback) { - var tx = { - extensions: [], - operations:[['feed_publish', { - publisher: publisher, - exchange_rate: exchangeRate - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - convert: function(wif, owner, requestid, amount, callback) { - var tx = { - extensions: [], - operations:[['convert', { - owner: owner, - requestid: requestid, - amount: amount - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - accountCreate: function(wif, fee, creator, newAccountName, owner, active, posting, memoKey, jsonMetadata, callback) { - var tx = { - extensions: [], - operations:[['account_create', { - fee: fee, - creator: creator, - new_account_name: newAccountName, - owner: owner, - active: active, - posting: posting, - memo_key: memoKey, - json_metadata: JSON.stringify(jsonMetadata) - }]] - }; - this.send(tx, {owner: wif}, function(err, result) { - callback(err, result) - }) - }, - accountUpdate: function(wif, account, owner, active, posting, memoKey, jsonMetadata, callback) { - var tx = { - extensions: [], - operations:[['account_update', { - account: account, - owner: owner, - active: active, - posting: posting, - memo_key: memoKey, - json_metadata: JSON.stringify(jsonMetadata) - }]] - }; - this.send(tx, {owner: wif}, function(err, result) { - callback(err, result) - }) - }, - witnessUpdate: function(wif, owner, url, blockSigningKey, props, fee, callback) { - var tx = { - extensions: [], - operations:[['witness_update', { - owner: owner, - url: url, - block_signing_key: blockSigningKey, - props: props, - fee: fee - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - accountWitnessVote: function(wif, account, witness, approve, callback) { - var tx = { - extensions: [], - operations:[['account_witness_vote', { - account: account, - witness: witness, - approve: approve - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - accountWitnessProxy: function(wif, account, proxy, callback) { - var tx = { - extensions: [], - operations:[['account_witness_proxy', { - account: account, - proxy: proxy - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - pow: function(wif, worker, input, signature, work, callback) { - var tx = { - extensions: [], - operations:[['pow', { - worker: worker, - input: input, - signature: signature, - work: work - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - custom: function(wif, requiredAuths, id, data, callback) { - var tx = { - extensions: [], - operations:[['custom', { - required_auths: requiredAuths, - id: id, - data: data - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - reportOverProduction: function(wif, reporter, firstBlock, secondBlock, callback) { - var tx = { - extensions: [], - operations:[['report_over_production', { - reporter: reporter, - first_block: firstBlock, - second_block: secondBlock - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - deleteComment: function(wif, author, permlink, callback) { - var tx = { - extensions: [], - operations:[['delete_comment', { - author: author, - permlink: permlink - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - customJson: function(wif, requiredAuths, requiredPostingAuths, id, json, callback) { - var tx = { - extensions: [], - operations:[['custom_json', { - required_auths: requiredAuths, - required_posting_auths: requiredPostingAuths, - id: id, - json: json - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - commentOptions: function(wif, author, permlink, maxAcceptedPayout, percentSteemDollars, allowVotes, allowCurationRewards, extensions, callback) { - var tx = { - extensions: [], - operations:[['comment_options', { - author: author, - permlink: permlink, - max_accepted_payout: maxAcceptedPayout, - percent_steem_dollars: percentSteemDollars, - allow_votes: allowVotes, - allow_curation_rewards: allowCurationRewards, - extensions: extensions - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - setWithdrawVestingRoute: function(wif, fromAccount, toAccount, percent, autoVest, callback) { - var tx = { - extensions: [], - operations:[['set_withdraw_vesting_route', { - from_account: fromAccount, - to_account: toAccount, - percent: percent, - auto_vest: autoVest - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - limitOrderCreate2: function(wif, owner, orderid, amountToSell, exchangeRate, fillOrKill, expiration, callback) { - var tx = { - extensions: [], - operations:[['limit_order_create2', { - owner: owner, - orderid: orderid, - amount_to_sell: amountToSell, - exchange_rate: exchangeRate, - fill_or_kill: fillOrKill, - expiration: expiration - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - challengeAuthority: function(wif, challenger, challenged, requireOwner, callback){ - var tx = { - extensions: [], - operations: [['challenge_authority', { - challenger: challenger, - challenged: challenged, - require_owner: requireOwner - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - proveAuthority: function(wif, challenged, requireOwner, callback){ - var tx = { - extensions: [], - operations: [['prove_authority', { - challenged: challenged, - require_owner: requireOwner - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - requestAccountRecovery: function(wif, recoveryAccount, accountToRecover, newOwnerAuthority, extensions, callback){ - var tx = { - extensions: [], - operations: [['request_account_recovery', { - recovery_account: recoveryAccount, - account_to_recover: accountToRecover, - new_owner_authority: newOwnerAuthority, - extensions: extensions - }]] - }; - this.send(tx, {owner: wif}, function(err, result) { - callback(err, result) - }) - }, - recoverAccount: function(wif, accountToRecover, newOwnerAuthority, recentOwnerAuthority, extensions, callback){ - var tx = { - extensions: [], - operations: [['recover_account', { - account_to_recover: accountToRecover, - new_owner_authority: newOwnerAuthority, - recent_owner_authority: recentOwnerAuthority, - extensions: extensions - }]] - }; - this.send(tx, {owner: wif}, function(err, result) { - callback(err, result) - }) - }, - changeRecoveryAccount: function(wif, accountToRecover, newRecoveryAccount, extensions, callback){ - var tx = { - extensions: [], - operations: [['change_recovery_account', { - account_to_recover: accountToRecover, - new_recovery_account: newRecoveryAccount, - extensions: extensions - }]] - }; - this.send(tx, {owner: wif}, function(err, result) { - callback(err, result) - }) - }, - escrowTransfer: function(wif, from, to, amount, memo, escrowId, agent, fee, jsonMeta, expiration, callback){ - var tx = { - extensions: [], - operations: [['escrow_transfer', { - from: from, - to: to, - amount: amount, - memo: memo, - escrow_id: escrowId, - agent: agent, - fee: fee, - json_meta: jsonMeta, - expiration: expiration - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - escrowDispute: function(wif, from, to, escrowId, who, callback){ - var tx = { - extensions: [], - operations: [['escrow_dispute', { - from: from, - to: to, - escrow_id: escrowId, - who: who - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - escrowRelease: function(wif, from, to, escrowId, who, amount, callback){ - var tx = { - extensions: [], - operations: [['escrow_release', { - from: from, - to: to, - escrow_id: escrowId, - who: who, - amount: amount - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - fillConvertRequest: function(wif, owner, requestid, amountIn, amountOut, callback){ - var tx = { - extensions: [], - operations: [['fill_convert_request', { - owner: owner, - requestid: requestid, - amount_in: amountIn, - amount_out: amountOut - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - commentReward: function(wif, author, permlink, sbdPayout, vestingPayout, callback){ - var tx = { - extensions: [], - operations: [['comment_reward', { - author: author, - permlink: permlink, - sbd_payout: sbdPayout, - vesting_payout: vestingPayout - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - curateReward: function(wif, curator, reward, commentAuthor, commentPermlink, callback){ - var tx = { - extensions: [], - operations: [['curate_reward', { - curator: curator, - reward: reward, - comment_author: commentAuthor, - comment_permlink: commentPermlink - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - liquidityReward: function(wif, owner, payout, callback){ - var tx = { - extensions: [], - operations: [['liquidity_reward', { - owner: owner, - payout: payout - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - interest: function(wif, owner, interest, callback){ - var tx = { - extensions: [], - operations: [['interest', { - owner: owner, - interest: interest - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - fillVestingWithdraw: function(wif, fromAccount, toAccount, withdrawn, deposited, callback){ - var tx = { - extensions: [], - operations: [['fill_vesting_withdraw', { - from_account: fromAccount, - to_account: toAccount, - withdrawn: withdrawn, - deposited: deposited - }]] - }; - this.send(tx, {active: wif}, function(err, result) { - callback(err, result) - }) - }, - fillOrder: function(wif, currentOwner, currentOrderid, currentPays, openOwner, openOrderid, openPays, callback){ - var tx = { - extensions: [], - operations: [['fill_order', { - current_owner: currentOwner, - current_orderid: currentOrderid, - current_pays: currentPays, - open_owner: openOwner, - open_orderid: openOrderid, - open_pays: openPays - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - }, - commentPayout: function(wif, author, permlink, payout, callback){ - var tx = { - extensions: [], - operations: [['comment_payout', { - author: author, - permlink: permlink, - payout: payout - }]] - }; - this.send(tx, {posting: wif}, function(err, result) { - callback(err, result) - }) - } + send: function(tx, privKeys, callback) { + steemApi.login('', '', function() { + steemApi.getDynamicGlobalProperties(function(err, result) { + var seconds = 1000; + result.timestamp = result.timestamp || Date.now() + var expiration = new Date(result.timestamp + 15 * seconds); + tx.expiration = expiration.toISOString().replace('Z', ''); + tx.ref_block_num = result.head_block_number & 0xFFFF; + tx.ref_block_prefix = new Buffer(result.head_block_id, 'hex').readUInt32LE(4); + var signedTransaction = steemAuth.signTransaction(tx, privKeys); + steemApi.broadcastTransactionWithCallback(function(){}, signedTransaction, function(err, result) { + callback(err, result); + }); + }); + }); + }, + vote: function(wif, voter, author, permlink, weight, callback) { + var tx = { + extensions: [], + operations: [['vote', { + voter: voter, + author: author, + permlink: permlink, + weight: weight + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result); + }) + }, + + upvote: function(wif, voter, author, permlink, weight, callback) { + weight = weight || 10000; + vote(wif, author, permlink, weight, function(err, result) { + callback(err, result); + }) + }, + + downvote: function(wif, voter, author, permlink, weight, callback) { + weight = weight || 10000; + vote(wif, author, permlink, -Math.abs(weight), function(err, result) { + callback(err, result); + }) + }, + + comment: function(wif, parentAuthor, parentPermlink, author, permlink, title, body, jsonMetadata, callback) { + permlink = permlink || formatter.commentPermlink(parentAuthor, parentPermlink); + var tx = { + extensions: [], + operations: [['comment', { + parent_author: parentAuthor, + parent_permlink: parentPermlink, + author: author, + permlink: permlink, + title: title, + body: body, + json_metadata: JSON.stringify(jsonMetadata) + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result); + }) + }, + + transfer: function(wif, from, to, amount, memo, callback) { + var tx = { + extensions: [], + operations: [['transfer', { + from: from, + to: to, + amount: amount, + memo: memo + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result); + }) + }, + + transferToVesting: function(wif, from, to, amount, callback) { + var tx = { + extensions: [], + operations: [['transfer_to_vesting', { + from: from, + to: to, + amount: amount + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result); + }) + }, + + withdrawVesting: function(wif, account, vestingShares, callback) { + var tx = { + extensions: [], + operations: [['withdraw_vesting', { + account: account, + vesting_shares: vestingShares + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result); + }) + }, + + limitOrderCreate: function(wif, owner, orderid, amountToSell, minToReceive, fillOrKill, expiration, callback) { + var tx = { + extensions: [], + operations:[['limit_order_create', { + owner: owner, + orderid: orderid, + amount_to_sell: amountToSell, + min_to_receive: minToReceive, + fill_or_kill: fillOrKill, + expiration: expiration + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + limitOrderCancel: function(wif, owner, orderid, callback) { + var tx = { + extensions: [], + operations:[['limit_order_cancel', { + owner: owner, + orderid: orderid + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + feedPublish: function(wif, publisher, exchangeRate, callback) { + var tx = { + extensions: [], + operations:[['feed_publish', { + publisher: publisher, + exchange_rate: exchangeRate + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + convert: function(wif, owner, requestid, amount, callback) { + var tx = { + extensions: [], + operations:[['convert', { + owner: owner, + requestid: requestid, + amount: amount + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + accountCreate: function(wif, fee, creator, newAccountName, owner, active, posting, memoKey, jsonMetadata, callback) { + var tx = { + extensions: [], + operations:[['account_create', { + fee: fee, + creator: creator, + new_account_name: newAccountName, + owner: owner, + active: active, + posting: posting, + memo_key: memoKey, + json_metadata: JSON.stringify(jsonMetadata) + }]] + }; + this.send(tx, {owner: wif}, function(err, result) { + callback(err, result) + }) + }, + + accountUpdate: function(wif, account, owner, active, posting, memoKey, jsonMetadata, callback) { + var tx = { + extensions: [], + operations:[['account_update', { + account: account, + owner: owner, + active: active, + posting: posting, + memo_key: memoKey, + json_metadata: JSON.stringify(jsonMetadata) + }]] + }; + this.send(tx, {owner: wif}, function(err, result) { + callback(err, result) + }) + }, + + witnessUpdate: function(wif, owner, url, blockSigningKey, props, fee, callback) { + var tx = { + extensions: [], + operations:[['witness_update', { + owner: owner, + url: url, + block_signing_key: blockSigningKey, + props: props, + fee: fee + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + accountWitnessVote: function(wif, account, witness, approve, callback) { + var tx = { + extensions: [], + operations:[['account_witness_vote', { + account: account, + witness: witness, + approve: approve + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + accountWitnessProxy: function(wif, account, proxy, callback) { + var tx = { + extensions: [], + operations:[['account_witness_proxy', { + account: account, + proxy: proxy + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + pow: function(wif, worker, input, signature, work, callback) { + var tx = { + extensions: [], + operations:[['pow', { + worker: worker, + input: input, + signature: signature, + work: work + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + custom: function(wif, requiredAuths, id, data, callback) { + var tx = { + extensions: [], + operations:[['custom', { + required_auths: requiredAuths, + id: id, + data: data + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + reportOverProduction: function(wif, reporter, firstBlock, secondBlock, callback) { + var tx = { + extensions: [], + operations:[['report_over_production', { + reporter: reporter, + first_block: firstBlock, + second_block: secondBlock + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + deleteComment: function(wif, author, permlink, callback) { + var tx = { + extensions: [], + operations:[['delete_comment', { + author: author, + permlink: permlink + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + customJson: function(wif, requiredAuths, requiredPostingAuths, id, json, callback) { + var tx = { + extensions: [], + operations:[['custom_json', { + required_auths: requiredAuths, + required_posting_auths: requiredPostingAuths, + id: id, + json: json + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + commentOptions: function(wif, author, permlink, maxAcceptedPayout, percentSteemDollars, allowVotes, allowCurationRewards, extensions, callback) { + var tx = { + extensions: [], + operations:[['comment_options', { + author: author, + permlink: permlink, + max_accepted_payout: maxAcceptedPayout, + percent_steem_dollars: percentSteemDollars, + allow_votes: allowVotes, + allow_curation_rewards: allowCurationRewards, + extensions: extensions + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + setWithdrawVestingRoute: function(wif, fromAccount, toAccount, percent, autoVest, callback) { + var tx = { + extensions: [], + operations:[['set_withdraw_vesting_route', { + from_account: fromAccount, + to_account: toAccount, + percent: percent, + auto_vest: autoVest + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + limitOrderCreate2: function(wif, owner, orderid, amountToSell, exchangeRate, fillOrKill, expiration, callback) { + var tx = { + extensions: [], + operations:[['limit_order_create2', { + owner: owner, + orderid: orderid, + amount_to_sell: amountToSell, + exchange_rate: exchangeRate, + fill_or_kill: fillOrKill, + expiration: expiration + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + challengeAuthority: function(wif, challenger, challenged, requireOwner, callback){ + var tx = { + extensions: [], + operations: [['challenge_authority', { + challenger: challenger, + challenged: challenged, + require_owner: requireOwner + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + proveAuthority: function(wif, challenged, requireOwner, callback){ + var tx = { + extensions: [], + operations: [['prove_authority', { + challenged: challenged, + require_owner: requireOwner + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + requestAccountRecovery: function(wif, recoveryAccount, accountToRecover, newOwnerAuthority, extensions, callback){ + var tx = { + extensions: [], + operations: [['request_account_recovery', { + recovery_account: recoveryAccount, + account_to_recover: accountToRecover, + new_owner_authority: newOwnerAuthority, + extensions: extensions + }]] + }; + this.send(tx, {owner: wif}, function(err, result) { + callback(err, result) + }) + }, + + recoverAccount: function(wif, accountToRecover, newOwnerAuthority, recentOwnerAuthority, extensions, callback){ + var tx = { + extensions: [], + operations: [['recover_account', { + account_to_recover: accountToRecover, + new_owner_authority: newOwnerAuthority, + recent_owner_authority: recentOwnerAuthority, + extensions: extensions + }]] + }; + this.send(tx, {owner: wif}, function(err, result) { + callback(err, result) + }) + }, + + changeRecoveryAccount: function(wif, accountToRecover, newRecoveryAccount, extensions, callback){ + var tx = { + extensions: [], + operations: [['change_recovery_account', { + account_to_recover: accountToRecover, + new_recovery_account: newRecoveryAccount, + extensions: extensions + }]] + }; + this.send(tx, {owner: wif}, function(err, result) { + callback(err, result) + }) + }, + + escrowTransfer: function(wif, from, to, amount, memo, escrowId, agent, fee, jsonMeta, expiration, callback){ + var tx = { + extensions: [], + operations: [['escrow_transfer', { + from: from, + to: to, + amount: amount, + memo: memo, + escrow_id: escrowId, + agent: agent, + fee: fee, + json_meta: jsonMeta, + expiration: expiration + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + escrowDispute: function(wif, from, to, escrowId, who, callback){ + var tx = { + extensions: [], + operations: [['escrow_dispute', { + from: from, + to: to, + escrow_id: escrowId, + who: who + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + escrowRelease: function(wif, from, to, escrowId, who, amount, callback){ + var tx = { + extensions: [], + operations: [['escrow_release', { + from: from, + to: to, + escrow_id: escrowId, + who: who, + amount: amount + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + fillConvertRequest: function(wif, owner, requestid, amountIn, amountOut, callback){ + var tx = { + extensions: [], + operations: [['fill_convert_request', { + owner: owner, + requestid: requestid, + amount_in: amountIn, + amount_out: amountOut + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + commentReward: function(wif, author, permlink, sbdPayout, vestingPayout, callback){ + var tx = { + extensions: [], + operations: [['comment_reward', { + author: author, + permlink: permlink, + sbd_payout: sbdPayout, + vesting_payout: vestingPayout + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + curateReward: function(wif, curator, reward, commentAuthor, commentPermlink, callback){ + var tx = { + extensions: [], + operations: [['curate_reward', { + curator: curator, + reward: reward, + comment_author: commentAuthor, + comment_permlink: commentPermlink + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + liquidityReward: function(wif, owner, payout, callback){ + var tx = { + extensions: [], + operations: [['liquidity_reward', { + owner: owner, + payout: payout + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + interest: function(wif, owner, interest, callback){ + var tx = { + extensions: [], + operations: [['interest', { + owner: owner, + interest: interest + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + fillVestingWithdraw: function(wif, fromAccount, toAccount, withdrawn, deposited, callback){ + var tx = { + extensions: [], + operations: [['fill_vesting_withdraw', { + from_account: fromAccount, + to_account: toAccount, + withdrawn: withdrawn, + deposited: deposited + }]] + }; + this.send(tx, {active: wif}, function(err, result) { + callback(err, result) + }) + }, + + fillOrder: function(wif, currentOwner, currentOrderid, currentPays, openOwner, openOrderid, openPays, callback){ + var tx = { + extensions: [], + operations: [['fill_order', { + current_owner: currentOwner, + current_orderid: currentOrderid, + current_pays: currentPays, + open_owner: openOwner, + open_orderid: openOrderid, + open_pays: openPays + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + }, + + commentPayout: function(wif, author, permlink, payout, callback){ + var tx = { + extensions: [], + operations: [['comment_payout', { + author: author, + permlink: permlink, + payout: payout + }]] + }; + this.send(tx, {posting: wif}, function(err, result) { + callback(err, result) + }) + } }; diff --git a/lib/browser.js b/lib/browser.js index ad353f7..8d6aaff 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -3,4 +3,4 @@ steem = { formatter: require('./formatter'), connect: require('steemconnect'), embed: require('steemembed') -}; \ No newline at end of file +}; diff --git a/lib/formatter.js b/lib/formatter.js index 6738cb9..9f88e52 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -1,31 +1,34 @@ module.exports = { - reputation: function (reputation) { - if (reputation == null) return reputation; - reputation = parseInt(reputation); - var rep = String(reputation); - var neg = rep.charAt(0) === '-'; - rep = neg ? rep.substring(1) : rep; - var str = rep; - var leadingDigits = parseInt(str.substring(0, 4)); - var log = Math.log(leadingDigits) / Math.log(10); - var n = str.length - 1; - var out = n + (log - parseInt(log)); - if (isNaN(out)) out = 0; - out = Math.max(out - 9, 0); - out = (neg ? -1 : 1) * out; - out = (out * 9) + 25; - out = parseInt(out); - return out; - }, - vestToSteem: function(vestingShares, totalVestingShares, totalVestingFundSteem) { - return parseFloat(totalVestingFundSteem) * (parseFloat(vestingShares) / parseFloat(totalVestingShares)); - }, - commentPermlink: function(parentAuthor, parentPermlink) { - var timeStr = new Date().toISOString().replace(/[^a-zA-Z0-9]+/g, ''); - parentPermlink = parentPermlink.replace(/(-\d{8}t\d{9}z)/g, ''); - return 're-' + parentAuthor + '-' + parentPermlink + '-' + timeStr; - }, - amount: function(amount, asset) { - return amount.toFixed(3) + ' ' + asset; - } -}; \ No newline at end of file + reputation: function (reputation) { + if (reputation == null) return reputation; + reputation = parseInt(reputation); + var rep = String(reputation); + var neg = rep.charAt(0) === '-'; + rep = neg ? rep.substring(1) : rep; + var str = rep; + var leadingDigits = parseInt(str.substring(0, 4)); + var log = Math.log(leadingDigits) / Math.log(10); + var n = str.length - 1; + var out = n + (log - parseInt(log)); + if (isNaN(out)) out = 0; + out = Math.max(out - 9, 0); + out = (neg ? -1 : 1) * out; + out = (out * 9) + 25; + out = parseInt(out); + return out; + }, + + vestToSteem: function(vestingShares, totalVestingShares, totalVestingFundSteem) { + return parseFloat(totalVestingFundSteem) * (parseFloat(vestingShares) / parseFloat(totalVestingShares)); + }, + + commentPermlink: function(parentAuthor, parentPermlink) { + var timeStr = new Date().toISOString().replace(/[^a-zA-Z0-9]+/g, ''); + parentPermlink = parentPermlink.replace(/(-\d{8}t\d{9}z)/g, ''); + return 're-' + parentAuthor + '-' + parentPermlink + '-' + timeStr; + }, + + amount: function(amount, asset) { + return amount.toFixed(3) + ' ' + asset; + } +}; diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 0000000..ae9f87d --- /dev/null +++ b/lib/util.js @@ -0,0 +1,6 @@ +const snakeCaseRe = /_([a-z])/g; +export function camelCase(str) { + return str.replace(snakeCaseRe, function (_m, l) { + return l.toUpperCase(); + }); +} diff --git a/package.json b/package.json index a33328c..fb41034 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Steem.js the JavaScript API for Steem blockchain", "main": "index.js", "scripts": { - "test": "mocha --require babel-register", - "build": "browserify lib/browser.js -o examples/steem.js && uglifyjs examples/steem.js -o examples/steem.min.js && uglifyjs examples/steem.js -o steem.min.js" + "test": "mocha -t 10000 --require babel-polyfill --require babel-register", + "build": "rm -rf dist && NODE_ENV=production webpack && gzip -k -f ./dist/*.js && du -h ./dist/*" }, "browser": { "ws": false @@ -27,6 +27,8 @@ }, "homepage": "https://github.com/adcpm/steem#readme", "dependencies": { + "bluebird": "^3.4.6", + "debug": "^2.2.0", "detect-node": "^2.0.3", "steemauth": "0.0.16", "steemconnect": "0.0.25", @@ -34,16 +36,20 @@ "ws": "^1.1.1" }, "devDependencies": { - "babel-polyfill": "^6.13.0", - "babel-preset-es2017": "^6.14.0", + "babel-loader": "^6.2.5", + "babel-polyfill": "^6.16.0", + "babel-preset-es2015": "^6.16.0", + "babel-preset-es2017": "^6.16.0", "babel-register": "^6.14.0", "bluebird": "^3.4.6", "browserify": "^13.0.1", "bufferutil": "^1.2.1", + "json-loader": "^0.5.4", "mocha": "^3.0.2", "should": "^11.1.0", "uglifyjs": "^2.4.10", - "utf-8-validate": "^1.2.1" + "utf-8-validate": "^1.2.1", + "webpack-visualizer-plugin": "^0.1.5" }, "contributors": [ "Fabien (https://github.com/adcpm)", diff --git a/test/api.test.js b/test/api.test.js index f8ea5eb..796cf6d 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -1,24 +1,135 @@ -const Promise = require('bluebird'); -const should = require('should'); +import Promise from 'bluebird'; +import should from 'should'; -const Steem = require('..'); -Promise.promisifyAll(Steem.api); +import steem from '../lib/api'; +import testPost from './test-post.json'; + +describe('steem', function () { + this.timeout(10000); + + before(async () => { + await steem.apiIdsP; + }); + + describe('getFollowers', () => { + describe('getting ned\'s followers', () => { + it('works', async () => { + const result = await steem.getFollowersAsync('ned', 0, 'blog', 5); + result.should.have.lengthOf(5); + }); + + it('clears listeners', async () => { + steem.listeners('message').should.have.lengthOf(0); + }); + }); + }); + + describe('getContent', () => { + describe('getting a random post', () => { + it('works', async () => { + const result = await steem.getContentAsync('yamadapc', 'test-1-2-3-4-5-6-7-9'); + result.should.have.properties(testPost); + }); + + it('clears listeners', async () => { + steem.listeners('message').should.have.lengthOf(0); + }); + }); + }); -describe('steem', () => { describe('getFollowers', () => { describe('getting ned\'s followers', () => { it('works', async () => { - const result = await Steem.api.getFollowersAsync('ned', 0, 'blog', 5) - result.should.have.lengthOf(5); + const result = await steem.getFollowersAsync('ned', 0, 'blog', 5); + result.should.have.lengthOf(5); + }); + }); + }); + + describe('streamBlockNumber', () => { + it('streams steem transactions', (done) => { + let i = 0; + const release = steem.streamBlockNumber((err, block) => { + should.exist(block); + block.should.be.instanceOf(Number); + i++; + if (i === 2) { + release(); + done(); + } + }); + }); + }); + + describe('streamBlock', () => { + it('streams steem blocks', (done) => { + let i = 0; + const release = steem.streamBlock((err, block) => { + try { + should.exist(block); + block.should.have.properties([ + 'previous', + 'transactions', + 'timestamp', + ]); + } catch (err) { + release(); + done(err); + return; + } + + i++; + if (i === 2) { + release(); + done(); + } }); + }); + }); + + describe('streamTransactions', () => { + it('streams steem transactions', (done) => { + let i = 0; + const release = steem.streamTransactions((err, transaction) => { + try { + should.exist(transaction); + transaction.should.have.properties([ + 'ref_block_num', + 'operations', + 'extensions', + ]); + } catch (err) { + release(); + done(err); + return; + } + + i++; + if (i === 2) { + release(); + done(); + } + }); + }); + }); + + describe('streamOperations', () => { + it('streams steem operations', (done) => { + let i = 0; + const release = steem.streamOperations((err, operation) => { + try { + should.exist(operation); + } catch (err) { + release(); + done(err); + return; + } - it.skip('the startFollower parameter has an impact on the result', async () => { - // Get the first 5 - const result1 = await Steem.api.getFollowersAsync('ned', 0, 'blog', 5) - result1.should.have.lengthOf(5); - const result2 = await Steem.api.getFollowersAsync('ned', 5, 'blog', 5) - result2.should.have.lengthOf(5); - result1.should.not.be.eql(result2); + i++; + if (i === 2) { + release(); + done(); + } }); }); }); diff --git a/test/test-post.json b/test/test-post.json new file mode 100644 index 0000000..3968069 --- /dev/null +++ b/test/test-post.json @@ -0,0 +1,13 @@ +{ + "author": "yamadapc", + "permlink": "test-1-2-3-4-5-6-7-9", + "category": "test", + "parent_author": "", + "parent_permlink": "test", + "title": "test-1-2-3-4-5-6-7-9", + "body": "<script>alert('hello world')</script>", + "allow_replies": true, + "allow_votes": true, + "allow_curation_rewards": true, + "url": "/test/@yamadapc/test-1-2-3-4-5-6-7-9" +} diff --git a/test/test.html b/test/test.html new file mode 100644 index 0000000..f771c7f --- /dev/null +++ b/test/test.html @@ -0,0 +1,13 @@ +<link href="../node_modules/mocha/mocha.css" rel="stylesheet" /> + +<div id="mocha"></div> + +<script src="../node_modules/mocha/mocha.js"></script> +<script>mocha.setup('bdd')</script> +<script src="../dist/steemjs-tests.min.js"></script> + + +<script> + mocha.checkLeaks(); + mocha.run(); +</script> diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..e3b3ddb --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,2 @@ +const makeConfig = require('./webpack/makeConfig'); +exports = module.exports = makeConfig(); diff --git a/webpack/makeConfig.js b/webpack/makeConfig.js new file mode 100644 index 0000000..8ea821f --- /dev/null +++ b/webpack/makeConfig.js @@ -0,0 +1,105 @@ +'use strict'; +const Visualizer = require('webpack-visualizer-plugin'); +const _ = require('lodash'); +const path = require('path'); +const webpack = require('webpack'); + +const DEFAULTS = { + isDevelopment: process.env.NODE_ENV !== 'production', + baseDir: path.join(__dirname, '..'), +}; + +function makePlugins(options) { + const isDevelopment = options.isDevelopment; + + let plugins = [ + new Visualizer({ + filename: './statistics.html' + }), + ]; + + if (!isDevelopment) { + plugins = plugins.concat([ + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin({ + minimize: true, + compress: { + warnings: false, + } + }), + new webpack.optimize.AggressiveMergingPlugin(), + ]); + } + + return plugins; +} + +function makeStyleLoaders(options) { + if (options.isDevelopment) { + return [ + { + test: /\.s[ac]ss$/, + loaders: [ + 'style', + 'css?sourceMap', + 'autoprefixer-loader?browsers=last 2 version', + 'sass?sourceMap&sourceMapContents', + ], + }, + ]; + } + + return [ + { + test: /\.s[ac]ss$/, + loader: ExtractTextPlugin.extract( + 'style-loader', + 'css!autoprefixer-loader?browsers=last 2 version!sass' + ), + }, + ]; +} + +function makeConfig(options) { + if (!options) options = {}; + _.defaults(options, DEFAULTS); + + const isDevelopment = options.isDevelopment; + + return { + devtool: isDevelopment ? 'cheap-eval-source-map' : 'source-map', + entry: isDevelopment ? { + steemjs: path.join(options.baseDir, 'lib/browser.js'), + 'steemjs-tests': path.join(options.baseDir, 'test/api.test.js'), + } : { + steemjs: path.join(options.baseDir, 'lib/browser.js'), + }, + output: { + path: path.join(options.baseDir, 'dist'), + filename: '[name].min.js', + }, + plugins: makePlugins(options), + module: { + loaders: [ + { + test: /\.js?$/, + exclude: /node_modules/, + loader: 'babel', + }, + { + test: /\.json?$/, + loader: 'json', + }, + ], + }, + }; +} + +if (!module.parent) { + console.log(makeConfig({ + isDevelopment: process.env.NODE_ENV !== 'production', + })); +} + +exports = module.exports = makeConfig; +exports.DEFAULTS = DEFAULTS; -- GitLab