diff --git a/config.json b/config.json index 7cc13784..994a7fda 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,9 @@ { + "transport": "ws", "websocket": "wss://steemd.steemit.com", + "uri": "https://steemd.steemit.com", + "dev_uri": "https://steemd.steemitdev.com", + "stage_uri": "https://steemd.steemitstage.com", "address_prefix": "STM", "chain_id": "0000000000000000000000000000000000000000000000000000000000000000" } diff --git a/doc/README.md b/doc/README.md index 7409d56b..b98317ee 100644 --- a/doc/README.md +++ b/doc/README.md @@ -3,6 +3,7 @@ - [Install](#install) - [Browser](#browser) - [Config](#config) +- [JSON-RPC](#jsonrpc) - [Database API](#api) - [Subscriptions](#subscriptions) - [Tags](#tags) @@ -54,6 +55,16 @@ steem.config.set('address_prefix','STM'); steem.config.get('chain_id'); ``` +## JSON-RPC +Here is how to activate JSON-RPC transport: +```js +steem.api.setOptions({ + transport: 'http', + uri: 'https://steemd.steemitdev.com' // Optional, by default https://steemd.steemit.com is used. +}); + +``` + # API ## Subscriptions @@ -517,12 +528,6 @@ steem.api.getFollowCount(account, function(err, result) { ## Broadcast API -### Broadcast Transaction -``` -steem.api.broadcastTransaction(trx, function(err, result) { - console.log(err, result); -}); -``` ### Broadcast Transaction Synchronous ``` steem.api.broadcastTransactionSynchronous(trx, function(err, result) { @@ -535,12 +540,7 @@ steem.api.broadcastBlock(b, function(err, result) { console.log(err, result); }); ``` -### Broadcast Transaction With Callback -``` -steem.api.broadcastTransactionWithCallback(confirmationCallback, trx, function(err, result) { - console.log(err, result); -}); -``` + # Broadcast ### Account Create diff --git a/package.json b/package.json index 04693c66..1945cab5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "steem", - "version": "0.5.20", + "version": "0.6.0", "description": "Steem.js the JavaScript API for Steem blockchain", "main": "lib/index.js", "scripts": { @@ -45,9 +45,10 @@ "debug": "^2.6.8", "detect-node": "^2.0.3", "ecurve": "^1.0.5", + "isomorphic-fetch": "^2.2.1", "lodash": "^4.16.4", "secure-random": "^1.1.1", - "ws": "^1.1.1" + "ws": "^3.0.0" }, "devDependencies": { "babel-cli": "^6.16.0", diff --git a/src/api/index.js b/src/api/index.js index 14cdb292..1d4cd0fd 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -1,255 +1,66 @@ import EventEmitter from 'events'; import Promise from 'bluebird'; -import cloneDeep from 'lodash/cloneDeep'; -import defaults from 'lodash/defaults'; -import isNode from 'detect-node'; -import newDebug from 'debug'; import config from '../config'; import methods from './methods'; -import { camelCase } from '../utils'; - -const debugEmitters = newDebug('steem:emitters'); -const debugProtocol = newDebug('steem:protocol'); -const debugSetup = newDebug('steem:setup'); -const debugApiIds = newDebug('steem:api_ids'); -const debugWs = newDebug('steem: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 = { - apiIds: { - database_api: 0, - login_api: 1, - follow_api: 2, - network_broadcast_api: 4, - }, - id: 0, -}; - -const expectedResponseMs = process.env.EXPECTED_RESPONSE_MS || 2000; +import transports from './transports'; +import {camelCase} from '../utils'; +import {hash} from '../auth/ecc'; +import {ops} from '../auth/serializer'; class Steem extends EventEmitter { constructor(options = {}) { super(options); - defaults(options, DEFAULTS); - this.options = cloneDeep(options); - - this.id = 0; - this.inFlight = 0; - this.currentP = Promise.fulfilled(); - this.apiIds = this.options.apiIds; - this.isOpen = false; - this.releases = []; - this.requests = {}; - - // A Map of api name to a promise to it's API ID refresh call - this.apiIdsP = {}; + this._setTransport(options); + this.options = options; } - setWebSocket(url) { - console.warn("steem.api.setWebSocket(url) is now deprecated instead use steem.config.set('websocket',url)"); - debugSetup('Setting WS', url); - config.set('websocket', url); - this.stop(); - } - - start() { - if (this.startP) { - return this.startP; - } - - const startP = new Promise((resolve, reject) => { - if (startP !== this.startP) return; - const url = config.get('websocket'); - this.ws = new WebSocket(url); - - const releaseOpen = this.listenTo(this.ws, 'open', () => { - debugWs('Opened WS connection with', url); - this.isOpen = true; - releaseOpen(); - resolve(); - }); - - const releaseClose = this.listenTo(this.ws, 'close', () => { - debugWs('Closed WS connection with', url); - this.isOpen = false; - delete this.ws; - this.stop(); + _setTransport(options) { + if (options.transport) { + if (this.transport && this._transportType !== options.transport) { + this.transport.stop(); + } - if (startP.isPending()) { - reject(new Error( - 'The WS connection was closed before this operation was made' - )); - } - }); + this._transportType = options.transport; - const releaseMessage = this.listenTo(this.ws, 'message', (message) => { - debugWs('Received message', message.data); - const data = JSON.parse(message.data); - const id = data.id; - const request = this.requests[id]; - if (!request) { - debugWs('Steem.onMessage error: unknown request ', id); - return; + if (typeof options.transport === 'string') { + if (!transports[options.transport]) { + throw new TypeError( + 'Invalid `transport`, valid values are `http`, `ws` or a class', + ); } - delete this.requests[id]; - this.onMessage(data, request); - }); - - this.releases = this.releases.concat([ - releaseOpen, - releaseClose, - releaseMessage, - ]); - }); - - this.startP = startP; - this.getApiIds(); + this.transport = new transports[options.transport](options); + } else { + this.transport = new options.transport(options); + } + } else { + this.transport = new transports.ws(options); + } + } - return startP; + start() { + return this.transport.start(); } stop() { - debugSetup('Stopping...'); - if (this.ws) this.ws.close(); - this.apiIdsP = {}; - delete this.startP; - delete this.ws; - this.releases.forEach((release) => release()); - this.releases = []; + return this.transport.stop(); } - 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); - - return () => { - debugEmitters('Removing listener for', eventName, 'from', target.constructor.name); - if (target.removeEventListener) target.removeEventListener(eventName, callback); - else target.removeListener(eventName, callback); - }; + send(api, data, callback) { + return this.transport.send(api, data, callback); } - /** - * Refreshes API IDs, populating the `Steem::apiIdsP` map. - * - * @param {String} [requestName] If provided, only this API will be refreshed - * @param {Boolean} [force] If true the API will be forced to refresh, ignoring existing results - */ - - getApiIds(requestName, force) { - if (!force && requestName && this.apiIdsP[requestName]) { - return this.apiIdsP[requestName]; - } - - const apiNamesToRefresh = requestName ? [requestName] : Object.keys(this.apiIds); - apiNamesToRefresh.forEach((name) => { - debugApiIds('Syncing API ID', name); - this.apiIdsP[name] = this.getApiByNameAsync(name).then((result) => { - if (result != null) { - this.apiIds[name] = result; - } else { - debugApiIds('Dropped null API ID for', name, result); - } - }); - }); - - // If `requestName` was provided, only wait for this API ID - if (requestName) { - return this.apiIdsP[requestName]; - } - - // Otherwise wait for all of them - return Promise.props(this.apiIdsP); + setOptions(options) { + Object.assign(this.options, options); + this._setTransport(this.options); + this.transport.setOptions(this.options); } - - onMessage(message, request) { - const {api, data, resolve, reject, start_time} = request; - debugWs('-- Steem.onMessage -->', message.id); - const errorCause = message.error; - if (errorCause) { - const err = new Error( - // eslint-disable-next-line prefer-template - (errorCause.message || 'Failed to complete operation') + - ' (see err.payload for the full error payload)' - ); - err.payload = message; - reject(err); - return; - } - - if (api === 'login_api' && data.method === 'login') { - debugApiIds( - 'network_broadcast_api API ID depends on the WS\' session. ' + - 'Triggering a refresh...' - ); - this.getApiIds('network_broadcast_api', true); - } - - debugProtocol('Resolved', api, data, '->', message); - this.emit('track-performance', data.method, Date.now() - start_time); - delete this.requests[message.id]; - resolve(message.result); + setWebSocket(url) { + this.setOptions({websocket: url}); } - send(api, data, callback) { - debugSetup('Steem::send', api, data); - const id = data.id || this.id++; - const startP = this.start(); - - const apiIdsP = api === 'login_api' && data.method === 'get_api_by_name' - ? Promise.fulfilled() - : this.getApiIds(api); - - if (api === 'login_api' && data.method === 'get_api_by_name') { - debugApiIds('Sending setup message'); - } else { - debugApiIds('Going to wait for setup messages to resolve'); - } - - this.currentP = Promise.join(startP, apiIdsP) - .then(() => new Promise((resolve, reject) => { - if (!this.ws) { - reject(new Error( - 'The WS connection was closed while this request was pending' - )); - return; - } - - const payload = JSON.stringify({ - id, - method: 'call', - params: [ - this.apiIds[api], - data.method, - data.params, - ], - }); - - debugWs('Sending message', payload); - this.requests[id] = { - api, - data, - resolve, - reject, - start_time: Date.now() - }; - - // this.inFlight += 1; - this.ws.send(payload); - })) - .nodeify(callback); - - return this.currentP; + setUri(url) { + this.setOptions({uri: url}); } streamBlockNumber(mode = 'head', callback, ts = 200) { @@ -263,8 +74,8 @@ class Steem extends EventEmitter { const update = () => { if (!running) return; - this.getDynamicGlobalPropertiesAsync() - .then((result) => { + this.getDynamicGlobalPropertiesAsync().then( + result => { const blockId = mode === 'irreversible' ? result.last_irreversible_block_num : result.head_block_number; @@ -286,9 +97,11 @@ class Steem extends EventEmitter { Promise.delay(ts).then(() => { update(); }); - }, (err) => { + }, + err => { callback(err); - }); + }, + ); }; update(); @@ -338,7 +151,7 @@ class Steem extends EventEmitter { } if (result && result.transactions) { - result.transactions.forEach((transaction) => { + result.transactions.forEach(transaction => { callback(null, transaction); }); } @@ -360,7 +173,7 @@ class Steem extends EventEmitter { return; } - transaction.operations.forEach((operation) => { + transaction.operations.forEach(operation => { callback(null, operation); }); }); @@ -370,35 +183,71 @@ class Steem extends EventEmitter { } // Generate Methods from methods.json -methods.forEach((method) => { +methods.forEach(method => { const methodName = method.method_name || camelCase(method.method); const methodParams = method.params || []; - Steem.prototype[`${methodName}With`] = - function Steem$$specializedSendWith(options, callback) { - const params = methodParams.map((param) => options[param]); - return this.send(method.api, { + Steem.prototype[`${methodName}With`] = function Steem$$specializedSendWith( + options, + callback, + ) { + const params = methodParams.map(param => options[param]); + return this.send( + method.api, + { method: method.method, params, - }, callback); - }; - - Steem.prototype[methodName] = - function Steem$specializedSend(...args) { - const options = methodParams.reduce((memo, param, i) => { - memo[param] = args[i]; // eslint-disable-line no-param-reassign - return memo; - }, {}); - const callback = args[methodParams.length]; - - return this[`${methodName}With`](options, callback); - }; + }, + callback, + ); + }; + + Steem.prototype[methodName] = function Steem$specializedSend(...args) { + const options = methodParams.reduce((memo, param, i) => { + memo[param] = args[i]; // eslint-disable-line no-param-reassign + return memo; + }, {}); + const callback = args[methodParams.length]; + return this[`${methodName}With`](options, callback); + }; }); +/** + * Wrap transaction broadcast: serializes the object and adds error reporting + */ + +Steem.prototype.broadcastTransactionSynchronousWith = function Steem$$specializedSendWith( + options, + callback, +) { + const trx = options.trx; + return this.send( + 'network_broadcast_api', + { + method: 'broadcast_transaction_synchronous', + params: [trx], + }, + (err, result) => { + if (err) { + const {signed_transaction} = ops; + // console.log('-- broadcastTransactionSynchronous -->', JSON.stringify(signed_transaction.toObject(trx), null, 2)); + // toObject converts objects into serializable types + const trObject = signed_transaction.toObject(trx); + const buf = signed_transaction.toBuffer(trx); + err.digest = hash.sha256(buf).toString('hex'); + err.transaction_id = buf.toString('hex'); + err.transaction = JSON.stringify(trObject); + callback(err, ''); + } else { + callback('', result); + } + }, + ); +}; + Promise.promisifyAll(Steem.prototype); // Export singleton instance -const steem = new Steem(); +const steem = new Steem(config); exports = module.exports = steem; exports.Steem = Steem; -exports.Steem.DEFAULTS = DEFAULTS; diff --git a/src/api/transports/base.js b/src/api/transports/base.js new file mode 100644 index 00000000..3abc85a3 --- /dev/null +++ b/src/api/transports/base.js @@ -0,0 +1,35 @@ +import Promise from 'bluebird'; +import EventEmitter from 'events'; +import each from 'lodash/each'; + +export default class Transport extends EventEmitter { + constructor(options = {}) { + super(options); + this.options = options; + this.id = 0; + } + + setOptions(options) { + each(options, (value, key) => { + this.options[key] = value; + }); + this.stop(); + } + + listenTo(target, eventName, callback) { + if (target.addEventListener) target.addEventListener(eventName, callback); + else target.on(eventName, callback); + + return () => { + if (target.removeEventListener) + target.removeEventListener(eventName, callback); + else target.removeListener(eventName, callback); + }; + } + + send() {} + start() {} + stop() {} +} + +Promise.promisifyAll(Transport.prototype); diff --git a/src/api/transports/http.js b/src/api/transports/http.js new file mode 100644 index 00000000..d831d088 --- /dev/null +++ b/src/api/transports/http.js @@ -0,0 +1,35 @@ +import fetch from 'isomorphic-fetch'; +import newDebug from 'debug'; + +import Transport from './base'; + +const debug = newDebug('steem:http'); + +export default class HttpTransport extends Transport { + send(api, data, callback) { + debug('Steem::send', api, data); + const id = data.id || this.id++; + const payload = { + id, + jsonrpc: '2.0', + method: data.method, + params: data.params, + }; + fetch(this.options.uri, { + method: 'POST', + body: JSON.stringify(payload), + }) + .then(res => { + debug('Steem::receive', api, data); + return res.json(); + }) + .then(json => { + const err = json.error || ''; + const result = json.result || ''; + callback(err, result); + }) + .catch(err => { + callback(err, ''); + }); + } +} diff --git a/src/api/transports/index.js b/src/api/transports/index.js new file mode 100644 index 00000000..4c88c0c7 --- /dev/null +++ b/src/api/transports/index.js @@ -0,0 +1,7 @@ +import HttpTransport from './http'; +import WsTransport from './ws'; + +export default { + http: HttpTransport, + ws: WsTransport, +}; diff --git a/src/api/transports/ws.js b/src/api/transports/ws.js new file mode 100644 index 00000000..7bb3161f --- /dev/null +++ b/src/api/transports/ws.js @@ -0,0 +1,229 @@ +import Promise from 'bluebird'; +import defaults from 'lodash/defaults'; +import isNode from 'detect-node'; +import newDebug from 'debug'; + +import Transport from './base'; + +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 debug = newDebug('steem:ws'); + +const DEFAULTS = { + apiIds: { + database_api: 0, + login_api: 1, + follow_api: 2, + network_broadcast_api: 4, + }, + id: 0, +}; + +export default class WsTransport extends Transport { + constructor(options = {}) { + defaults(options, DEFAULTS); + super(options); + + this.apiIds = options.apiIds; + + this.inFlight = 0; + this.currentP = Promise.fulfilled(); + this.isOpen = false; + this.releases = []; + this.requests = {}; + this.requestsTime = {}; + + // A Map of api name to a promise to it's API ID refresh call + this.apiIdsP = {}; + } + + start() { + if (this.startP) { + return this.startP; + } + + const startP = new Promise((resolve, reject) => { + if (startP !== this.startP) return; + const url = this.options.websocket; + this.ws = new WebSocket(url); + + const releaseOpen = this.listenTo(this.ws, 'open', () => { + debug('Opened WS connection with', url); + this.isOpen = true; + releaseOpen(); + resolve(); + }); + + const releaseClose = this.listenTo(this.ws, 'close', () => { + debug('Closed WS connection with', url); + this.isOpen = false; + delete this.ws; + this.stop(); + + if (startP.isPending()) { + reject( + new Error( + 'The WS connection was closed before this operation was made', + ), + ); + } + }); + + const releaseMessage = this.listenTo(this.ws, 'message', message => { + debug('Received message', message.data); + const data = JSON.parse(message.data); + const id = data.id; + const request = this.requests[id]; + if (!request) { + debug('Steem.onMessage: unknown request ', id); + } + delete this.requests[id]; + this.onMessage(data, request); + }); + + this.releases = this.releases.concat([ + releaseOpen, + releaseClose, + releaseMessage, + ]); + }); + + this.startP = startP; + this.getApiIds(); + + return startP; + } + + stop() { + debug('Stopping...'); + if (this.ws) this.ws.close(); + this.apiIdsP = {}; + delete this.startP; + delete this.ws; + this.releases.forEach(release => release()); + this.releases = []; + } + + /** + * Refreshes API IDs, populating the `Steem::apiIdsP` map. + * + * @param {String} [requestName] If provided, only this API will be refreshed + * @param {Boolean} [force] If true the API will be forced to refresh, ignoring existing results + */ + + getApiIds(requestName, force) { + if (!force && requestName && this.apiIdsP[requestName]) { + return this.apiIdsP[requestName]; + } + + const apiNamesToRefresh = requestName + ? [requestName] + : Object.keys(this.apiIds); + apiNamesToRefresh.forEach(name => { + this.apiIdsP[name] = this.sendAsync('login_api', { + method: 'get_api_by_name', + params: [name], + }).then(result => { + if (result != null) { + this.apiIds[name] = result; + } + }); + }); + + // If `requestName` was provided, only wait for this API ID + if (requestName) { + return this.apiIdsP[requestName]; + } + + // Otherwise wait for all of them + return Promise.props(this.apiIdsP); + } + + send(api, data, callback) { + debug('Steem::send', api, data); + const id = data.id || this.id++; + const startP = this.start(); + + const apiIdsP = api === 'login_api' && data.method === 'get_api_by_name' + ? Promise.fulfilled() + : this.getApiIds(api); + + if (api === 'login_api' && data.method === 'get_api_by_name') { + debug('Sending setup message'); + } else { + debug('Going to wait for setup messages to resolve'); + } + + this.currentP = Promise.join(startP, apiIdsP) + .then( + () => + new Promise((resolve, reject) => { + if (!this.ws) { + reject( + new Error( + 'The WS connection was closed while this request was pending', + ), + ); + return; + } + + const payload = JSON.stringify({ + id, + method: 'call', + params: [this.apiIds[api], data.method, data.params], + }); + + debug('Sending message', payload); + this.requests[id] = { + api, + data, + resolve, + reject, + start_time: Date.now(), + }; + + // this.inFlight += 1; + this.ws.send(payload); + }), + ) + .nodeify(callback); + + return this.currentP; + } + + onMessage(message, request) { + const {api, data, resolve, reject, start_time} = request; + debug('-- Steem.onMessage -->', message.id); + const errorCause = message.error; + if (errorCause) { + const err = new Error( + // eslint-disable-next-line prefer-template + (errorCause.message || 'Failed to complete operation') + + ' (see err.payload for the full error payload)', + ); + err.payload = message; + reject(err); + return; + } + + if (api === 'login_api' && data.method === 'login') { + debug( + "network_broadcast_api API ID depends on the WS' session. " + + 'Triggering a refresh...', + ); + this.getApiIds('network_broadcast_api', true); + } + + debug('Resolved', api, data, '->', message); + this.emit('track-performance', data.method, Date.now() - start_time); + delete this.requests[message.id]; + resolve(message.result); + } +} diff --git a/src/broadcast/index.js b/src/broadcast/index.js index 49e53f0a..1053aaa7 100644 --- a/src/broadcast/index.js +++ b/src/broadcast/index.js @@ -1,6 +1,5 @@ import Promise from 'bluebird'; import newDebug from 'debug'; -import noop from 'lodash/noop'; import broadcastHelpers from './helpers'; import formatterFactory from '../formatter'; @@ -10,6 +9,7 @@ import steemAuth from '../auth'; import { camelCase } from '../utils'; const debug = newDebug('steem:broadcast'); +const noop = function() {} const formatter = formatterFactory(steemApi); const steemBroadcast = {}; @@ -37,8 +37,7 @@ steemBroadcast.send = function steemBroadcast$send(tx, privKeys, callback) { 'Broadcasting transaction (transaction, transaction.operations)', transaction, transaction.operations ); - return steemApi.broadcastTransactionWithCallbackAsync( - () => {}, + return steemApi.broadcastTransactionSynchronousAsync( signedTransaction ).then(() => signedTransaction); }); @@ -47,11 +46,7 @@ steemBroadcast.send = function steemBroadcast$send(tx, privKeys, callback) { }; steemBroadcast._prepareTransaction = function steemBroadcast$_prepareTransaction(tx) { - // Login and get global properties - const loginP = steemApi.loginAsync('', ''); - const propertiesP = loginP.then(() => { - return steemApi.getDynamicGlobalPropertiesAsync() - }); + const propertiesP = steemApi.getDynamicGlobalPropertiesAsync(); return propertiesP .then((properties) => { // Set defaults on the transaction diff --git a/src/config.js b/src/config.js index 415518e4..d0e8be2a 100644 --- a/src/config.js +++ b/src/config.js @@ -1,10 +1,20 @@ +import each from 'lodash/each'; const defaultConfig = require('../config.json'); -module.exports = (function () { - const config = defaultConfig; - const get = (key) => config[key]; - const set = (key, value) => { - config[key] = value; - }; - return { get, set }; -})(); \ No newline at end of file +class Config { + constructor(c) { + each(c, (value, key) => { + this[key] = value; + }); + } + + get(k) { + return this[k]; + } + + set(k, v) { + this[k] = v; + } +} + +module.exports = new Config(defaultConfig); diff --git a/test/api.test.js b/test/api.test.js index fcb506e6..4974ffc6 100644 --- a/test/api.test.js +++ b/test/api.test.js @@ -1,64 +1,39 @@ require('babel-polyfill'); -import Promise from 'bluebird'; import assert from 'assert'; -import makeStub from 'mocha-make-stub' import should from 'should'; - -import steem, { Steem } from '../src/api/index'; import config from '../src/config'; import testPost from './test-post.json'; +import api from '../src/api'; describe('steem.api:', function () { this.timeout(30 * 1000); - describe('new Steem', () => { - it('doesn\'t open a connection until required', () => { - assert(!steem.ws, 'There was a connection on the singleton?'); - assert(!new Steem().ws, 'There was a connection on a new instance?'); - }); - - it('opens a connection on demand', (done) => { - const s = new Steem(); - assert(!new Steem().ws, 'There was a connection on a new instance?'); - s.start(); - process.nextTick(() => { - assert(s.ws, 'There was no connection?'); - done(); - }); - }); - }); - - describe('setWebSocket', () => { + describe('setUri', () => { it('works', () => { - steem.setWebSocket('ws://localhost'); - config.get('websocket').should.be.eql('ws://localhost'); - config.set('websocket', 'wss://steemd.steemit.com') + // api.setUri('http://localhost'); + config.set('uri', config.get('dev_uri')); }); }); - beforeEach(async () => { - await steem.apiIdsP; - }); - describe('getFollowers', () => { describe('getting ned\'s followers', () => { it('works', async () => { - const result = await steem.getFollowersAsync('ned', 0, 'blog', 5); + const result = await api.getFollowersAsync('ned', 0, 'blog', 5); assert(result, 'getFollowersAsync resoved to null?'); result.should.have.lengthOf(5); }); it('the startFollower parameter has an impact on the result', async () => { // Get the first 5 - const result1 = await steem.getFollowersAsync('ned', 0, 'blog', 5) - result1.should.have.lengthOf(5); - const result2 = await steem.getFollowersAsync('ned', result1[result1.length - 1].follower, 'blog', 5) - result2.should.have.lengthOf(5); + const result1 = await api.getFollowersAsync('ned', 0, 'blog', 5) + result1.should.have.lengthOf(5); + const result2 = await api.getFollowersAsync('ned', result1[result1.length - 1].follower, 'blog', 5) + result2.should.have.lengthOf(5); result1.should.not.be.eql(result2); }); it('clears listeners', async () => { - steem.listeners('message').should.have.lengthOf(0); + api.listeners('message').should.have.lengthOf(0); }); }); }); @@ -66,12 +41,12 @@ describe('steem.api:', function () { 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'); + const result = await api.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); + api.listeners('message').should.have.lengthOf(0); }); }); }); @@ -79,7 +54,7 @@ describe('steem.api:', function () { describe('streamBlockNumber', () => { it('streams steem transactions', (done) => { let i = 0; - const release = steem.streamBlockNumber((err, block) => { + const release = api.streamBlockNumber((err, block) => { should.exist(block); block.should.be.instanceOf(Number); i++; @@ -94,7 +69,7 @@ describe('steem.api:', function () { describe('streamBlock', () => { it('streams steem blocks', (done) => { let i = 0; - const release = steem.streamBlock((err, block) => { + const release = api.streamBlock((err, block) => { try { should.exist(block); block.should.have.properties([ @@ -102,9 +77,9 @@ describe('steem.api:', function () { 'transactions', 'timestamp', ]); - } catch (err) { + } catch (err2) { release(); - done(err); + done(err2); return; } @@ -120,7 +95,7 @@ describe('steem.api:', function () { describe('streamTransactions', () => { it('streams steem transactions', (done) => { let i = 0; - const release = steem.streamTransactions((err, transaction) => { + const release = api.streamTransactions((err, transaction) => { try { should.exist(transaction); transaction.should.have.properties([ @@ -128,9 +103,9 @@ describe('steem.api:', function () { 'operations', 'extensions', ]); - } catch (err) { + } catch (err2) { release(); - done(err); + done(err2); return; } @@ -146,12 +121,12 @@ describe('steem.api:', function () { describe('streamOperations', () => { it('streams steem operations', (done) => { let i = 0; - const release = steem.streamOperations((err, operation) => { + const release = api.streamOperations((err, operation) => { try { should.exist(operation); - } catch (err) { + } catch (err2) { release(); - done(err); + done(err2); return; } @@ -163,37 +138,4 @@ describe('steem.api:', function () { }); }); }); - - describe('when there are network failures (the ws closes)', () => { - const originalStart = Steem.prototype.start; - makeStub(Steem.prototype, 'start', function () { - return originalStart.apply(this, arguments); - }); - - const originalStop = Steem.prototype.stop; - makeStub(Steem.prototype, 'stop', function () { - return originalStop.apply(this, arguments); - }); - - it('tries to reconnect automatically', async () => { - const steem = new Steem(); - // console.log('RECONNECT TEST start'); - assert(!steem.ws, 'There was a websocket connection before a call?'); - // console.log('RECONNECT TEST make followers call'); - await steem.getFollowersAsync('ned', 0, 'blog', 5); - assert(steem.ws, 'There was no websocket connection after a call?'); - // console.log('RECONNECT TEST wait 1s'); - await Promise.delay(1000); - // console.log('RECONNECT TEST simulate close event'); - assert(!steem.stop.calledOnce, 'Steem::stop was already called before disconnect?'); - steem.ws.emit('close'); - assert(!steem.ws); - assert(!steem.startP); - assert(steem.stop.calledOnce, 'Steem::stop wasn\'t called when the connection closed?'); - // console.log('RECONNECT TEST make followers call'); - await steem.getFollowersAsync('ned', 0, 'blog', 5); - assert(steem.ws, 'There was no websocket connection after a call?'); - assert(steem.isOpen, 'There was no websocket connection after a call?'); - }); - }); }); diff --git a/test/broadcast.test.js b/test/broadcast.test.js index 053feeb3..02532324 100644 --- a/test/broadcast.test.js +++ b/test/broadcast.test.js @@ -150,4 +150,20 @@ describe('steem.broadcast:', () => { ]); }); }); + + describe('writeOperations', () => { + it('wrong', (done) => { + const wif = steem.auth.toWif('username', 'password', 'posting'); + steem.broadcast.vote(wif, 'voter', 'author', 'permlink', 0, (err) => { + if(err && /tx_missing_posting_auth/.test(err.message)) { + should.exist(err.digest); + should.exist(err.transaction); + should.exist(err.transaction_id); + done(); + } else { + console.log(err); + } + }); + }); + }); });