diff --git a/golos-lib-js/docs/files/msgs.md b/golos-lib-js/docs/files/msgs.md
index 2cfc7d5..0ad9ace 100644
--- a/golos-lib-js/docs/files/msgs.md
+++ b/golos-lib-js/docs/files/msgs.md
@@ -1,10 +1,15 @@
# Личные сообщения
-Блокчейн Golos предоставляет подсистему мгновенных сообщений, которая позволяет создать полноценный мессенджер с приватными, шифрованными сообщениями. Сообщения шифруются на стороне клиента (с использованием закрытого мемо-ключа отправителя и открытого мемо-ключа получателя), отправляются в блокчейн через `private_message_operation`, а затем могут быть получены из БД с помощью API `private_message` и расшифрованы на стороне клиента (с использованием закрытого мемо-ключа из from / to и публичный мемо-ключ другого пользователя).
+Блокчейн Golos предоставляет подсистему мгновенных сообщений, которая позволяет создать полноценный мессенджер с приватными, шифрованными сообщениями, а также с группами с поддержкой шифрования сообщений.
+
+Сообщения в приватных чатах шифруются на стороне клиента (приватные: с использованием закрытого мемо-ключа отправителя и открытого мемо-ключа получателя).
+Сообщения в группах, поддерживающих шифрование, шифруются на ноде блокчейна, при этом оригиналы **не** сохраняются ни в какую-либо базу данных, ни в block-log.
+
+Зашифрованные сообщения отправляются в блокчейн через `private_message_operation`, а затем могут быть получены из БД с помощью API `private_message` и расшифрованы на стороне клиента.
### Шифрование и отправка
-Сообщения - это объекты JSON. Если вы хотите, чтобы отправленные вами сообщения отображались в Golos Messenger (в блогах, на форумах), вы должны использовать объекты JSON с полем `body`, содержащим строку с текстом сообщения, а также с полями `app` и `version`, описывающими ваше приложение. Также вы можете добавлять любые ваши собственные поля. Но если вы используете только `body`, мы рекомендуем вам установить `app` - `'golos-messenger'`, а `version` - `1`.
+Сообщения - это объекты JSON. Если вы хотите, чтобы отправленные вами сообщения отображались в Golos Messenger, вы должны использовать объекты JSON с полем `body`, содержащим строку с текстом сообщения, а также с полями `app` и `version`, описывающими ваше приложение. Также вы можете добавлять любые ваши собственные поля. Но если вы используете только `body`, мы рекомендуем вам установить `app` - `'golos-messenger'`, а `version` - `1`.
Для создания объекта-сообщения используйте функцию `golos.messages.newTextMsg`.
@@ -13,22 +18,59 @@
- `version` должно быть целым числом, начиная с 1;
- `body` должно быть строкой.
-Полсе создания объекта-сообщения, следует преобразовать его в JSON-строку, зашифровать (используется SHA-512 с nonce, который является уникальным идентифиатором на основе UNIX timestamp, и затем AES), и преобразовать результат в HEX-строку (`encrypted_message`). Все это автоматически делается с помощью функции `golos.messages.encode`.
+Полсе создания объекта-сообщения, следует преобразовать его в JSON-строку, а затем зашифровать.
+- В приватных чатах используется SHA-512 с nonce, который является уникальным идентификатором на основе UNIX timestamp, и затем AES, после этого результат преобразуется в HEX-строку (`encrypted_message`).
+- В группах, поддерживающих шифрование, JSON-строку надо отправить в `golos.api.encryptBodyAsync`, затем обернуть результат в JSON-строку формата `{"t":"em","c":"результат шифрования"}` и преобразовать ее в HEX-строку.
+- В группах без шифрования, JSON-строку надо сразу преобразовать в HEX-строку.
+Все это можно сделать автоматически, просто вызвав функцию `golos.messages.encodeMsg`.
+
+Пример для приватного чата:
+
+```js
+let data = await golos.messages.encodeMsg({ private_memo: 'alice private memo key',
+ to_public_memo: 'bob public memo key',
+ msg: golos.messages.newTextMsg('Hello world', 'golos-messenger', 1)
+})
+
+const json = JSON.stringify(['private_message', {
+ from: 'alice',
+ to: 'bob',
+ nonce: data.nonce,
+ from_memo_key: data.from_memo_key,
+ to_memo_key: data.to_memo_key,
+ checksum: data.checksum,
+ update: false,
+ encrypted_message: data.encrypted_message,
+}]);
+golos.broadcast.customJson('alice private posting key', [], ['alice'], 'private_message', json, (err, result) => {
+ alert(err);
+ alert(JSON.stringify(result));
+});
+```
-Полный пример:
+Пример для группы:
```js
-let data = golos.messages.encode('alice private memo key', 'bob public memo key', golos.messages.newTextMsg('Hello world', 'golos-messenger', 1));
+// group - это объект группы, полученный через `golos.api.getGroupsAsync`,
+// содержащий по меньшей мере поля `name` и `is_encrypted`, необходимые для
+// правильного формирования сообщения
+
+let data = await golos.messages.encodeMsg({ group,
+ msg: golos.messages.newTextMsg('Hello world', 'golos-messenger', 1)
+})
const json = JSON.stringify(['private_message', {
from: 'alice',
to: 'bob',
nonce: data.nonce,
- from_memo_key: 'alice PUBLIC memo key',
- to_memo_key: 'bob public memo key',
+ from_memo_key: data.from_memo_key,
+ to_memo_key: data.to_memo_key,
checksum: data.checksum,
update: false,
encrypted_message: data.encrypted_message,
+ extensions: [[0, {
+ group: group.name
+ }]],
}]);
golos.broadcast.customJson('alice private posting key', [], ['alice'], 'private_message', json, (err, result) => {
alert(err);
@@ -38,10 +80,13 @@ golos.broadcast.customJson('alice private posting key', [], ['alice'], 'private_
### Редактирование сообщений
-Сообщения идентифицируются с помощью from+to+nonce, поэтому при обновлении сообщения вы должны кодировать его тем же значением nonce, что и в предыдущей версии.
+Сообщения идентифицируются с помощью group+from+to+nonce, поэтому при обновлении сообщения вы должны кодировать его тем же значением nonce, что и в предыдущей версии.
```js
-data = golos.messages.encode('alice private memo key', 'bob public memo key', golos.messages.newTextMsg('Goodbye world', 'golos-messenger', 1), data.nonce);
+data = await golos.messages.encodeMsg({ private_memo: 'alice private memo key',
+ to_public_memo: 'bob public memo key', msg: golos.messages.newTextMsg('Goodbye world', 'golos-messenger', 1),
+ nonce: data.nonce
+})
```
Затем эти данные должны быть отправлены с помощью операции `private_message`, как и в предыдущем случае, но с `update` = `true`.
@@ -73,7 +118,7 @@ try {
console.error(err);
}
if (msg) {
- let data = golos.messages.encode('alice private memo key', 'bob public memo key', msg);
+ let data = await golos.messages.encodeMsg({ private_memo: 'alice private memo key', to_public_memo: 'bob public memo key', msg })
// ...и отправьте, так же, как обычное текстовое сообщение
}
```
@@ -86,8 +131,9 @@ golos.messages.newImageMsg('https://site.com/https-is-recommended.jpg', (err, ms
alert(err);
console.error(err);
} else {
- let data = golos.messages.encode('alice private memo key', 'bob public memo key', msg);
- // ...и отправьте, так же, как обычное текстовое сообщение
+ golos.messages.encodeMsg({ private_memo: 'alice private memo key', to_public_memo: 'bob public memo key', msg}).then((data) => {
+ // ...и отправьте, так же, как обычное текстовое сообщение
+ })
}
}, (progress, extra_data) => {
console.log('Progress: %i%', progress);
@@ -97,27 +143,50 @@ golos.messages.newImageMsg('https://site.com/https-is-recommended.jpg', (err, ms
### Получение сообщений при открытии мессенджера. Расшифровка сообщений
-Сообщение можно получить с помощью `golos.api.getThread`, каждое сообщение является объектом с полями `from_memo_key`, `to_memo_key`, `nonce`, `checksum`, `encrypted_message` и другими полями. Затем сообщение можно расшифровать с помощью `golos.messages.decode`, который поддерживает пакетную обработку (может расшифровать несколько сообщений одновременно) и обеспечивает высокую производительность.
+Сообщение можно получить с помощью `golos.api.getThread`, каждое сообщение является объектом с полями `from_memo_key`, `to_memo_key`, `nonce`, `checksum`, `encrypted_message` и другими полями. Затем сообщение можно расшифровать с помощью `golos.messages.decodeMsgs`, который поддерживает пакетную обработку (может расшифровать несколько сообщений одновременно) и обеспечивает высокую производительность. Метод позволяет за один вызов расшифровывать сообщения даже из разных групп и разных приватных чатов (актуально для получения "последних сообщений" в списке Контактов).
-:electron: `golos.messages.decode` использует WebAssembly. Перед первым действием вызовите `await golos.importNativeLib()`. [Подробнее](./wasm.md).
+:electron: `golos.messages.decodeMsgs` использует WebAssembly.
-```js
-await golos.importNativeLib();
+Пример для приватного чата:
-golos.api.getThread('alice', 'bob', {}, (err, results) => {
- results = golos.messages.decode('alice private key', 'bob public memo key', results);
+```js
+golos.api.getThread({ from: 'alice', to: 'bob', }, (err, results) => {
+ results = await golos.messages.decodeMsgs({ private_memo: 'alice private key', msgs: results })
alert(results[0].message.body);
});
```
-**Примечание:** это также проверяет сообщения на соответствие следующим правилам:
+В случае с группой, getThread позволяет **сразу** расшифровать сообщения.
+От Алисы для этого нужен не memo-, а posting-ключ.
+
+```js
+const results = await golos.auth.withNodeLogin({ account: 'alice', keys: {
+ posting: 'alice POSTING key',
+}, call: async (loginData) => {
+ const th = await golos.api.getThreadAsync({
+ ...loginData,
+ group: 'test-group',
+ })
+ return th
+}})
+alert(results)
+```
+
+Однако, перед рендерингом сообщений в uI вам все равно нужно вызвать `decodeMsgs`, чтобы:
+- проверить сообщения на соответствие правилам (об этом ниже) и по результатам проверки либо исключить, либо пометить ошибочные сообщения;
+- можно было вызывать `getThread` только при открытии пользователем группы, как более тяжелый, а при получении новых сообщений через Golos Notify Service вызывать лишь `decodeMsgs`.
+
+При этом, `decodeMsgs` расшифрует лишь те сообщения в массиве, которые не были расшифрованы до этого (встроенной расшифровкой в `getThread`, или прошлым вызовом `decodeMsgs`).
+Для еще большей оптимизации вы можете кешировать сообщения, используя `before_decode` и `for_each`, как это делаем мы в коде Golos Messenger.
+
+**Примечание:** `decodeMsgs` также проверяет сообщения на соответствие следующим правилам:
- сообщение должно быть правильным объектом JSON с полями, соответствующими следующим правилам;
- поле `app` должно быть строкой длиной от 1 до 16;
- поле `версия` должно быть целым числом, начиная с 1;
- body должно быть строкой;
- для сообщений-изображений: previewWidth и previewHeight должны быть целыми числами, которые являются результатом подгонки изображения к области 600x300 пикселей.
-**Примечание:** если сообщение не может быть расшифровано, распарсено как JSON и/или проверено, оно все равно добавляется к результату, но имеет `message: null` (если не может быть распарсено как JSON или проверено) и `raw_message: null` (если вообще не может быть расшифровано). Такое поведение позволяет клиенту пометить это сообщение как прочитанное в блокчейне, но не отображать его пользователю. Если вы хотите изменить это поведение, вы можете переопределить параметр `on_error` в `golos.messages.decode` (подробнее см. в коде).
+**Примечание:** если сообщение не может быть расшифровано, распарсено как JSON и/или проверено, оно все равно добавляется к результату, но имеет `message: null` (если не может быть распарсено как JSON или проверено) и `raw_message: null` (если вообще не может быть расшифровано). Такое поведение позволяет клиенту пометить это сообщение как прочитанное в блокчейне, но не отображать его пользователю. Если вы хотите изменить это поведение, вы можете переопределить параметр `on_error` в `golos.messages.decodeMsgs` (подробнее см. в коде).
### Мгновенное получение сообщений
@@ -135,7 +204,7 @@ golos.api.getThread('alice', 'bob', {}, (err, results) => {
Для создания диапазонов вы можете использовать `golos.messages.makeDatedGroups`, который строит такие диапазоны по условию и может превращать их в реальные операции "на лету".
-Он принимает декодированные сообщения от `golos.messages.decode`.
+Он принимает декодированные сообщения от `golos.messages.decodeMsgs`.
**Примечание: функция должна перебирать сообщения от start к end.**
@@ -211,7 +280,7 @@ msg = {...msg, ...quote}; // добавляем цитату
#### Отображение сообщений с цитатами
-`golos.messages.decode` поддерживает сообщения с цитатами. Каждое такое сообщение имеет поле `quote` в своем поле `сообщение`. Но, если `quote` сообщения неверна (сообщение составлено с некорректным пользовательским интерфейсом, который не использует `makeQuoteMsg` и неправильно составляет цитаты), **весь объект сообщения будет считаться некорректным**, то есть поле `message` будет `null`.
+`golos.messages.decodeMsgs` поддерживает сообщения с цитатами. Каждое такое сообщение имеет поле `quote` в своем поле `сообщение`. Но, если `quote` сообщения неверна (сообщение составлено с некорректным пользовательским интерфейсом, который не использует `makeQuoteMsg` и неправильно составляет цитаты), **весь объект сообщения будет считаться некорректным**, то есть поле `message` будет `null`.
#### Редактирование сообщений с цитатами
diff --git a/golos-lib-js/package.json b/golos-lib-js/package.json
index f5e6075..a1422d2 100644
--- a/golos-lib-js/package.json
+++ b/golos-lib-js/package.json
@@ -1,6 +1,6 @@
{
"name": "golos-lib-js",
- "version": "0.9.73",
+ "version": "0.9.74",
"description": "Golos-js the JavaScript library with API for GOLOS blockchain",
"main": "lib/index.js",
"scripts": {
diff --git a/golos-lib-js/src/api/methods.js b/golos-lib-js/src/api/methods.js
index 84cf652..8e4adad 100644
--- a/golos-lib-js/src/api/methods.js
+++ b/golos-lib-js/src/api/methods.js
@@ -421,7 +421,8 @@ module.exports = [
{
"api": "database_api",
"method": "get_accounts",
- "params": ["accountNames"]
+ "has_default_values": true,
+ "params": ["accountNames", `query=${EMPTY_OBJECT}`]
},
{
"api": "database_api",
@@ -679,7 +680,12 @@ module.exports = [
{
"api": "private_message",
"method": "get_thread",
- "params": ["from", "to", "query"]
+ "has_default_values": true,
+ "params": [
+ "from_or_query",
+ `to=${EMPTY_STRING}`,
+ `opts=${EMPTY_OBJECT}`,
+ ]
},
{
"api": "private_message",
@@ -695,12 +701,22 @@ module.exports = [
{
"api": "private_message",
"method": "get_contact_info",
- "params": ["owner", "contact"]
+ "has_default_values": true,
+ "params": [
+ "owner_or_query",
+ `contact=${EMPTY_STRING}`,
+ ]
},
{
"api": "private_message",
"method": "get_contacts",
- "params": ["owner", "type", "limit", "offset"]
+ "has_default_values": true,
+ "params": [
+ "owner_or_query",
+ `type="unknown"`,
+ `limit=20`,
+ `offset=0`,
+ ]
},
{
"api": "private_message",
@@ -793,4 +809,9 @@ module.exports = [
"method": "decrypt_comments",
"params": ["query={}"]
},
+ {
+ "api": "cryptor",
+ "method": "decrypt_messages",
+ "params": ["query={}"]
+ },
]
diff --git a/golos-lib-js/src/auth/ecc/src/aes.js b/golos-lib-js/src/auth/ecc/src/aes.js
index 470d5a3..0e2fe31 100644
--- a/golos-lib-js/src/auth/ecc/src/aes.js
+++ b/golos-lib-js/src/auth/ecc/src/aes.js
@@ -123,7 +123,7 @@ function cryptoJsEncrypt(message, key, iv) {
/** @return {string} unique 64 bit unsigned number string. Being time based, this is careful to never choose the same nonce twice. This value could be recorded in the blockchain for a long time.
*/
-function uniqueNonce() {
+export function uniqueNonce() {
if(unique_nonce_entropy === null) {
const b = secureRandom.randomUint8Array(2)
unique_nonce_entropy = parseInt(b[0] << 8 | b[1], 10)
diff --git a/golos-lib-js/src/auth/index.js b/golos-lib-js/src/auth/index.js
index 85772db..f8b3c4d 100644
--- a/golos-lib-js/src/auth/index.js
+++ b/golos-lib-js/src/auth/index.js
@@ -10,7 +10,7 @@ var bigi = require('bigi'),
PublicKey = require('./ecc/src/key_public'),
session = require('./session'),
multiSession = require('./multiSession'),
- api = require('../api'),
+ golosApi = require('../api'),
hash = require('./ecc/src/hash');
var Auth = {};
@@ -97,7 +97,7 @@ Auth.loginAsync = function (name, password, callback) {
privateKeys[role] = PrivateKey.fromSeed(`${name}${role}${password}`).toString()
);
}
- api.getAccountsAsync([name], (err, res) => {
+ golosApi.getAccounts([name], (err, res) => {
if (err) {
callback(err, null);
return;
@@ -128,6 +128,60 @@ Auth.loginAsync = function (name, password, callback) {
Auth.login = promisify(Auth.loginAsync);
+// `keys` is object like:
+// { posting: "private posting key" }
+Auth.withNodeLogin = async function ({ account, keys,
+ call, dgp, api, sessionName }) {
+ if (!sessionName) sessionName = 'node_login'
+ if (!api) {
+ api = golosApi
+ }
+
+ if (!dgp) {
+ dgp = await api.getDynamicGlobalPropertiesAsync()
+ }
+
+ let resp
+
+ const { MultiSession } = multiSession
+ const ms = new MultiSession(sessionName)
+ const sessionData = ms.load()
+ let loginData = sessionData.getVal(account, 'login_data')
+ if (loginData) {
+ let resp = await call(loginData)
+ if (!resp.login_error) {
+ return resp
+ }
+ }
+
+ const { head_block_number, witness } = dgp
+
+ console.time('withNodeLogin - signData')
+ const signed = this.signData(head_block_number.toString(), keys)
+ console.timeEnd('withNodeLogin - signData')
+
+ // TODO: only 1st, because node supports only 1 key
+ const signature = Object.values(signed)[0]
+
+ loginData = {
+ account,
+ signed_data: {
+ head_block_number,
+ witness,
+ },
+ signature,
+ }
+
+ resp = await call(loginData)
+ if (resp.login_error) {
+ throw resp.login_error
+ }
+
+ sessionData.setVal(account, 'login_data', loginData).save()
+
+ return resp
+}
+
Auth.toWif = function (name, password, role) {
var seed = name + role + password;
var brainKey = seed.trim().split(/[\t\n\v\f\r ]+/).join(' ');
diff --git a/golos-lib-js/src/auth/messages.js b/golos-lib-js/src/auth/messages.js
index 099bdff..523a52d 100644
--- a/golos-lib-js/src/auth/messages.js
+++ b/golos-lib-js/src/auth/messages.js
@@ -2,11 +2,14 @@
import ByteBuffer from 'bytebuffer'
import assert from 'assert'
import base58 from 'bs58'
+import newDebug from 'debug'
import truncate from 'lodash/truncate';
import {Aes, PrivateKey, PublicKey} from './ecc'
import {ops} from './serializer'
+import golosApi from '../api'
+import auth from '.'
import {fitImageToSize} from '../utils';
-import { promisify, } from '../promisify';
+import { promisify, } from '../promisify'
const {isInteger} = Number
/** @const {string} DEFAULT_APP
@@ -31,6 +34,10 @@ export const MAX_IMAGE_QUOTE_LENGTH = 2000;
const toPrivateObj = o => (o ? o.d ? o : PrivateKey.fromWif(o) : o/*null or undefined*/)
const toPublicObj = o => (o ? o.Q ? o : PublicKey.fromString(o) : o/*null or undefined*/)
+export const emptyPublicKey = 'GLS1111111111111111111111111111111114T1Anm'
+
+const debugMsgs = newDebug('golos:messages')
+
function validateAppVersion(app, version) {
assert(typeof app === 'string' && app.length >= 1 && app.length <= 16,
'message.app should be a string, >= 1, <= 16');
@@ -217,6 +224,58 @@ function forEachMessage(message_objects, begin_idx, end_idx, callback) {
}
}
+function msgFromBuf(buf, lengthPrefixed = false) {
+ const toUTF8String = () => {
+ return new Buffer(buf.toString('binary'), 'binary').toString('utf-8')
+ }
+ if (!lengthPrefixed) { // Used in groups. Prefixed used in private chats
+ buf.mark()
+ return toUTF8String()
+ }
+ let rawMsg
+ try {
+ buf.mark()
+ rawMsg = buf.readVString()
+ } catch(e) {
+ buf.reset()
+ // Sender did not length-prefix the message
+ rawMsg = toUTF8String()
+ }
+ return rawMsg
+}
+
+function msgFromHex(hex, lengthPrefixed = false) {
+ const buf = ByteBuffer.fromHex(hex, ByteBuffer.LITTLE_ENDIAN)
+ return msgFromBuf(buf, lengthPrefixed)
+}
+
+const parseMsg = (message, rawMsg, raw_messages = false) => {
+ message.raw_message = rawMsg
+ message.decrypt_date = message.receive_date
+ if (!raw_messages) {
+ let msg = JSON.parse(message.raw_message)
+ msg.type = msg.type || 'text'
+ validateBody(msg.body)
+ if (msg.type === 'image')
+ validateImageMsg(msg)
+ validateAppVersion(msg.app, msg.version)
+ validateMsgWithQuote(msg)
+ message.message = msg
+ }
+}
+
+const parseQuery = (query) => {
+ assert(query && typeof(query) === 'object' && !Array.isArray(query),
+ 'argument should be an object with argument-field. See golos-lib-js documentation.')
+ return query
+}
+
+const warnDeprecation = (oldMethod, newMethod) => {
+ console.warn(`golos.messages.${oldMethod} deprecated. ` +
+ `It is not async, not supports groups, etc. ` +
+ `Will be removed in future. Migrate to golos.messages.${newMethod}.`)
+}
+
/**
Decodes messages of format used by golos.messages.encode(), which are length-prefixed, and also messages sent by another way (not length-prefixed).
Also, parses (JSON) and validates each message (app, version...). (Invalid messages are also added to result, it is need to mark them as read. To change it, use on_error
).
@@ -234,14 +293,15 @@ function forEachMessage(message_objects, begin_idx, end_idx, callback) {
@return {array} - result array of message_objects. Each object has "message" and "raw_message" fields. If message is invalid, it has only "raw_message" field. And if message cannot be decoded at all, it hasn't any of these fields.
*/
export function decode(private_memo_key, second_user_public_memo_key, message_objects, before_decode = undefined, for_each = undefined, on_error = undefined, begin_idx = undefined, end_idx = undefined, raw_messages = false) {
+ warnDeprecation('decode', 'decodeMsgs')
+
assert(private_memo_key, 'private_memo_key is required');
assert(second_user_public_memo_key, 'second_user_public_memo_key is required');
assert(message_objects, 'message_objects is required');
- // Most "heavy" lines
const private_key = toPrivateObj(private_memo_key);
const public_key = toPublicObj(second_user_public_memo_key);
- let shared_secret = private_key.get_shared_secret(public_key);
+ let shared_secret
let results = [];
forEachMessage(message_objects, begin_idx, end_idx, (message_object, i) => {
@@ -264,33 +324,18 @@ export function decode(private_memo_key, second_user_public_memo_key, message_ob
return true;
}
+ // Most "heavy" line
+ if (!shared_secret) shared_secret = private_key.get_shared_secret(public_key)
+
let decrypted = Aes.decrypt(shared_secret, null,
message_object.nonce.toString(),
Buffer.from(message_object.encrypted_message, 'hex'),
message_object.checksum);
const mbuf = ByteBuffer.fromBinary(decrypted.toString('binary'), ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN)
- try {
- mbuf.mark()
- decrypted = mbuf.readVString()
- } catch(e) {
- mbuf.reset()
- // Sender did not length-prefix the memo
- decrypted = new Buffer(mbuf.toString('binary'), 'binary').toString('utf-8')
- }
+ decrypted = msgFromBuf(mbuf, true)
- decrypted = decrypted.toString();
- message_object.raw_message = decrypted;
- if (!raw_messages) {
- let msg = JSON.parse(message_object.raw_message);
- msg.type = msg.type || 'text';
- validateBody(msg.body);
- if (msg.type === 'image')
- validateImageMsg(msg);
- validateAppVersion(msg.app, msg.version);
- validateMsgWithQuote(msg);
- message_object.message = msg;
- }
+ parseMsg(message_object, decrypted, raw_messages)
} catch (exception) {
if (processOnError(exception))
return true;
@@ -307,6 +352,191 @@ export function decode(private_memo_key, second_user_public_memo_key, message_ob
return results;
}
+const ExceptionTypes = {
+ NodeError: 1,
+ Error: 2,
+ OuterError: 3,
+ RequestError: 4,
+}
+
+export async function decodeMsgs(query) {
+ let { msgs, before_decode, for_each, on_error,
+ raw_messages,
+ private_memo, // chats
+ api, login, // groups
+ begin_idx, end_idx,
+ } = parseQuery(query)
+
+ assert(msgs, 'msgs is required')
+
+ let private_key = private_memo && toPrivateObj(private_memo)
+ const myPublic = private_key && private_key.toPublic().toString()
+ let shareds = {}
+
+ let results = []
+ let entriesDec = {}
+
+ forEachMessage(msgs, begin_idx, end_idx, (message, i) => {
+ // Return true if for_each should not be called
+ let processOnError = (exception, exType = ExceptionTypes.Error) => {
+ if (on_error) {
+ if (!on_error(message, i, exception, exType)) {
+ results.push(message)
+ }
+ return true
+ }
+ console.warn('golos.messages.decodeMsgs', i, exception)
+ return false
+ }
+
+ try {
+ if (!message.group || message.decrypt_date !== message.receive_date) {
+ message.raw_message = null // Will be set if message will be successfully decoded
+ message.message = null // Will be set if message will be also successfully parsed and validated
+ }
+
+ if (before_decode && before_decode(message, i, results)) {
+ return true
+ }
+
+ if (!message.group) {
+ const pubKey = message.from_memo_key === myPublic ?
+ message.to_memo_key : message.from_memo_key
+ // Most "heavy" line (in private chats)
+ if (!shareds[pubKey]) {
+ shareds[pubKey] = private_key.get_shared_secret(toPublicObj(pubKey))
+ }
+
+ let decrypted = Aes.decrypt(shareds[pubKey], null,
+ message.nonce.toString(),
+ Buffer.from(message.encrypted_message, 'hex'),
+ message.checksum)
+
+ const mbuf = ByteBuffer.fromBinary(decrypted.toString('binary'), ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN)
+ decrypted = msgFromBuf(mbuf, true)
+
+ parseMsg(message, decrypted, raw_messages)
+ message.decryptor = 'golos-lib'
+ } else {
+ let rawMsg = msgFromHex(message.encrypted_message)
+ const isEncrypted = rawMsg.startsWith('{"t":"em"')
+
+ if (isEncrypted) {
+ if (!message.decrypted || message.decrypt_date !== message.receive_date) {
+ entriesDec[i] = { group: message.group,
+ encrypted_message: message.encrypted_message, }
+ results.push(message)
+ return true
+ }
+ rawMsg = msgFromHex(message.decrypted)
+ }
+
+ parseMsg(message, rawMsg, raw_messages)
+ }
+ } catch (exception) {
+ if (processOnError(exception))
+ return true
+ }
+ try {
+ if (!for_each || !for_each(message, i)) {
+ results.push(message);
+ }
+ } catch (exception) {
+ console.error(exception)
+ processOnError(exception, ExceptionTypes.OuterError)
+ }
+
+ return true
+ })
+
+ const entries = Object.values(entriesDec)
+ if (entries.length) {
+ if (!api) {
+ api = golosApi
+ }
+ let decRes, decErr
+ try {
+ debugMsgs('decryptMessagesAsync', entries.length + ' msgs')
+
+ const { dgp, account, keys, sessionName } = login
+ decRes = await auth.withNodeLogin({ account, keys, sessionName, dgp,
+ call: async (loginData) => {
+ const decRes = await api.decryptMessagesAsync({
+ ...loginData,
+ entries
+ })
+ return decRes
+ }
+ })
+ } catch (err) {
+ decErr = err
+ }
+ debugMsgs('decryptMessagesAsync', entries.length + ' msgs')
+
+ // Return true if for_each should not be called
+ let processOnError = (msg, idx, exception, exType = ExceptionTypes.NodeError) => {
+ if (on_error) {
+ if (on_error(msg, idx, exception, exType)) {
+ msg.ignore = true
+ }
+ return true
+ }
+ console.warn('golos.messages.decodeMsgs', i, exception)
+ return false
+ }
+
+ const idxs = Object.keys(entriesDec)
+ for (let j = 0; j < entries.length; ++j) {
+ let i = idxs[j]
+ let msg = msgs[i]
+
+ try {
+ let proceedId = true
+ if (!decRes) {
+ proceedId = false
+ const ex = new Error((decErr && decErr.message) || 'Unknown error')
+ if (processOnError(msg, i, decErr, ExceptionTypes.RequestError))
+ continue
+ } else if (decRes.status !== 'ok' || !decRes.results) {
+ proceedId = false
+ const ex = new Error(decRes.err || 'Unknown error')
+ if (processOnError(msg, i, ex))
+ continue
+ }
+
+ if (proceedId) {
+ const dec = decRes.results[j]
+ if (dec && dec.body) {
+ dec.decrypted = dec.body // TODO: but `decrypted` should be hex
+ parseMsg(msg, dec.decrypted, raw_messages)
+ msg.decryptor = 'node/decrypt_messages'
+ } else {
+ const ex = new Error(dec.err || (!dec ? ' No decrypt result' : 'No body') + ', unknown error')
+ if (processOnError(msg, i, ex))
+ continue
+ }
+ }
+ } catch (err) {
+ if (processOnError(msg, i, exception, ExceptionTypes.Error))
+ continue
+ }
+
+ try {
+ if (!msg.ignore && for_each && for_each(msg, i)) {
+ msg.ignore = true
+ }
+ } catch (exception) {
+ console.error(exception)
+ processOnError(exception, ExceptionTypes.OuterError)
+ }
+ }
+ }
+
+ results = results.filter(res => !res.ignore)
+
+ return results
+}
+
/**
Encodes message to send with private_message_operation. Converts object to JSON string. Uses writeVString, so format of data to encode is string length + string.
@arg {string|PrivateKey} from_private_memo_key - private memo key of "from"
@@ -316,6 +546,8 @@ export function decode(private_memo_key, second_user_public_memo_key, message_ob
@return {object} - Object with fields: nonce, checksum and message.
*/
export function encode(from_private_memo_key, to_public_memo_key, message, nonce = undefined) {
+ warnDeprecation('encode', 'encodeMsg')
+
assert(from_private_memo_key, 'from_private_memo_key is required');
assert(to_public_memo_key, 'to_public_memo_key is required');
assert(message, 'message is required');
@@ -339,6 +571,85 @@ export function encode(from_private_memo_key, to_public_memo_key, message, nonce
};
}
+export async function encodeMsg({ group,
+ private_memo, to_public_memo,
+ msg, nonce,
+ api
+}) {
+ assert(msg, 'msg is required')
+
+ const msgToBuffer = (msg) => {
+ const mbuf = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN)
+ if (group) {
+ mbuf.writeUTF8String(JSON.stringify(msg))
+ } else {
+ mbuf.writeVString(JSON.stringify(msg))
+ }
+ msg = new Buffer(mbuf.copy(0, mbuf.offset).toBinary(), 'binary')
+ return msg
+ }
+
+ if (!group) {
+ assert(private_memo, 'private_memo is required in private chats')
+ assert(to_public_memo, 'to_public_memo is required in private chats')
+
+ const fromKey = toPrivateObj(private_memo)
+ const toKey = toPublicObj(to_public_memo)
+
+ msg = msgToBuffer(msg)
+
+ let data = Aes.encrypt(fromKey,
+ toKey,
+ msg,
+ nonce)
+
+ return {
+ nonce: data.nonce.toString(),
+ encrypted_message: data.message.toString('hex'),
+ checksum: data.checksum,
+ from_memo_key: fromKey.toPublic().toString(),
+ to_memo_key: to_public_memo,
+ }
+ }
+
+ assert(!private_memo, 'private_memo is for private messages, not groups')
+ assert(!to_public_memo, 'to_public_memo is for private messages, not groups')
+
+ if (group.is_encrypted) {
+ if (!api) {
+ api = golosApi
+ }
+ const body = JSON.stringify(msg)
+
+ let res
+ {
+ debugMsgs('encryptBodyAsync')
+ res = await api.encryptBodyAsync({ group: group.name, body })
+ debugMsgs('encryptBodyAsync')
+ }
+
+ if (res.error) {
+ throw new Error(res.error)
+ }
+
+ let newBody = {}
+ newBody.t = 'em'
+ newBody.c = res.encrypted
+ msg = msgToBuffer(newBody)
+ } else {
+ msg = msgToBuffer(msg)
+ }
+
+ const encrypted_message = msg.toString('hex')
+ return {
+ nonce: Aes.uniqueNonce().toString(),
+ encrypted_message,
+ checksum: 0,
+ from_memo_key: emptyPublicKey,
+ to_memo_key: emptyPublicKey,
+ }
+}
+
/**
Selects messages by condition (e.g unread, or selected by user), and groups them into ranges with `nonce` (if range has 1 message) or `start_date`+`stop_date` (if range has few messages). Can wrap these ranges into operations: `private_mark_message` and `private_delete_message`.
@arg {array} message_objects - array of message objects. It can be result array from `golos.messages.decode`.
diff --git a/golos-lib-js/test/messages.test.js b/golos-lib-js/test/messages.test.js
index 0c8e12a..c2625d9 100644
--- a/golos-lib-js/test/messages.test.js
+++ b/golos-lib-js/test/messages.test.js
@@ -1,6 +1,6 @@
import { assert } from 'chai';
import cloneDeep from 'lodash/cloneDeep';
-import { encode, decode, newTextMsg, newImageMsgAsync, makeQuoteMsg,
+import { encode, encodeMsg, decode, decodeMsgs, newTextMsg, newImageMsgAsync, makeQuoteMsg,
DEFAULT_APP, DEFAULT_VERSION, MAX_PREVIEW_WIDTH, MAX_PREVIEW_HEIGHT,
MAX_TEXT_QUOTE_LENGTH, MAX_IMAGE_QUOTE_LENGTH } from '../src/auth/messages';
import th from './test_helper';
@@ -17,22 +17,24 @@ const bob = {
memo_pub: 'GLS67bFM2GtnEcrayTquHXdA8QdgRUgmGtUTQK24ez3uz4XLDShzc',
};
-describe('golos.messages: encode()', function() {
+describe('golos.messages: encodeMsg()', function() {
beforeEach(async function() {
await importNativeLib();
});
- it('input arguments', function() {
- assert.throws(() => encode(), 'from_private_memo_key is required');
- assert.throws(() => encode(alice.memo), 'to_public_memo_key is required');
- assert.throws(() => encode(alice.memo, bob.memo_pub), 'message is required');
+ it('input arguments', async function() {
+ await assert.isRejected(encodeMsg({}), 'msg is required');
+ await assert.isRejected(encodeMsg({ msg: 1 }), 'private_memo is required in private chats');
+ await assert.isRejected(encodeMsg({ msg: 1, private_memo: alice.memo }),
+ 'to_public_memo is required in private chats');
- assert.throws(() => encode(1, alice.memo, bob.memo_pub));
+ await assert.isRejected(encodeMsg({ msg: 1, private_memo: 1, to_public_memo: alice.memo }))
})
- it('normal case', function() {
+ it('normal case', async function() {
var msg = newTextMsg('Привет');
- var res = encode(alice.memo, bob.memo_pub, msg);
+ var res = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
assert.isString(res.nonce);
assert.isNotEmpty(res.nonce);
@@ -43,7 +45,7 @@ describe('golos.messages: encode()', function() {
assert.isNotEmpty(res.encrypted_message);
})
- it('cyrillic, emoji, etc', function() {
+ it('cyrillic, emoji, etc', async function() {
var veryLong = 'Очень';
for (let i = 0; i < 100; ++i) {
veryLong += ' длинный текст. Длинный текст.\nОчень';
@@ -63,76 +65,114 @@ describe('golos.messages: encode()', function() {
veryLong,
]) {
var msg = newTextMsg(str);
- var enc = encode(alice.memo, bob.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
msg.type = 'text';
- var res = decode(bob.memo, alice.memo_pub, [enc]);
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: [enc] })
assert.lengthOf(res, 1);
assert.deepStrictEqual(res[0].message, msg);
}
})
- it('alice -> alice, bob', function() {
+ it('alice -> alice, bob', async function() {
var msg = newTextMsg('Привет');
- var enc = encode(alice.memo, bob.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
msg.type = 'text';
- var res = decode(alice.memo, bob.memo_pub, [enc]);
+ var res = await decodeMsgs({ private_memo: alice.memo,
+ msgs: [enc] });
assert.lengthOf(res, 1);
+ console.error(res[0].raw_message)
+ console.error(res[0].message)
assert.deepStrictEqual(res[0].message, msg);
- var res = decode(bob.memo, alice.memo_pub, [enc]);
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: [enc] })
assert.lengthOf(res, 1);
assert.deepStrictEqual(res[0].message, msg);
})
- it('alice -> alice', function() {
+ it('alice -> alice', async function() {
var msg = newTextMsg('Привет');
- var enc = encode(alice.memo, alice.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: alice.memo_pub, msg })
msg.type = 'text';
- var res = decode(alice.memo, alice.memo_pub, [enc]);
+ var res = await decodeMsgs({ private_memo: alice.memo, msgs: [enc] })
assert.lengthOf(res, 1);
assert.deepStrictEqual(res[0].message, msg);
- var res = decode(bob.memo, alice.memo_pub, [enc]);
- assert.lengthOf(res, 1);
- assert.isNull(res[0].message);
-
- var res = decode(alice.memo, bob.memo_pub, [enc]);
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: [enc] })
assert.lengthOf(res, 1);
assert.isNull(res[0].message);
})
- it('edit case', function() {
+ it('edit case', async function() {
var msg = newTextMsg('Привет');
- var enc = encode(alice.memo, bob.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
msg.type = 'text';
- var res = decode(bob.memo, alice.memo_pub, [enc]);
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: [enc] })
assert.lengthOf(res, 1);
assert.deepStrictEqual(res[0].message, msg);
var msg = newTextMsg('Приветик');
- var enc = encode(alice.memo, bob.memo_pub, msg, enc.nonce);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg, nonce: enc.nonce })
msg.type = 'text';
- var res2 = decode(bob.memo, alice.memo_pub, [enc]);
+ var res2 = await decodeMsgs({ private_memo: bob.memo, msgs: [enc] })
assert.lengthOf(res2, 1);
assert.deepStrictEqual(res2[0].message, msg);
assert.strictEqual(res2[0].nonce, res[0].nonce);
assert.strictEqual(res2[0].checksum, res[0].checksum);
})
+
+ it('normal + edit case, with legacy encode() and decode()', async function() {
+ var msg = newTextMsg('Привет')
+ var enc = encode(alice.memo, bob.memo_pub, msg)
+ msg.type = 'text'
+
+ assert.isString(enc.nonce)
+ assert.isNotEmpty(enc.nonce)
+
+ assert.isTrue(Number.isInteger(enc.checksum))
+
+ assert.isString(enc.encrypted_message)
+ assert.isNotEmpty(enc.encrypted_message)
+
+ // these are need for compatibility with decodeMsgs()
+ enc.from_memo_key = alice.memo_pub
+ enc.to_memo_key = bob.memo_pub
+
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: [enc] })
+ assert.lengthOf(res, 1)
+ assert.deepStrictEqual(res[0].message, msg)
+
+ var msg = newTextMsg('Приветик')
+ var enc = encode(alice.memo, bob.memo_pub, msg, enc.nonce)
+ msg.type = 'text'
+
+ var res2 = decode(bob.memo, alice.memo_pub, [enc])
+ assert.lengthOf(res2, 1)
+ assert.deepStrictEqual(res2[0].message, msg)
+ assert.strictEqual(res2[0].nonce, res[0].nonce)
+ assert.strictEqual(res2[0].checksum, res[0].checksum)
+ })
})
-describe('golos.messages: decode()', function() {
+describe('golos.messages: decodeMsgs()', function() {
before(async function () {
var msg = newTextMsg('Привет');
- var msgEnc = encode(alice.memo, bob.memo_pub, msg);
+ var msgEnc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
msg.type = 'text'; // for comparison
var msg2 = await newImageMsgAsync(correctImageURL);
- var msgEnc2 = encode(alice.memo, bob.memo_pub, msg2);
+ var msgEnc2 = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: msg2 })
this._msgs = [
msg,
@@ -143,11 +183,13 @@ describe('golos.messages: decode()', function() {
this._msgObjs = [
msgEnc,
msgEnc2,
- encode(alice.memo, alice.memo_pub, {}),
- encode(alice.memo, bob.memo_pub, {}),
+ await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: alice.memo_pub, msg: {} }),
+ await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: {} }),
];
- this._decoded = decode(bob.memo, alice.memo_pub, this._msgObjs);
+ this._decoded = await decodeMsgs({ private_memo: bob.memo, msgs: this._msgObjs })
})
beforeEach(function() {
// encode/decode are slow, so we just cloning instead of recreating
@@ -157,14 +199,9 @@ describe('golos.messages: decode()', function() {
})
it('input arguments', async function() {
- assert.throws(() => decode(), 'private_memo_key is required');
- assert.throws(() => decode(null), 'private_memo_key is required');
- assert.throws(() => decode(alice.memo), 'second_user_public_memo_key is required');
- assert.throws(() => decode(alice.memo, null), 'second_user_public_memo_key is required');
- assert.throws(() => decode(alice.memo, bob.memo_pub), 'message_objects is required');
-
- assert.throws(() => decode('wrong key', bob.memo_pub, []), 'Non-base58 character');
- assert.throws(() => decode(alice.memo, 'wrong key', []));
+ await assert.isRejected(decodeMsgs(), 'argument should be an object with argument-field. See golos-lib-js documentation.');
+ await assert.isRejected(decodeMsgs({}), 'msgs is required')
+ await assert.isRejected(decodeMsgs({ msgs: [], private_memo: 'wrong key' }), 'Non-base58 character')
})
it('validation', async function() {
@@ -178,9 +215,12 @@ describe('golos.messages: decode()', function() {
// non-decodable
{
- let msg = encode(alice.memo, alice.memo_pub, normalText);
+ let msg = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: alice.memo_pub, msg: normalText })
messages.push({
nonce: msg.nonce,
+ from_memo_key: msg.from_memo_key,
+ to_memo_key: msg.to_memo_key,
checksum: 1,
encrypted_message: 'not encrypted',
});
@@ -191,16 +231,20 @@ describe('golos.messages: decode()', function() {
return data; // as it is
});
- messages.push(encode(alice.memo, bob.memo_pub, 'не json'));
+ messages.push(await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: 'не json' }));
JSON.stringify.restore();
}
// JSON, but not object
- messages.push(encode(alice.memo, bob.memo_pub, 'Привет'));
+ messages.push(await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: 'Привет' }))
// no body
- messages.push(encode(alice.memo, bob.memo_pub, {}));
+ messages.push(await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: {} }));
// no app, version
- messages.push(encode(alice.memo, bob.memo_pub, {body: 'Привет'}));
+ messages.push(await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: {body: 'Привет'} }));
// normal text msgs
for (let i = 0; i < 50 - 5; ++i) {
@@ -212,7 +256,7 @@ describe('golos.messages: decode()', function() {
messages.push(normalImageEnc);
}
- var res = decode(bob.memo, alice.memo_pub, messages);
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: messages })
assert.lengthOf(res, 100);
// non-decodable
@@ -242,10 +286,11 @@ describe('golos.messages: decode()', function() {
// With on_error
var on_error = sandbox.spy();
- var res2 = decode(bob.memo, alice.memo_pub, cloneDeep(messages),
- undefined, undefined, (msg, idx, err) => {
+ var res2 = await decodeMsgs({ private_memo: bob.memo, msgs: cloneDeep(messages),
+ on_error: (msg, idx, err) => {
on_error(msg, idx, err.message);
- });
+ }
+ })
on_error = on_error.getCalls();
assert.lengthOf(on_error, 5);
@@ -265,10 +310,10 @@ describe('golos.messages: decode()', function() {
// With raw_messages
var on_error_raw = sandbox.spy();
- var res3 = decode(bob.memo, alice.memo_pub, cloneDeep(messages),
- undefined, undefined, (msg, idx, err) => {
- on_error_raw(msg, idx, err.message);
- }, undefined, undefined, true);
+ var res3 = await decodeMsgs({ private_memo: bob.memo, msgs: cloneDeep(messages),
+ on_error: (msg, idx, err) => {
+ on_error_raw(msg, idx, err.message)
+ }, raw_messages: true})
on_error_raw = on_error_raw.getCalls();
assert.lengthOf(on_error_raw, 1);
@@ -308,31 +353,33 @@ describe('golos.messages: decode()', function() {
// normal
messages.push(normalImageEnc);
- var addMsg = ((breaker) => {
+ var addMsg = async (breaker) => {
var msg = Object.assign({}, normalImage);
breaker(msg);
- msg = encode(alice.memo, bob.memo_pub, msg);
+ msg = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
messages.push(msg);
- });
+ };
// no body
- addMsg(msg => delete msg.body);
+ await addMsg(msg => delete msg.body);
// no app
- addMsg(msg => delete msg.app);
+ await addMsg(msg => delete msg.app);
// previewWidth, previewHeight problems
- addMsg(msg => delete msg.previewWidth);
- addMsg(msg => msg.previewWidth = '12px');
- addMsg(msg => msg.previewWidth = 0);
- addMsg(msg => msg.previewWidth = MAX_PREVIEW_WIDTH + 1);
- addMsg(msg => delete msg.previewHeight);
- addMsg(msg => msg.previewHeight = '12px');
- addMsg(msg => msg.previewHeight = 0);
- addMsg(msg => msg.previewHeight = MAX_PREVIEW_HEIGHT + 1);
+ await addMsg(msg => delete msg.previewWidth);
+ await addMsg(msg => msg.previewWidth = '12px');
+ await addMsg(msg => msg.previewWidth = 0);
+ await addMsg(msg => msg.previewWidth = MAX_PREVIEW_WIDTH + 1);
+ await addMsg(msg => delete msg.previewHeight);
+ await addMsg(msg => msg.previewHeight = '12px');
+ await addMsg(msg => msg.previewHeight = 0);
+ await addMsg(msg => msg.previewHeight = MAX_PREVIEW_HEIGHT + 1);
var on_error = sandbox.spy();
- var res = decode(bob.memo, alice.memo_pub, messages,
- undefined, undefined, (msg, idx, err) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: messages,
+ on_error: (msg, idx, err) => {
on_error(msg, idx, err.message);
- });
+ }
+ })
on_error = on_error.getCalls();
assert.lengthOf(on_error, 10);
@@ -357,10 +404,11 @@ describe('golos.messages: decode()', function() {
it('on_error without return', async function() {
var on_error = sandbox.spy();
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined, undefined,
- (msg, i, ex) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ on_error: (msg, i, ex) => {
on_error(msg, i, ex.message);
- });
+ }
+ })
assert.deepStrictEqual(res, this.decoded);
on_error = on_error.getCalls();
@@ -370,18 +418,20 @@ describe('golos.messages: decode()', function() {
})
it('on_error with return false', async function() {
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined, undefined,
- (msg, i, ex) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ on_error: (msg, i, ex) => {
return false;
- });
+ }
+ })
assert.deepStrictEqual(res, this.decoded);
})
it('on_error with return true', async function() {
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined, undefined,
- (msg, i, ex) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ on_error: (msg, i, ex) => {
return true;
- });
+ }
+ })
assert.lengthOf(res, 2);
assert.deepStrictEqual(res[0].message, this.msgs[0]);
assert.deepStrictEqual(res[1].message, this.msgs[1]);
@@ -389,10 +439,11 @@ describe('golos.messages: decode()', function() {
it('for_each without return', async function() {
var for_each = sandbox.spy();
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined,
- (msg, i) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ for_each: (msg, i) => {
for_each(msg, i);
- });
+ }
+ })
assert.deepStrictEqual(res, this.decoded);
for_each = for_each.getCalls();
@@ -403,18 +454,20 @@ describe('golos.messages: decode()', function() {
})
it('for_each with return false', async function() {
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined,
- (msg, i) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ for_each: (msg, i) => {
return false;
- });
+ }
+ })
assert.deepStrictEqual(res, this.decoded);
})
it('for_each with return true', async function() {
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined,
- (msg, i) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ for_each: (msg, i) => {
if (i % 2 != 0) return true;
- });
+ }
+ })
assert.deepStrictEqual(res[0], this.decoded[0]);
assert.deepStrictEqual(res[1], this.decoded[2]);
})
@@ -422,12 +475,14 @@ describe('golos.messages: decode()', function() {
it('for_each with on_error shouldn\'t be called if error occured', async function() {
var for_each = sandbox.spy();
var on_error = sandbox.spy();
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined,
- (msg, i) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ for_each: (msg, i) => {
for_each(msg, i);
- }, (msg, i, exception) => {
+ },
+ on_error: (msg, i, exception) => {
on_error(msg, i, exception.message);
- });
+ }
+ })
assert.lengthOf(res, this.msgObjs.length);
for_each = for_each.getCalls();
@@ -443,13 +498,15 @@ describe('golos.messages: decode()', function() {
it('for_each throws, on_error decides push result or not', async function() {
var on_error = sandbox.spy();
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs, undefined,
- (msg, i) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ for_each: (msg, i) => {
throw new Error('for_each fail');
- }, (msg, i, exception) => {
+ },
+ on_error: (msg, i, exception) => {
on_error(msg, i, exception.message);
if (i === 2) return true; // do not push
- });
+ }
+ })
assert.lengthOf(res, 3);
@@ -466,11 +523,12 @@ describe('golos.messages: decode()', function() {
for (let obj of this.decoded) obj.field = 'test';
var before_decode = sandbox.spy();
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs,
- (msg, i, results) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ before_decode: (msg, i, results) => {
msg.field = 'test';
before_decode(msg, i, results);
- });
+ }
+ })
assert.deepStrictEqual(res, this.decoded);
@@ -487,11 +545,12 @@ describe('golos.messages: decode()', function() {
for (let obj of this.decoded) obj.field = 'test';
// before_decode with return false
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs,
- (msg, i, results) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ before_decode: (msg, i, results) => {
msg.field = 'test';
return false;
- });
+ }
+ })
assert.deepStrictEqual(res, this.decoded);
})
@@ -500,11 +559,12 @@ describe('golos.messages: decode()', function() {
// for comparison
for (let obj of this.decoded) obj.field = 'test';
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs,
- (msg, i, results) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ before_decode: (msg, i, results) => {
msg.field = 'test';
return i % 2 != 0;
- });
+ }
+ })
assert.lengthOf(res, 2);
assert.deepStrictEqual(res[0], this.decoded[0]);
@@ -513,11 +573,12 @@ describe('golos.messages: decode()', function() {
it('before_decode throws without on_error', async function() {
var before_decode = sandbox.spy();
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs,
- (msg, i, results) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ before_decode: (msg, i, results) => {
before_decode(msg, i, results);
throw new Error('before_decode fail');
- });
+ }
+ })
before_decode = before_decode.getCalls();
@@ -531,13 +592,15 @@ describe('golos.messages: decode()', function() {
})
it('before_decode throws with on_error', async function() {
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs,
- (msg, i, results) => {
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ before_decode: (msg, i, results) => {
throw new Error('before_decode fail');
- }, undefined, (msg, i, err) => {
+ },
+ on_error: (msg, i, err) => {
assert.equal(err.message, 'before_decode fail');
msg.message = 'alt msg';
- });
+ }
+ })
assert.lengthOf(res, this.msgObjs.length);
for (let i = 0; i < res.length; ++i) {
@@ -548,25 +611,25 @@ describe('golos.messages: decode()', function() {
it('ordering + slicing', async function() {
// default case
- var res = decode(bob.memo, alice.memo_pub, this.msgObjs);
+ var res = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs })
// reversed order case
- var resRev = decode(bob.memo, alice.memo_pub, this.msgObjs,
- undefined, undefined, undefined, this.msgObjs.length - 1, -1);
+ var resRev = await decodeMsgs({ private_memo: bob.memo, msgs: this.msgObjs,
+ begin_idx: this.msgObjs.length - 1, end_idx: -1 })
assert.deepStrictEqual([...resRev].reverse(), res);
// reversed + slicing
- var resSl = decode(bob.memo, alice.memo_pub, cloneDeep(this.msgObjs),
- undefined, undefined, undefined, this.msgObjs.length - 2, 0);
+ var resSl = await decodeMsgs({ private_memo: bob.memo, msgs: cloneDeep(this.msgObjs),
+ begin_idx: this.msgObjs.length - 2, end_idx: 0 })
assert.lengthOf(resSl, 2);
assert.deepStrictEqual(resSl[0], resRev[1]);
assert.deepStrictEqual(resSl[1], resRev[2]);
// default + slicing by begin_idx only
- var resSl = decode(bob.memo, alice.memo_pub, cloneDeep(this.msgObjs),
- undefined, undefined, undefined, 2);
+ var resSl = await decodeMsgs({ private_memo: bob.memo, msgs: cloneDeep(this.msgObjs),
+ begin_idx: 2 })
assert.lengthOf(resSl, 2);
assert.deepStrictEqual(resSl[0], res[2]);
assert.deepStrictEqual(resSl[1], res[3]);
@@ -574,23 +637,25 @@ describe('golos.messages: decode()', function() {
})
describe('golos.messages: decode() replies', function() {
- it('message', function() {
+ it('message', async function() {
var msg = newTextMsg('Привет');
- var enc = encode(alice.memo, bob.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
const orig = Object.assign({
from: 'alice',
}, enc);
- var origDecoded = decode(alice.memo, bob.memo_pub, [orig]);
+ var origDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig] })
var msg2 = newTextMsg('Hi');
msg2 = makeQuoteMsg(msg2, origDecoded[0]);
- var enc2 = encode(alice.memo, bob.memo_pub, msg2);
+ var enc2 = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: msg2 })
const reply = Object.assign({
from: 'bob',
}, enc2);
- var bothDecoded = decode(alice.memo, bob.memo_pub, [orig, enc2]);
+ var bothDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig, enc2] })
assert.lengthOf(bothDecoded, 2);
assert.strictEqual(bothDecoded[1].message.quote.from, bothDecoded[0].from);
assert.strictEqual(bothDecoded[1].message.quote.nonce, bothDecoded[0].nonce);
@@ -598,25 +663,27 @@ describe('golos.messages: decode() replies', function() {
assert.strictEqual(bothDecoded[1].message.quote.type, bothDecoded[0].message.type);
})
- it('too long message', function() {
+ it('too long message', async function() {
var msg = newTextMsg('a'.repeat(MAX_TEXT_QUOTE_LENGTH + 1));
- var enc = encode(alice.memo, bob.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
const orig = Object.assign({
from: 'alice',
}, enc);
- var origDecoded = decode(alice.memo, bob.memo_pub, [orig]);
+ var origDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig] })
var msg2 = newTextMsg('Hi');
msg2 = makeQuoteMsg(msg2, origDecoded[0]);
// keep its original length to make message invalid
msg2.quote.body = 'a'.repeat(MAX_TEXT_QUOTE_LENGTH + 1);
- var enc2 = encode(alice.memo, bob.memo_pub, msg2);
+ var enc2 = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: msg2 })
const reply = Object.assign({
from: 'bob',
}, enc2);
- var bothDecoded = decode(alice.memo, bob.memo_pub, [orig, enc2]);
+ var bothDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig, enc2] })
assert.lengthOf(bothDecoded, 2);
assert.isNotNull(bothDecoded[1].raw_message);
assert.isNull(bothDecoded[1].message);
@@ -626,21 +693,23 @@ describe('golos.messages: decode() replies', function() {
assert.isTrue(correctImageURL.length > MAX_TEXT_QUOTE_LENGTH, 'too short correctImageURL for this test');
var msg = await newImageMsgAsync(correctImageURL);
- var enc = encode(alice.memo, bob.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
const orig = Object.assign({
from: 'alice',
}, enc);
- var origDecoded = decode(alice.memo, bob.memo_pub, [orig]);
+ var origDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig] })
var msg2 = newTextMsg('Hi');
msg2 = makeQuoteMsg(msg2, origDecoded[0]);
- var enc2 = encode(alice.memo, bob.memo_pub, msg2);
+ var enc2 = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: msg2 })
const reply = Object.assign({
from: 'bob',
}, enc2);
- var bothDecoded = decode(alice.memo, bob.memo_pub, [orig, enc2]);
+ var bothDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig, enc2] })
assert.lengthOf(bothDecoded, 2);
assert.strictEqual(bothDecoded[1].message.quote.from, bothDecoded[0].from);
assert.strictEqual(bothDecoded[1].message.quote.nonce, bothDecoded[0].nonce);
@@ -657,21 +726,23 @@ describe('golos.messages: decode() replies', function() {
previewWidth: MAX_PREVIEW_WIDTH,
previewHeight: MAX_PREVIEW_HEIGHT,
};
- var enc = encode(alice.memo, bob.memo_pub, msg);
+ var enc = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg })
const orig = Object.assign({
from: 'alice',
}, enc);
- var origDecoded = decode(alice.memo, bob.memo_pub, [orig]);
+ var origDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig] })
var msg2 = newTextMsg('Hi');
msg2 = makeQuoteMsg(msg2, origDecoded[0]);
- var enc2 = encode(alice.memo, bob.memo_pub, msg2);
+ var enc2 = await encodeMsg({ private_memo: alice.memo,
+ to_public_memo: bob.memo_pub, msg: msg2 })
const reply = Object.assign({
from: 'bob',
}, enc2);
- var bothDecoded = decode(alice.memo, bob.memo_pub, [orig, enc2]);
+ var bothDecoded = await decodeMsgs({ private_memo: alice.memo, msgs: [orig, enc2] })
assert.lengthOf(bothDecoded, 2);
assert.strictEqual(bothDecoded[1].message.quote.from, bothDecoded[0].from);
assert.strictEqual(bothDecoded[1].message.quote.nonce, bothDecoded[0].nonce);
diff --git a/golos-lib-js/test/methods_by_version.js b/golos-lib-js/test/methods_by_version.js
index dc4eef0..fb279fd 100644
--- a/golos-lib-js/test/methods_by_version.js
+++ b/golos-lib-js/test/methods_by_version.js
@@ -135,5 +135,6 @@ export const methods_0_25_3 = [
"get_nft_orders",
"get_nft_bets",
"encrypt_body",
- "decrypt_comments"
+ "decrypt_comments",
+ "decrypt_messages"
]