use common package

This commit is contained in:
Daniel Lugo 2020-05-17 15:36:59 -04:00
parent 00e143ab60
commit 58a69977db
18 changed files with 140 additions and 814 deletions

View file

@ -23,9 +23,6 @@ const mySEA = {}
const $$__SHOCKWALLET__MSG__ = '$$__SHOCKWALLET__MSG__' const $$__SHOCKWALLET__MSG__ = '$$__SHOCKWALLET__MSG__'
const $$__SHOCKWALLET__ENCRYPTED__ = '$$_SHOCKWALLET__ENCRYPTED__' const $$__SHOCKWALLET__ENCRYPTED__ = '$$_SHOCKWALLET__ENCRYPTED__'
// TO DO: Move this constant to common repo
const IS_GUN_AUTH = 'IS_GUN_AUTH'
mySEA.encrypt = (msg, secret) => { mySEA.encrypt = (msg, secret) => {
if (typeof msg !== 'string') { if (typeof msg !== 'string') {
throw new TypeError( throw new TypeError(
@ -172,10 +169,11 @@ mySEA.secret = async (recipientOrSenderEpub, recipientOrSenderSEA) => {
const auth = require('../../auth/auth') const auth = require('../../auth/auth')
const Action = require('../action-constants.js') const { Constants } = require('shock-common')
const { Action, Event } = Constants
const API = require('../contact-api/index') const API = require('../contact-api/index')
const Config = require('../config') const Config = require('../config')
const Event = require('../event-constants')
// const { nonEncryptedRoutes } = require('../../../utils/protectedRoutes') // const { nonEncryptedRoutes } = require('../../../utils/protectedRoutes')
/** /**
@ -441,7 +439,7 @@ class Mediator {
this.socket.on(Event.ON_BIO, this.onBio) this.socket.on(Event.ON_BIO, this.onBio)
this.socket.on(Event.ON_SEED_BACKUP, this.onSeedBackup) this.socket.on(Event.ON_SEED_BACKUP, this.onSeedBackup)
this.socket.on(IS_GUN_AUTH, this.isGunAuth) this.socket.on(Constants.Misc.IS_GUN_AUTH, this.isGunAuth)
this.socket.on(Action.SET_LAST_SEEN_APP, this.setLastSeenApp) this.socket.on(Action.SET_LAST_SEEN_APP, this.setLastSeenApp)
@ -557,7 +555,7 @@ class Mediator {
try { try {
const isGunAuth = isAuthenticated() const isGunAuth = isAuthenticated()
this.socket.emit(IS_GUN_AUTH, { this.socket.emit(Constants.Misc.IS_GUN_AUTH, {
ok: true, ok: true,
msg: { msg: {
isGunAuth isGunAuth
@ -565,7 +563,7 @@ class Mediator {
origBody: {} origBody: {}
}) })
} catch (err) { } catch (err) {
this.socket.emit(IS_GUN_AUTH, { this.socket.emit(Constants.Misc.IS_GUN_AUTH, {
ok: false, ok: false,
msg: err.message, msg: err.message,
origBody: {} origBody: {}

View file

@ -1,16 +0,0 @@
const Actions = {
ACCEPT_REQUEST: "ACCEPT_REQUEST",
BLACKLIST: "BLACKLIST",
GENERATE_NEW_HANDSHAKE_NODE: "GENERATE_NEW_HANDSHAKE_NODE",
SEND_HANDSHAKE_REQUEST: "SEND_HANDSHAKE_REQUEST",
SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG: "SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG",
SEND_MESSAGE: "SEND_MESSAGE",
SEND_PAYMENT: "SEND_PAYMENT",
SET_AVATAR: "SET_AVATAR",
SET_DISPLAY_NAME: "SET_DISPLAY_NAME",
SET_BIO: "SET_BIO",
DISCONNECT: "DISCONNECT",
SET_LAST_SEEN_APP: "SET_LAST_SEEN_APP"
};
module.exports = Actions;

View file

@ -3,37 +3,29 @@
*/ */
const uuidv1 = require('uuid/v1') const uuidv1 = require('uuid/v1')
const logger = require('winston') const logger = require('winston')
const { Constants, Schema } = require('shock-common')
const { ErrorCode } = Constants
const LightningServices = require('../../../utils/lightningServices') const LightningServices = require('../../../utils/lightningServices')
const ErrorCode = require('./errorCode')
const Getters = require('./getters') const Getters = require('./getters')
const Key = require('./key') const Key = require('./key')
const Utils = require('./utils') const Utils = require('./utils')
// const { promisifyGunNode: p } = Utils
const {
isHandshakeRequest,
isOrderResponse,
encodeSpontaneousPayment
} = require('./schema')
/** /**
* @typedef {import('./SimpleGUN').GUNNode} GUNNode * @typedef {import('./SimpleGUN').GUNNode} GUNNode
* @typedef {import('./SimpleGUN').ISEA} ISEA * @typedef {import('./SimpleGUN').ISEA} ISEA
* @typedef {import('./SimpleGUN').UserGUNNode} UserGUNNode * @typedef {import('./SimpleGUN').UserGUNNode} UserGUNNode
* @typedef {import('./schema').HandshakeRequest} HandshakeRequest * @typedef {import('shock-common').Schema.HandshakeRequest} HandshakeRequest
* @typedef {import('./schema').StoredRequest} StoredReq * @typedef {import('shock-common').Schema.StoredRequest} StoredReq
* @typedef {import('./schema').Message} Message * @typedef {import('shock-common').Schema.Message} Message
* @typedef {import('./schema').Outgoing} Outgoing * @typedef {import('shock-common').Schema.Outgoing} Outgoing
* @typedef {import('./schema').PartialOutgoing} PartialOutgoing * @typedef {import('shock-common').Schema.PartialOutgoing} PartialOutgoing
* @typedef {import('./schema').Order} Order * @typedef {import('shock-common').Schema.Order} Order
* @typedef {import('./SimpleGUN').Ack} Ack * @typedef {import('./SimpleGUN').Ack} Ack
*/ */
/**
* An special message signaling the acceptance.
*/
const INITIAL_MSG = '$$__SHOCKWALLET__INITIAL__MESSAGE'
/** /**
* Create a an outgoing feed. The feed will have an initial special acceptance * Create a an outgoing feed. The feed will have an initial special acceptance
* message. Returns a promise that resolves to the id of the newly-created * message. Returns a promise that resolves to the id of the newly-created
@ -92,7 +84,7 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
/** @type {Message} */ /** @type {Message} */
const initialMsg = { const initialMsg = {
body: await SEA.encrypt(INITIAL_MSG, ourSecret), body: await SEA.encrypt(Constants.Misc.INITIAL_MSG, ourSecret),
timestamp: Date.now() timestamp: Date.now()
} }
@ -203,7 +195,7 @@ const acceptRequest = async (
.get(requestID) .get(requestID)
.then() .then()
if (!isHandshakeRequest(hr)) { if (!Schema.isHandshakeRequest(hr)) {
throw new Error(ErrorCode.TRIED_TO_ACCEPT_AN_INVALID_REQUEST) throw new Error(ErrorCode.TRIED_TO_ACCEPT_AN_INVALID_REQUEST)
} }
@ -981,7 +973,7 @@ const sendPayment = async (to, amount, memo) => {
) )
/** /**
* @type {import('./schema').OrderResponse} * @type {import('shock-common').Schema.OrderResponse}
*/ */
const encryptedOrderRes = await Promise.race([ const encryptedOrderRes = await Promise.race([
Promise.race([onMethod, freshGunMethod]).then(v => { Promise.race([onMethod, freshGunMethod]).then(v => {
@ -996,14 +988,14 @@ const sendPayment = async (to, amount, memo) => {
}) })
]) ])
if (!isOrderResponse(encryptedOrderRes)) { if (!Schema.isOrderResponse(encryptedOrderRes)) {
throw new Error( throw new Error(
'received response not an OrderResponse, instead got: ' + 'received response not an OrderResponse, instead got: ' +
JSON.stringify(encryptedOrderRes) JSON.stringify(encryptedOrderRes)
) )
} }
/** @type {import('./schema').OrderResponse} */ /** @type {import('shock-common').Schema.OrderResponse} */
const orderResponse = { const orderResponse = {
response: await SEA.decrypt(encryptedOrderRes.response, ourSecret), response: await SEA.decrypt(encryptedOrderRes.response, ourSecret),
type: encryptedOrderRes.type type: encryptedOrderRes.type
@ -1075,7 +1067,7 @@ const sendPayment = async (to, amount, memo) => {
if (Utils.successfulHandshakeAlreadyExists(to)) { if (Utils.successfulHandshakeAlreadyExists(to)) {
await sendMessage( await sendMessage(
to, to,
encodeSpontaneousPayment(amount, memo || 'no memo', preimage), Schema.encodeSpontaneousPayment(amount, memo || 'no memo', preimage),
require('../Mediator').getUser(), require('../Mediator').getUser(),
require('../Mediator').mySEA require('../Mediator').mySEA
) )

View file

@ -1,45 +0,0 @@
/**
* @prettier
*/
exports.ALREADY_AUTH = 'ALREADY_AUTH'
exports.NOT_AUTH = 'NOT_AUTH'
exports.COULDNT_ACCEPT_REQUEST = 'COULDNT_ACCEPT_REQUEST'
exports.COULDNT_SENT_REQUEST = 'COULDNT_SENT_REQUEST'
exports.COULDNT_PUT_REQUEST_RESPONSE = 'COULDNT_PUT_REQUEST_RESPONSE'
/**
* Error thrown when trying to accept a request, and on retrieval of that
* request invalid data (not resembling a request) is received.
*/
exports.TRIED_TO_ACCEPT_AN_INVALID_REQUEST =
'TRIED_TO_ACCEPT_AN_INVALID_REQUEST'
exports.UNSUCCESSFUL_LOGOUT = 'UNSUCCESSFUL_LOGOUT'
exports.UNSUCCESSFUL_REQUEST_ACCEPT = 'UNSUCCESSFUL_REQUEST_ACCEPT'
/**
* Error thrown when trying to send a handshake request to an user for which
* there's already an successful handshake.
*/
exports.ALREADY_HANDSHAKED = 'ALREADY_HANDSHAKED'
/**
* Error thrown when trying to send a handshake request to an user for which
* there's already a handshake request on his current handshake node.
*/
exports.ALREADY_REQUESTED_HANDSHAKE = 'ALREADY_REQUESTED_HANDSHAKE'
/**
* Error thrown when trying to send a handshake request to an user on an stale
* handshake address.
*/
exports.STALE_HANDSHAKE_ADDRESS = 'STALE_HANDSHAKE_ADDRESS'
exports.TIMEOUT_ERR = 'TIMEOUT_ERR'
exports.ORDER_NOT_ANSWERED_IN_TIME = 'ORDER_NOT_ANSWERED_IN_TIME'

View file

@ -3,24 +3,27 @@
*/ */
const debounce = require('lodash/debounce') const debounce = require('lodash/debounce')
const logger = require('winston') const logger = require('winston')
const {
Constants: { ErrorCode },
Schema,
Utils: CommonUtils
} = require('shock-common')
const ErrorCode = require('../errorCode')
const Key = require('../key') const Key = require('../key')
const Schema = require('../schema')
const Utils = require('../utils') const Utils = require('../utils')
/** /**
* @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode * @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode
* @typedef {import('../SimpleGUN').GUNNode} GUNNode * @typedef {import('../SimpleGUN').GUNNode} GUNNode
* @typedef {import('../SimpleGUN').ISEA} ISEA * @typedef {import('../SimpleGUN').ISEA} ISEA
* @typedef {import('../SimpleGUN').ListenerData} ListenerData * @typedef {import('../SimpleGUN').ListenerData} ListenerData
* @typedef {import('../schema').HandshakeRequest} HandshakeRequest * @typedef {import('shock-common').Schema.HandshakeRequest} HandshakeRequest
* @typedef {import('../schema').Message} Message * @typedef {import('shock-common').Schema.Message} Message
* @typedef {import('../schema').Outgoing} Outgoing * @typedef {import('shock-common').Schema.Outgoing} Outgoing
* @typedef {import('../schema').PartialOutgoing} PartialOutgoing * @typedef {import('shock-common').Schema.PartialOutgoing} PartialOutgoing
* @typedef {import('../schema').Chat} Chat * @typedef {import('shock-common').Schema.Chat} Chat
* @typedef {import('../schema').ChatMessage} ChatMessage * @typedef {import('shock-common').Schema.ChatMessage} ChatMessage
* @typedef {import('../schema').SimpleSentRequest} SimpleSentRequest * @typedef {import('shock-common').Schema.SimpleSentRequest} SimpleSentRequest
* @typedef {import('../schema').SimpleReceivedRequest} SimpleReceivedRequest * @typedef {import('shock-common').Schema.SimpleReceivedRequest} SimpleReceivedRequest
*/ */
const DEBOUNCE_WAIT_TIME = 500 const DEBOUNCE_WAIT_TIME = 500
@ -360,59 +363,62 @@ const onOutgoing = cb => {
const SEA = require('../../Mediator').mySEA const SEA = require('../../Mediator').mySEA
const mySecret = await Utils.mySecret() const mySecret = await Utils.mySecret()
await Utils.asyncForEach(Object.entries(data), async ([id, out]) => { await CommonUtils.asyncForEach(
if (typeof out !== 'object') { Object.entries(data),
return async ([id, out]) => {
} if (typeof out !== 'object') {
return
if (out === null) {
newOuts[id] = null
return
}
const { with: encPub, messages } = out
if (typeof encPub !== 'string') {
return
}
const pub = await SEA.decrypt(encPub, mySecret)
if (!newOuts[id]) {
newOuts[id] = {
with: pub,
messages: {}
} }
}
const ourSec = await SEA.secret( if (out === null) {
await Utils.pubToEpub(pub), newOuts[id] = null
user._.sea return
) }
if (typeof messages === 'object' && messages !== null) { const { with: encPub, messages } = out
await Utils.asyncForEach(
Object.entries(messages), if (typeof encPub !== 'string') {
async ([mid, msg]) => { return
if (typeof msg === 'object' && msg !== null) { }
if (
typeof msg.body === 'string' && const pub = await SEA.decrypt(encPub, mySecret)
typeof msg.timestamp === 'number'
) { if (!newOuts[id]) {
const newOut = newOuts[id] newOuts[id] = {
if (!newOut) { with: pub,
return messages: {}
} }
newOut.messages[mid] = { }
body: await SEA.decrypt(msg.body, ourSec),
timestamp: msg.timestamp const ourSec = await SEA.secret(
await Utils.pubToEpub(pub),
user._.sea
)
if (typeof messages === 'object' && messages !== null) {
await CommonUtils.asyncForEach(
Object.entries(messages),
async ([mid, msg]) => {
if (typeof msg === 'object' && msg !== null) {
if (
typeof msg.body === 'string' &&
typeof msg.timestamp === 'number'
) {
const newOut = newOuts[id]
if (!newOut) {
return
}
newOut.messages[mid] = {
body: await SEA.decrypt(msg.body, ourSec),
timestamp: msg.timestamp
}
} }
} }
} }
} )
) }
} }
}) )
currentOutgoings = newOuts currentOutgoings = newOuts
notifyOutgoingsListeners() notifyOutgoingsListeners()

View file

@ -1,13 +1,13 @@
/** @format */ /** @format */
const debounce = require('lodash/debounce') const debounce = require('lodash/debounce')
const logger = require('winston') const logger = require('winston')
const { Schema } = require('shock-common')
const Key = require('../key') const Key = require('../key')
const Schema = require('../schema')
const Streams = require('../streams') const Streams = require('../streams')
/** /**
* @typedef {Readonly<Schema.SimpleReceivedRequest>} SimpleReceivedRequest * @typedef {Readonly<import('shock-common').Schema.SimpleReceivedRequest>} SimpleReceivedRequest
* @typedef {(reqs: ReadonlyArray<SimpleReceivedRequest>) => void} Listener * @typedef {(reqs: ReadonlyArray<SimpleReceivedRequest>) => void} Listener
*/ */
@ -22,7 +22,7 @@ let currReceivedReqsMap = {}
/** /**
* Unprocessed requests in current handshake node. * Unprocessed requests in current handshake node.
* @type {Record<string, Schema.HandshakeRequest>} * @type {Record<string, import('shock-common').Schema.HandshakeRequest>}
*/ */
let currAddressData = {} let currAddressData = {}

View file

@ -8,14 +8,14 @@ const Streams = require('../streams')
* @typedef {import('../SimpleGUN').GUNNode} GUNNode * @typedef {import('../SimpleGUN').GUNNode} GUNNode
* @typedef {import('../SimpleGUN').ISEA} ISEA * @typedef {import('../SimpleGUN').ISEA} ISEA
* @typedef {import('../SimpleGUN').ListenerData} ListenerData * @typedef {import('../SimpleGUN').ListenerData} ListenerData
* @typedef {import('../schema').HandshakeRequest} HandshakeRequest * @typedef {import('shock-common').Schema.HandshakeRequest} HandshakeRequest
* @typedef {import('../schema').Message} Message * @typedef {import('shock-common').Schema.Message} Message
* @typedef {import('../schema').Outgoing} Outgoing * @typedef {import('shock-common').Schema.Outgoing} Outgoing
* @typedef {import('../schema').PartialOutgoing} PartialOutgoing * @typedef {import('shock-common').Schema.PartialOutgoing} PartialOutgoing
* @typedef {import('../schema').Chat} Chat * @typedef {import('shock-common').Schema.Chat} Chat
* @typedef {import('../schema').ChatMessage} ChatMessage * @typedef {import('shock-common').Schema.ChatMessage} ChatMessage
* @typedef {import('../schema').SimpleSentRequest} SimpleSentRequest * @typedef {import('shock-common').Schema.SimpleSentRequest} SimpleSentRequest
* @typedef {import('../schema').SimpleReceivedRequest} SimpleReceivedRequest * @typedef {import('shock-common').Schema.SimpleReceivedRequest} SimpleReceivedRequest
*/ */
/** /**

View file

@ -5,6 +5,5 @@ const Actions = require('./actions')
const Events = require('./events') const Events = require('./events')
const Jobs = require('./jobs') const Jobs = require('./jobs')
const Key = require('./key') const Key = require('./key')
const Schema = require('./schema')
module.exports = { Actions, Events, Jobs, Key, Schema } module.exports = { Actions, Events, Jobs, Key }

View file

@ -4,9 +4,13 @@
const logger = require('winston') const logger = require('winston')
const ErrorCode = require('../errorCode') const {
Constants: {
ErrorCode,
Misc: { LAST_SEEN_NODE_INTERVAL }
}
} = require('shock-common')
const Key = require('../key') const Key = require('../key')
const { LAST_SEEN_NODE_INTERVAL } = require('../utils')
/** /**
* @typedef {import('../SimpleGUN').GUNNode} GUNNode * @typedef {import('../SimpleGUN').GUNNode} GUNNode

View file

@ -2,10 +2,12 @@
* @format * @format
*/ */
const logger = require('winston') const logger = require('winston')
const {
Constants: { ErrorCode },
Schema
} = require('shock-common')
const ErrorCode = require('../errorCode')
const Key = require('../key') const Key = require('../key')
const Schema = require('../schema')
const Utils = require('../utils') const Utils = require('../utils')
/** /**

View file

@ -6,11 +6,14 @@ const logger = require('winston')
const isFinite = require('lodash/isFinite') const isFinite = require('lodash/isFinite')
const isNumber = require('lodash/isNumber') const isNumber = require('lodash/isNumber')
const isNaN = require('lodash/isNaN') const isNaN = require('lodash/isNaN')
const {
Constants: { ErrorCode },
Schema
} = require('shock-common')
const LightningServices = require('../../../../utils/lightningServices') const LightningServices = require('../../../../utils/lightningServices')
const ErrorCode = require('../errorCode')
const Key = require('../key') const Key = require('../key')
const Schema = require('../schema')
const Utils = require('../utils') const Utils = require('../utils')
const getUser = () => require('../../Mediator').getUser() const getUser = () => require('../../Mediator').getUser()
@ -127,7 +130,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
`onOrders() -> Will now place the encrypted invoice in order to response usergraph: ${addr}` `onOrders() -> Will now place the encrypted invoice in order to response usergraph: ${addr}`
) )
/** @type {Schema.OrderResponse} */ /** @type {import('shock-common').Schema.OrderResponse} */
const orderResponse = { const orderResponse = {
response: encInvoice, response: encInvoice,
type: 'invoice' type: 'invoice'
@ -157,7 +160,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
) )
logger.error(err) logger.error(err)
/** @type {Schema.OrderResponse} */ /** @type {import('shock-common').Schema.OrderResponse} */
const orderResponse = { const orderResponse = {
response: err.message, response: err.message,
type: 'err' type: 'err'

View file

@ -1,15 +0,0 @@
/**
* @format
* Contains types that are used throughout the application. Written in
* typescript for easier implementation.
*
* Nominal types are archieved through the enum method as outlined here:
* https://basarat.gitbook.io/typescript/main-1/nominaltyping
*/
enum _EncSpontPayment {
_ = ''
}
/**
* Spontaneous payment as found inside a chat message body.
*/
export type EncSpontPayment = _EncSpontPayment & string

View file

@ -1,531 +0,0 @@
/**
* @format
*/
const logger = require('winston')
const isFinite = require('lodash/isFinite')
const isNumber = require('lodash/isNumber')
const isNaN = require('lodash/isNaN')
/**
* @typedef {object} HandshakeRequest
* @prop {string} from Public key of the requestor.
* @prop {string} response Encrypted string where, if the recipient accepts the
* request, his outgoing feed id will be put. Before that the sender's outgoing
* feed ID will be placed here, encrypted so only the recipient can access it.
* @prop {number} timestamp Unix time.
*/
/**
* @typedef {object} Message
* @prop {string} body
* @prop {number} timestamp
*/
/**
* @typedef {object} ChatMessage
* @prop {string} body
* @prop {string} id
* @prop {boolean} outgoing True if the message is an outgoing message,
* otherwise it is an incoming message.
* @prop {number} timestamp
*/
/**
*
* @param {any} item
* @returns {item is ChatMessage}
*/
exports.isChatMessage = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {ChatMessage} */ (item)
if (typeof obj.body !== 'string') {
return false
}
if (typeof obj.id !== 'string') {
return false
}
if (typeof obj.outgoing !== 'boolean') {
return false
}
if (typeof obj.timestamp !== 'number') {
return false
}
return true
}
/**
* A simpler representation of a conversation between two users than the
* outgoing/incoming feed paradigm. It combines both the outgoing and incoming
* messages into one data structure plus metada about the chat.
* @typedef {object} Chat
* @prop {string} id Chats now have IDs because of disconnect.
* RecipientPublicKey will no longer be unique.
* @prop {string|null} recipientAvatar Base64 encoded image.
* @prop {string} recipientPublicKey A way to uniquely identify each chat.
* @prop {ChatMessage[]} messages Sorted from most recent to least recent.
* @prop {string|null} recipientDisplayName
* @prop {boolean} didDisconnect True if the recipient performed a disconnect.
* @prop {number|undefined|null} lastSeenApp
*/
/**
* @param {any} item
* @returns {item is Chat}
*/
exports.isChat = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {Chat} */ (item)
if (typeof obj.recipientAvatar !== 'string' && obj.recipientAvatar !== null) {
return false
}
if (!Array.isArray(obj.messages)) {
return false
}
if (typeof obj.recipientPublicKey !== 'string') {
return false
}
if (obj.recipientPublicKey.length === 0) {
return false
}
if (typeof obj.didDisconnect !== 'boolean') {
return false
}
if (typeof obj.id !== 'string') {
return false
}
return obj.messages.every(msg => exports.isChatMessage(msg))
}
/**
* @typedef {object} Outgoing
* @prop {Record<string, Message>} messages
* @prop {string} with Public key for whom the outgoing messages are intended.
*/
/**
* @typedef {object} PartialOutgoing
* @prop {string} with (Encrypted) Public key for whom the outgoing messages are
* intended.
*/
/**
* @typedef {object} StoredRequest
* @prop {string} sentReqID
* @prop {string} recipientPub
* @prop {string} handshakeAddress
* @prop {number} timestamp
*/
/**
* @param {any} item
* @returns {item is StoredRequest}
*/
exports.isStoredRequest = item => {
if (typeof item !== 'object') return false
if (item === null) return false
const obj = /** @type {StoredRequest} */ (item)
if (typeof obj.recipientPub !== 'string') return false
if (typeof obj.handshakeAddress !== 'string') return false
if (typeof obj.handshakeAddress !== 'string') return false
if (typeof obj.timestamp !== 'number') return false
return true
}
/**
* @typedef {object} SimpleSentRequest
* @prop {string} id
* @prop {string|null} recipientAvatar
* @prop {boolean} recipientChangedRequestAddress True if the recipient changed
* the request node address and therefore can't no longer accept the request.
* @prop {string|null} recipientDisplayName
* @prop {string} recipientPublicKey Fallback for when user has no display name.
* @prop {number} timestamp
*/
/**
* @param {any} item
* @returns {item is SimpleSentRequest}
*/
exports.isSimpleSentRequest = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {SimpleSentRequest} */ (item)
if (typeof obj.id !== 'string') {
return false
}
if (typeof obj.recipientAvatar !== 'string' && obj.recipientAvatar !== null) {
return false
}
if (typeof obj.recipientChangedRequestAddress !== 'boolean') {
return false
}
if (
typeof obj.recipientDisplayName !== 'string' &&
obj.recipientDisplayName !== null
) {
return false
}
if (typeof obj.recipientPublicKey !== 'string') {
return false
}
if (typeof obj.timestamp !== 'number') {
return false
}
return true
}
/**
* @typedef {object} SimpleReceivedRequest
* @prop {string} id
* @prop {string|null} requestorAvatar
* @prop {string|null} requestorDisplayName
* @prop {string} requestorPK
* @prop {number} timestamp
*/
/**
* @param {any} item
* @returns {item is SimpleReceivedRequest}
*/
exports.isSimpleReceivedRequest = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {SimpleReceivedRequest} */ (item)
if (typeof obj.id !== 'string') {
return false
}
if (typeof obj.requestorAvatar !== 'string' && obj.requestorAvatar !== null) {
return false
}
if (
typeof obj.requestorDisplayName !== 'string' &&
obj.requestorDisplayName !== null
) {
return false
}
if (typeof obj.requestorPK !== 'string') {
return false
}
if (typeof obj.timestamp !== 'number') {
return false
}
return true
}
/**
* @param {any} item
* @returns {item is HandshakeRequest}
*/
exports.isHandshakeRequest = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {HandshakeRequest} */ (item)
if (typeof obj.from !== 'string') {
return false
}
if (typeof obj.response !== 'string') {
return false
}
if (typeof obj.timestamp !== 'number') {
return false
}
return true
}
/**
* @param {any} item
* @returns {item is Message}
*/
exports.isMessage = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {Message} */ (item)
return typeof obj.body === 'string' && typeof obj.timestamp === 'number'
}
/**
* @param {any} item
* @returns {item is PartialOutgoing}
*/
exports.isPartialOutgoing = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {PartialOutgoing} */ (item)
return typeof obj.with === 'string'
}
/**
* @param {any} item
* @returns {item is Outgoing}
*/
exports.isOutgoing = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {Outgoing} */ (item)
const messagesAreMessages = Object.values(obj.messages).every(msg =>
exports.isMessage(msg)
)
return typeof obj.with === 'string' && messagesAreMessages
}
/**
* @typedef {object} Order
* @prop {string} from Public key of sender.
* @prop {string} amount Encrypted
* @prop {string} memo Encrypted
* @prop {number} timestamp
*/
/**
* @param {any} item
* @returns {item is Order}
*/
exports.isOrder = item => {
if (typeof item !== 'object') {
return false
}
if (item === null) {
return false
}
const obj = /** @type {Order} */ (item)
if (typeof obj.amount !== 'string') {
return false
}
if (typeof obj.from !== 'string') {
return false
}
if (typeof obj.memo !== 'string') {
return false
}
return typeof obj.timestamp === 'number'
}
/**
* @typedef {object} OrderResponse
* @prop {'err'|'invoice'} type
* @prop {string} response
*/
/**
* @param {*} o
* @returns {o is OrderResponse}
*/
exports.isOrderResponse = o => {
if (typeof o !== 'object') {
return false
}
if (o === null) {
return false
}
const obj = /** @type {OrderResponse} */ (o)
if (typeof obj.response !== 'string') {
return false
}
return obj.type === 'err' || obj.type === 'invoice'
}
/**
* @typedef {import('./schema-types').EncSpontPayment} EncSpontPayment
*/
const ENC_SPONT_PAYMENT_PREFIX = '$$__SHOCKWALLET__SPONT__PAYMENT'
/**
* @param {string} s
* @returns {s is EncSpontPayment}
*/
const isEncodedSpontPayment = s => s.indexOf(ENC_SPONT_PAYMENT_PREFIX) === 0
exports.isEncodedSpontPayment = isEncodedSpontPayment
/**
* @typedef {object} SpontaneousPayment
* @prop {number} amt
* @prop {string} memo
* @prop {string} preimage
*/
/**
*
* @param {EncSpontPayment} sp
* @throws {Error} If decoding fails.
* @returns {SpontaneousPayment}
*/
exports.decodeSpontPayment = sp => {
try {
const [preimage, amtStr, memo] = sp
.slice((ENC_SPONT_PAYMENT_PREFIX + '__').length)
.split('__')
if (typeof preimage !== 'string') {
throw new TypeError('Could not parse preimage')
}
if (typeof amtStr !== 'string') {
throw new TypeError('Could not parse amt')
}
if (typeof memo !== 'string') {
throw new TypeError('Could not parse memo')
}
const amt = Number(amtStr)
if (!isNumber(amt)) {
throw new TypeError(`Could parse amount as a number, not a number?`)
}
if (isNaN(amt)) {
throw new TypeError(`Could not amount as a number, got NaN.`)
}
if (!isFinite(amt)) {
throw new TypeError(
`Amount was correctly parsed, but got a non finite number.`
)
}
return {
amt,
memo,
preimage
}
} catch (err) {
logger.debug(`Encoded spontaneous payment: ${sp}`)
logger.error(err)
throw err
}
}
/**
* @param {number} amt
* @param {string} memo
* @param {string} preimage
* @returns {EncSpontPayment}
*/
exports.encodeSpontaneousPayment = (amt, memo, preimage) => {
if (typeof amt !== 'number') {
throw new TypeError('amt must be a number')
}
if (typeof memo !== 'string') {
throw new TypeError('memo must be an string')
}
if (typeof preimage !== 'string') {
throw new TypeError('preimage must be an string')
}
if (amt <= 0) {
throw new RangeError('Amt must be greater than zero')
}
if (memo.length < 1) {
throw new TypeError('Memo must be populated')
}
if (preimage.length < 1) {
throw new TypeError('preimage must be populated')
}
const enc = `${ENC_SPONT_PAYMENT_PREFIX}__${amt}__${memo}__${preimage}`
if (isEncodedSpontPayment(enc)) {
return enc
}
throw new Error('isEncodedSpontPayment(enc) false')
}

View file

@ -1,6 +1,7 @@
/** @format */ /** @format */
const { Schema, Utils: CommonUtils } = require('shock-common')
const Key = require('../key') const Key = require('../key')
const Schema = require('../schema')
const Utils = require('../utils') const Utils = require('../utils')
/** /**
* @typedef {Record<string, string|null|undefined>} Avatars * @typedef {Record<string, string|null|undefined>} Avatars
@ -97,7 +98,7 @@ const onDisplayName = (cb, pub) => {
} }
/** /**
* @typedef {import('../schema').StoredRequest} StoredRequest * @typedef {import('shock-common').Schema.StoredRequest} StoredRequest
* @typedef {(reqs: StoredRequest[]) => void} StoredRequestsListener * @typedef {(reqs: StoredRequest[]) => void} StoredRequestsListener
*/ */
@ -121,7 +122,7 @@ const processStoredReqs = async () => {
encryptedStoredReqs = [] encryptedStoredReqs = []
const mySecret = await Utils.mySecret() const mySecret = await Utils.mySecret()
const SEA = require('../../Mediator').mySEA const SEA = require('../../Mediator').mySEA
const finalReqs = await Utils.asyncMap(ereqs, async er => { const finalReqs = await CommonUtils.asyncMap(ereqs, async er => {
/** @type {StoredRequest} */ /** @type {StoredRequest} */
const r = { const r = {
handshakeAddress: await SEA.decrypt(er.handshakeAddress, mySecret), handshakeAddress: await SEA.decrypt(er.handshakeAddress, mySecret),

View file

@ -2,12 +2,12 @@
const uuidv1 = require('uuid/v1') const uuidv1 = require('uuid/v1')
const logger = require('winston') const logger = require('winston')
const debounce = require('lodash/debounce') const debounce = require('lodash/debounce')
const { Schema, Utils: CommonUtils } = require('shock-common')
const Schema = require('../schema')
const Key = require('../key') const Key = require('../key')
const Utils = require('../utils') const Utils = require('../utils')
/** /**
* @typedef {import('../schema').ChatMessage} Message * @typedef {import('shock-common').Schema.ChatMessage} Message
* @typedef {import('../SimpleGUN').OpenListenerData} OpenListenerData * @typedef {import('../SimpleGUN').OpenListenerData} OpenListenerData
*/ */
@ -98,7 +98,7 @@ const onOpenForPubFeedPair = ([pub, feed]) =>
return return
} }
const incoming = /** @type {Schema.Outgoing} */ (data) const incoming = /** @type {import('shock-common').Schema.Outgoing} */ (data)
// incomplete data, let's not assume anything // incomplete data, let's not assume anything
if ( if (
@ -108,12 +108,12 @@ const onOpenForPubFeedPair = ([pub, feed]) =>
return return
} }
/** @type {Schema.ChatMessage[]} */ /** @type {import('shock-common').Schema.ChatMessage[]} */
const newMsgs = Object.entries(incoming.messages) const newMsgs = Object.entries(incoming.messages)
// filter out messages with incomplete data // filter out messages with incomplete data
.filter(([_, msg]) => Schema.isMessage(msg)) .filter(([_, msg]) => Schema.isMessage(msg))
.map(([id, msg]) => { .map(([id, msg]) => {
/** @type {Schema.ChatMessage} */ /** @type {import('shock-common').Schema.ChatMessage} */
const m = { const m = {
// we'll decrypt later // we'll decrypt later
body: msg.body, body: msg.body,
@ -144,8 +144,8 @@ const onOpenForPubFeedPair = ([pub, feed]) =>
const ourSecret = await SEA.secret(await Utils.pubToEpub(pub), user._.sea) const ourSecret = await SEA.secret(await Utils.pubToEpub(pub), user._.sea)
const decryptedMsgs = await Utils.asyncMap(newMsgs, async m => { const decryptedMsgs = await CommonUtils.asyncMap(newMsgs, async m => {
/** @type {Schema.ChatMessage} */ /** @type {import('shock-common').Schema.ChatMessage} */
const decryptedMsg = { const decryptedMsg = {
...m, ...m,
body: await SEA.decrypt(m.body, ourSecret) body: await SEA.decrypt(m.body, ourSecret)

View file

@ -2,9 +2,9 @@
const uuidv1 = require('uuid/v1') const uuidv1 = require('uuid/v1')
const debounce = require('lodash/debounce') const debounce = require('lodash/debounce')
const logger = require('winston') const logger = require('winston')
const { Utils: CommonUtils } = require('shock-common')
const { USER_TO_INCOMING } = require('../key') const { USER_TO_INCOMING } = require('../key')
const { asyncForEach } = require('../utils')
/** @typedef {import('../SimpleGUN').OpenListenerData} OpenListenerData */ /** @typedef {import('../SimpleGUN').OpenListenerData} OpenListenerData */
/** /**
@ -49,16 +49,19 @@ const onOpen = debounce(async uti => {
/** @type {PubToIncoming} */ /** @type {PubToIncoming} */
const newPubToIncoming = {} const newPubToIncoming = {}
await asyncForEach(Object.entries(uti), async ([pub, encFeedID]) => { await CommonUtils.asyncForEach(
if (encFeedID === null) { Object.entries(uti),
newPubToIncoming[pub] = null async ([pub, encFeedID]) => {
return if (encFeedID === null) {
} newPubToIncoming[pub] = null
return
}
if (typeof encFeedID === 'string') { if (typeof encFeedID === 'string') {
newPubToIncoming[pub] = await SEA.decrypt(encFeedID, mySec) newPubToIncoming[pub] = await SEA.decrypt(encFeedID, mySec)
}
} }
}) )
// avoid old data from overwriting new data if decrypting took longer to // avoid old data from overwriting new data if decrypting took longer to
// process for the older open() call than for the newer open() call // process for the older open() call than for the newer open() call

View file

@ -3,8 +3,8 @@
*/ */
/* eslint-disable init-declarations */ /* eslint-disable init-declarations */
const logger = require('winston') const logger = require('winston')
const { Constants } = require('shock-common')
const ErrorCode = require('../errorCode')
const Key = require('../key') const Key = require('../key')
/** /**
@ -41,7 +41,7 @@ const timeout10 = promise => {
new Promise((_, rej) => { new Promise((_, rej) => {
timeoutID = setTimeout(() => { timeoutID = setTimeout(() => {
rej(new Error(ErrorCode.TIMEOUT_ERR)) rej(new Error(Constants.ErrorCode.TIMEOUT_ERR))
}, 10000) }, 10000)
}) })
]) ])
@ -64,7 +64,7 @@ const timeout5 = promise => {
new Promise((_, rej) => { new Promise((_, rej) => {
timeoutID = setTimeout(() => { timeoutID = setTimeout(() => {
rej(new Error(ErrorCode.TIMEOUT_ERR)) rej(new Error(Constants.ErrorCode.TIMEOUT_ERR))
}, 5000) }, 5000)
}) })
]) ])
@ -252,55 +252,6 @@ const recipientToOutgoingID = async recipientPub => {
return null return null
} }
/**
* @template T
* @param {T[]} arr
* @param {(item: T) => void} cb
* @returns {Promise<void>}
*/
const asyncForEach = async (arr, cb) => {
const promises = arr.map(item => cb(item))
await Promise.all(promises)
}
/**
* @template T
* @template U
* @param {T[]} arr
* @param {(item: T) => Promise<U>} cb
* @returns {Promise<U[]>}
*/
const asyncMap = (arr, cb) => {
if (arr.length === 0) {
return Promise.resolve([])
}
const promises = arr.map(item => cb(item))
return Promise.all(promises)
}
/**
* @template T
* @param {T[]} arr
* @param {(item: T) => Promise<boolean>} cb
* @returns {Promise<T[]>}
*/
const asyncFilter = async (arr, cb) => {
if (arr.length === 0) {
return []
}
/** @type {Promise<boolean>[]} */
const promises = arr.map(item => cb(item))
/** @type {boolean[]} */
const results = await Promise.all(promises)
return arr.filter((_, idx) => results[idx])
}
/** /**
* @param {import('../SimpleGUN').ListenerData} listenerData * @param {import('../SimpleGUN').ListenerData} listenerData
* @returns {listenerData is import('../SimpleGUN').ListenerObj} * @returns {listenerData is import('../SimpleGUN').ListenerObj}
@ -308,14 +259,6 @@ const asyncFilter = async (arr, cb) => {
const dataHasSoul = listenerData => const dataHasSoul = listenerData =>
typeof listenerData === 'object' && listenerData !== null typeof listenerData === 'object' && listenerData !== null
/**
* @param {string} pub
* @returns {string}
*/
const defaultName = pub => 'anon' + pub.slice(0, 8)
const LAST_SEEN_NODE_INTERVAL = 10000
/** /**
* @param {string} pub * @param {string} pub
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
@ -341,15 +284,12 @@ const isNodeOnline = async pub => {
return ( return (
isOnlineApp || isOnlineApp ||
(typeof lastSeenNode === 'number' && (typeof lastSeenNode === 'number' &&
Date.now() - lastSeenNode < LAST_SEEN_NODE_INTERVAL * 2) Date.now() - lastSeenNode < Constants.Misc.LAST_SEEN_NODE_INTERVAL * 2)
) )
} }
module.exports = { module.exports = {
asyncMap,
asyncFilter,
dataHasSoul, dataHasSoul,
defaultName,
delay, delay,
pubToEpub, pubToEpub,
recipientPubToLastReqSentID, recipientPubToLastReqSentID,
@ -358,8 +298,6 @@ module.exports = {
tryAndWait, tryAndWait,
mySecret, mySecret,
promisifyGunNode: require('./promisifygun'), promisifyGunNode: require('./promisifygun'),
asyncForEach,
timeout5, timeout5,
LAST_SEEN_NODE_INTERVAL,
isNodeOnline isNodeOnline
} }

View file

@ -1,13 +0,0 @@
const Events = {
ON_AVATAR: "ON_AVATAR",
ON_BLACKLIST: "ON_BLACKLIST",
ON_CHATS: "ON_CHATS",
ON_DISPLAY_NAME: "ON_DISPLAY_NAME",
ON_HANDSHAKE_ADDRESS: "ON_HANDSHAKE_ADDRESS",
ON_RECEIVED_REQUESTS: "ON_RECEIVED_REQUESTS",
ON_SENT_REQUESTS: "ON_SENT_REQUESTS",
ON_BIO: "ON_BIO",
ON_SEED_BACKUP: "ON_SEED_BACKUP",
};
module.exports = Events;