Merge branch 'master' into feature/lnd-10

This commit is contained in:
emad-salah 2020-07-09 17:47:31 +01:00
commit 33e18bec2e
19 changed files with 1275 additions and 60 deletions

3
.dockerignore Normal file
View file

@ -0,0 +1,3 @@
**/.git
**/node_modules
**/radata

6
.env.example Normal file
View file

@ -0,0 +1,6 @@
DATA_FILE_NAME=data3
PEERS=["http://gun.shock.network:8765/gun"]
MS_TO_TOKEN_EXPIRATION=4500000
DISABLE_SHOCK_ENCRYPTION=false
CACHE_HEADERS_MANDATORY=true
SHOCK_CACHE=true

29
Dockerfile Normal file
View file

@ -0,0 +1,29 @@
FROM node:12.18.0-alpine3.9
WORKDIR /usr/src/app
ADD ./package.json /usr/src/app/package.json
ADD ./yarn.lock /usr/src/app/yarn.lock
#RUN useradd app && \
# mkdir -p /home/app/.lnd
RUN apk update && apk upgrade && \
apk add --no-cache bash git openssh
RUN yarn install
ADD . /usr/src/app
RUN ls /usr/src/app
RUN chmod +x ./docker-start.sh
#ADD ./tls.cert /usr/src/app/tls.cert
#ADD ./admin.macaroon /usr/src/app/admin.macaroon
# && \
# chown -R app:app /home/app && \
# chown -R app:app /usr/src/app && \
# chown -R app:app /start.sh
#ARG lnd_address
#ENV LND_ADDR=$lnd_address
EXPOSE 9835
CMD ["./docker-start.sh"]

5
docker-start.sh Normal file
View file

@ -0,0 +1,5 @@
#!/bin/ash
node main -h 0.0.0.0 \
-m admin.macaroon \
-d tls.cert \
-l $LND_ADDR

View file

@ -41,11 +41,12 @@
"lodash": "^4.17.15",
"method-override": "^2.3.7",
"promise": "^8.0.1",
"ramda": "^0.27.0",
"request": "^2.87.0",
"request-promise": "^4.2.2",
"response-time": "^2.3.2",
"shelljs": "^0.8.2",
"shock-common": "^0.0.1",
"shock-common": "4.0.1",
"socket.io": "2.1.1",
"text-encoding": "^0.7.0",
"tingodb": "^0.6.1",
@ -61,6 +62,7 @@
"@types/jest": "^24.0.18",
"@types/jsonwebtoken": "^8.3.7",
"@types/lodash": "^4.14.141",
"@types/ramda": "types/npm-ramda#dist",
"@types/socket.io": "^2.1.4",
"@types/uuid": "^3.4.5",
"babel-eslint": "^10.0.3",
@ -78,9 +80,12 @@
"typescript": "^3.6.3"
},
"lint-staged": {
"*.{js,ts}": [
"*.js": [
"prettier --check",
"eslint"
],
"*.ts": [
"prettier --check"
]
},
"husky": {

View file

@ -2,14 +2,20 @@
* @format
*/
const Gun = require('gun')
// @ts-ignore
require('gun/nts')
const logger = require('winston')
// @ts-ignore
Gun.log = () => {}
// @ts-ignore
require('gun/lib/open')
// @ts-ignore
require('gun/lib/load')
const debounce = require('lodash/debounce')
const Encryption = require('../../../utils/encryptionStore')
const Key = require('../contact-api/key')
/** @type {import('../contact-api/SimpleGUN').ISEA} */
// @ts-ignore
const SEAx = require('gun/sea')
@ -265,6 +271,24 @@ const authenticate = async (alias, pass, __user) => {
if (typeof ack.err === 'string') {
throw new Error(ack.err)
} else if (typeof ack.sea === 'object') {
// clock skew
await new Promise(res => setTimeout(res, 2000))
await new Promise((res, rej) => {
_user.get(Key.FOLLOWS).put(
{
unused: null
},
ack => {
if (ack.err) {
rej(new Error(`Error initializing follows: ${ack.err}`))
} else {
res()
}
}
)
})
return ack.sea.pub
} else {
throw new Error('Unknown error.')
@ -277,6 +301,25 @@ const authenticate = async (alias, pass, __user) => {
`Tried to re-authenticate with an alias different to that of stored one, tried: ${alias} - stored: ${_currentAlias}, logoff first if need to change aliases.`
)
}
// clock skew
await new Promise(res => setTimeout(res, 2000))
await new Promise((res, rej) => {
_user.get(Key.FOLLOWS).put(
{
unused: null
},
ack => {
if (ack.err) {
rej(new Error(`Error initializing follows: ${ack.err}`))
} else {
res()
}
}
)
})
// move this to a subscription; implement off() ? todo
API.Jobs.onAcceptedRequests(_user, mySEA)
API.Jobs.onOrders(_user, gun, mySEA)
@ -310,6 +353,21 @@ const authenticate = async (alias, pass, __user) => {
await new Promise(res => setTimeout(res, 5000))
await new Promise((res, rej) => {
_user.get(Key.FOLLOWS).put(
{
unused: null
},
ack => {
if (ack.err) {
rej(new Error(`Error initializing follows: ${ack.err}`))
} else {
res()
}
}
)
})
API.Jobs.onAcceptedRequests(_user, mySEA)
API.Jobs.onOrders(_user, gun, mySEA)
API.Jobs.lastSeenNode(_user)
@ -328,6 +386,17 @@ const logoff = () => {
}
const instantiateGun = () => {
if (user) {
user.leave()
}
// @ts-ignore
user = null
if (gun) {
gun.off()
}
// @ts-ignore
gun = null
const _gun = /** @type {unknown} */ (new Gun({
axe: false,
peers: Config.PEERS
@ -1217,7 +1286,7 @@ const register = async (alias, pass) => {
if (typeof ack.err === 'string') {
throw new Error(ack.err)
} else if (typeof ack.pub === 'string') {
} else if (typeof ack.pub === 'string' || typeof user._.sea === 'object') {
const mySecret = await mySEA.secret(user._.sea.epub, user._.sea)
_currentAlias = alias
_currentPass = await mySEA.encrypt(pass, mySecret)

View file

@ -38,6 +38,9 @@ export interface Soul {
export type OpenListenerData = Primitive | null | OpenListenerDataObj
export type OpenListener = (data: OpenListenerData, key: string) => void
export type LoadListenerData = OpenListenerData
export type LoadListener = (data: LoadListenerData, key: string) => void
export interface GUNNodeBase {
_: Soul
@ -47,6 +50,9 @@ export interface GUNNodeBase {
once(this: GUNNode, cb?: Listener): GUNNode
open(this: GUNNode, cb?: OpenListener): GUNNode
load(this: GUNNode, cb?: OpenListener): GUNNode
load(this: GUNNode, cb?: LoadListener): GUNNode
off(): void
user(): UserGUNNode

View file

@ -3,7 +3,8 @@
*/
const uuidv1 = require('uuid/v1')
const logger = require('winston')
const { Constants, Schema } = require('shock-common')
const Common = require('shock-common')
const { Constants, Schema } = Common
const { ErrorCode } = Constants
@ -1133,7 +1134,21 @@ const setBio = (bio, user) =>
resolve()
}
})
})
}).then(
() =>
new Promise((resolve, reject) => {
user
.get(Key.PROFILE)
.get(Key.BIO)
.put(bio, ack => {
if (ack.err) {
reject(new Error(ack.err))
} else {
resolve()
}
})
})
)
/**
* @param {string[]} mnemonicPhrase
@ -1156,7 +1171,7 @@ const saveSeedBackup = async (mnemonicPhrase, user, SEA) => {
return new Promise((res, rej) => {
user.get(Key.SEED_BACKUP).put(encryptedSeed, ack => {
if (ack.err) {
rej(ack.err)
rej(new Error(ack.err))
} else {
res()
}
@ -1179,7 +1194,7 @@ const saveChannelsBackup = async (backups, user, SEA) => {
return new Promise((res, rej) => {
user.get(Key.CHANNELS_BACKUP).put(encryptBackups, ack => {
if (ack.err) {
rej(ack.err)
rej(new Error(ack.err))
} else {
res()
}
@ -1216,6 +1231,275 @@ const setLastSeenApp = () =>
res()
}
})
}).then(
() =>
new Promise((res, rej) => {
require('../Mediator')
.getUser()
.get(Key.PROFILE)
.get(Key.LAST_SEEN_APP)
.put(Date.now(), ack => {
if (ack.err) {
rej(new Error(ack.err))
} else {
res()
}
})
})
)
/**
* @param {string[]} tags
* @param {string} title
* @param {Common.Schema.ContentItem[]} content
* @returns {Promise<Common.Schema.Post>}
*/
const createPost = async (tags, title, content) => {
if (content.length === 0) {
throw new Error(`A post must contain at least one paragraph/image/video`)
}
const numOfPages = await (async () => {
const maybeNumOfPages = await Utils.tryAndWait(
(_, user) =>
user
.get(Key.WALL)
.get(Key.NUM_OF_PAGES)
.then(),
v => typeof v !== 'number'
)
return typeof maybeNumOfPages === 'number' ? maybeNumOfPages : 0
})()
let pageIdx = Math.max(0, numOfPages - 1).toString()
const count = await (async () => {
if (numOfPages === 0) {
return 0
}
const maybeCount = await Utils.tryAndWait(
(_, user) =>
user
.get(Key.WALL)
.get(Key.PAGES)
.get(pageIdx)
.get(Key.COUNT)
.then(),
v => typeof v !== 'number'
)
return typeof maybeCount === 'number' ? maybeCount : 0
})()
const shouldBeNewPage =
count >= Common.Constants.Misc.NUM_OF_POSTS_PER_WALL_PAGE
if (shouldBeNewPage) {
pageIdx = Number(pageIdx + 1).toString()
}
await new Promise((res, rej) => {
require('../Mediator')
.getUser()
.get(Key.WALL)
.get(Key.PAGES)
.get(pageIdx)
.put(
{
[Key.COUNT]: shouldBeNewPage ? 1 : count + 1,
posts: {
unused: null
}
},
ack => {
if (ack.err) {
rej(new Error(ack.err))
}
res()
}
)
})
/** @type {string} */
const postID = await new Promise((res, rej) => {
const _n = require('../Mediator')
.getUser()
.get(Key.WALL)
.get(Key.PAGES)
.get(pageIdx)
.get(Key.POSTS)
.set(
{
date: Date.now(),
status: 'publish',
tags: tags.join('-'),
title
},
ack => {
if (ack.err) {
rej(new Error(ack.err))
} else {
res(_n._.get)
}
}
)
})
if (shouldBeNewPage || numOfPages === 0) {
await new Promise(res => {
require('../Mediator')
.getUser()
.get(Key.WALL)
.get(Key.NUM_OF_PAGES)
.put(numOfPages + 1, ack => {
if (ack.err) {
throw new Error(ack.err)
}
res()
})
})
}
const contentItems = require('../Mediator')
.getUser()
.get(Key.WALL)
.get(Key.PAGES)
.get(pageIdx)
.get(Key.POSTS)
.get(postID)
.get(Key.CONTENT_ITEMS)
try {
await Promise.all(
content.map(
ci =>
new Promise(res => {
// @ts-ignore
contentItems.set(ci, ack => {
if (ack.err) {
throw new Error(ack.err)
}
res()
})
})
)
)
} catch (e) {
await new Promise(res => {
require('../Mediator')
.getUser()
.get(Key.WALL)
.get(Key.PAGES)
.get(pageIdx)
.get(Key.POSTS)
.get(postID)
.put(null, ack => {
if (ack.err) {
throw new Error(ack.err)
}
res()
})
})
throw e
}
const loadedPost = await new Promise(res => {
require('../Mediator')
.getUser()
.get(Key.WALL)
.get(Key.PAGES)
.get(pageIdx)
.get(Key.POSTS)
.get(postID)
.load(data => {
res(data)
})
})
/** @type {Common.Schema.User} */
const userForPost = await Getters.getMyUser()
/** @type {Common.Schema.Post} */
const completePost = {
...loadedPost,
author: userForPost,
id: postID
}
if (!Common.Schema.isPost(completePost)) {
throw new Error(
`completePost not a Post inside Actions.createPost(): ${JSON.stringify(
createPost
)}`
)
}
return completePost
}
/**
* @param {string} postId
* @returns {Promise<void>}
*/
const deletePost = async postId => {
await new Promise(res => {
res(postId)
})
}
/**
* @param {string} publicKey
* @param {boolean} isPrivate Will overwrite previous private status.
* @returns {Promise<string>}
*/
const follow = (publicKey, isPrivate) => {
/** @type {import('shock-common').Schema.Follow} */
const newFollow = {
private: isPrivate,
status: 'ok',
user: publicKey
}
return new Promise((res, rej) => {
require('../Mediator')
.getUser()
.get(Key.FOLLOWS)
.get(publicKey)
// @ts-ignore
.put(newFollow, ack => {
if (ack.err) {
rej(new Error(ack.err))
} else {
res()
}
})
})
}
/**
* @param {string} publicKey
* @returns {Promise<void>}
*/
const unfollow = publicKey =>
new Promise((res, rej) => {
require('../Mediator')
.getUser()
.get(Key.FOLLOWS)
.get(publicKey)
.put(null, ack => {
if (ack.err) {
rej(new Error(ack.err))
} else {
res()
}
})
})
module.exports = {
@ -1236,5 +1520,9 @@ module.exports = {
saveSeedBackup,
saveChannelsBackup,
disconnect,
setLastSeenApp
setLastSeenApp,
createPost,
deletePost,
follow,
unfollow
}

View file

@ -1,29 +0,0 @@
const Key = require('./key')
const Utils = require('./utils')
/**
* @param {string} pub
* @returns {Promise<string>}
*/
exports.currentOrderAddress = async (pub) => {
const currAddr = await Utils.tryAndWait((gun) => gun.user(pub).get(Key.CURRENT_ORDER_ADDRESS).then())
if (typeof currAddr !== 'string') {
throw new TypeError('Expected user.currentOrderAddress to be an string')
}
return currAddr
}
/**
* @param {string} pub
* @returns {Promise<string|null>}
*/
exports.userToIncomingID = async (pub) => {
const incomingID = await require('../Mediator').getUser().get(Key.USER_TO_INCOMING).get(pub).then()
if (typeof incomingID === 'string') return incomingID
return null
}

View file

@ -0,0 +1,79 @@
/**
* @format
*/
const Common = require('shock-common')
const isFinite = require('lodash/isFinite')
const shuffle = require('lodash/shuffle')
const R = require('ramda')
const Follows = require('./follows')
const Wall = require('./wall')
/**
* @param {number} numberOfPublicKeyGroups
* @param {number} pageRequested
* @returns {[ number , number ]}
*/
const calculateFeedPage = (numberOfPublicKeyGroups, pageRequested) => {
// thanks to sebassdc
return [
(pageRequested - 1) % numberOfPublicKeyGroups,
Math.ceil(pageRequested / numberOfPublicKeyGroups)
]
}
/**
* @param {number} page
* @throws {TypeError}
* @throws {RangeError}
* @returns {Promise<Common.SchemaTypes.Post[]>}
*/
const getFeedPage = async page => {
if (!isFinite(page)) {
throw new TypeError(`Please provide an actual number for [page]`)
}
if (page <= 0) {
throw new RangeError(`Please provide only positive numbers for [page]`)
}
const subbedPublicKeys = Object.values(await Follows.currentFollows()).map(
f => f.user
)
if (subbedPublicKeys.length === 0) {
return []
}
// say there are 20 public keys total
// page 1: page 1 from first 10 public keys
// page 2: page 1 from second 10 public keys
// page 3: page 2 from first 10 public keys
// page 4: page 2 from first 10 public keys
// etc
// thanks to sebassdc (github)
const pagedPublicKeys = R.splitEvery(10, shuffle(subbedPublicKeys))
const [publicKeyGroupIdx, pageToRequest] = calculateFeedPage(
pagedPublicKeys.length,
page
)
const publicKeys = pagedPublicKeys[publicKeyGroupIdx]
const fetchedPages = await Promise.all(
publicKeys.map(pk => Wall.getWallPage(pageToRequest, pk))
)
const fetchedPostsGroups = fetchedPages.map(wp => Object.values(wp.posts))
const fetchedPosts = R.flatten(fetchedPostsGroups)
const sortered = R.sort((a, b) => b.date - a.date, fetchedPosts)
return sortered
}
module.exports = {
getFeedPage
}

View file

@ -0,0 +1,59 @@
/**
* @format
*/
const Common = require('shock-common')
const Logger = require('winston')
const size = require('lodash/size')
const Utils = require('../utils')
const Key = require('../key')
/**
* @typedef {Common.Schema.Follow} Follow
*/
/**
* @throws {TypeError}
* @returns {Promise<Record<string, Common.Schema.Follow>>}
*/
exports.currentFollows = async () => {
/**
* @type {Record<string, Common.Schema.Follow>}
*/
const raw = await Utils.tryAndWait(
// @ts-ignore
(_, user) => new Promise(res => user.get(Key.FOLLOWS).load(res)),
v => {
if (typeof v !== 'object' || v === null) {
return true
}
if (size(v) === 0) {
return true
}
return false
}
)
if (typeof raw !== 'object' || raw === null) {
Logger.error(
`Expected user.follows to be an object but instead got: ${JSON.stringify(
raw
)}`
)
throw new TypeError('Could not get follows, not an object')
}
const clean = {
...raw
}
for (const [key, followOrNull] of Object.entries(clean)) {
if (!Common.Schema.isFollow(followOrNull)) {
delete clean[key]
}
}
return clean
}

View file

@ -0,0 +1,101 @@
/**
* @format
*/
const Common = require('shock-common')
const Key = require('../key')
const Utils = require('../utils')
const Wall = require('./wall')
const Feed = require('./feed')
const User = require('./user')
/**
* @param {string} pub
* @returns {Promise<string>}
*/
exports.currentOrderAddress = async pub => {
const currAddr = await Utils.tryAndWait(gun =>
gun
.user(pub)
.get(Key.CURRENT_ORDER_ADDRESS)
.then()
)
if (typeof currAddr !== 'string') {
throw new TypeError('Expected user.currentOrderAddress to be an string')
}
return currAddr
}
/**
* @param {string} pub
* @returns {Promise<string|null>}
*/
exports.userToIncomingID = async pub => {
const incomingID = await require('../../Mediator')
.getUser()
.get(Key.USER_TO_INCOMING)
.get(pub)
.then()
if (typeof incomingID === 'string') return incomingID
return null
}
/**
* @returns {Promise<Common.SchemaTypes.User>}
*/
const getMyUser = async () => {
const oldProfile = await Utils.tryAndWait(
(_, user) => new Promise(res => user.get(Key.PROFILE).load(res)),
v => typeof v !== 'object'
)
const bio = await Utils.tryAndWait(
(_, user) => user.get(Key.BIO).then(),
v => typeof v !== 'string'
)
const lastSeenApp = await Utils.tryAndWait(
(_, user) => user.get(Key.LAST_SEEN_APP).then(),
v => typeof v !== 'number'
)
const lastSeenNode = await Utils.tryAndWait(
(_, user) => user.get(Key.LAST_SEEN_NODE).then(),
v => typeof v !== 'number'
)
const publicKey = await Utils.tryAndWait(
(_, user) => Promise.resolve(user.is && user.is.pub),
v => typeof v !== 'string'
)
/** @type {Common.SchemaTypes.User} */
const u = {
avatar: oldProfile.avatar,
// @ts-ignore
bio,
displayName: oldProfile.displayName,
// @ts-ignore
lastSeenApp,
// @ts-ignore
lastSeenNode,
// @ts-ignore
publicKey
}
return u
}
module.exports.getMyUser = getMyUser
module.exports.Follows = require('./follows')
module.exports.getWallPage = Wall.getWallPage
module.exports.getWallTotalPages = Wall.getWallTotalPages
module.exports.getFeedPage = Feed.getFeedPage
module.exports.getAnUser = User.getAnUser

View file

@ -0,0 +1,111 @@
/**
* @format
*/
const Common = require('shock-common')
const Key = require('../key')
const Utils = require('../utils')
/**
* @param {string} publicKey
* @returns {Promise<Common.SchemaTypes.User>}
*/
const getAnUser = async publicKey => {
const oldProfile = await Utils.tryAndWait(
g => {
const user = g.get(`~${publicKey}`)
return new Promise(res => user.get(Key.PROFILE).load(res))
},
v => typeof v !== 'object'
)
const bio = await Utils.tryAndWait(
g =>
g
.get(`~${publicKey}`)
.get(Key.BIO)
.then(),
v => typeof v !== 'string'
)
const lastSeenApp = await Utils.tryAndWait(
g =>
g
.get(`~${publicKey}`)
.get(Key.LAST_SEEN_APP)
.then(),
v => typeof v !== 'number'
)
const lastSeenNode = await Utils.tryAndWait(
(_, user) => user.get(Key.LAST_SEEN_NODE).then(),
v => typeof v !== 'number'
)
/** @type {Common.SchemaTypes.User} */
const u = {
avatar: oldProfile.avatar,
// @ts-ignore
bio,
displayName: oldProfile.displayName,
// @ts-ignore
lastSeenApp,
// @ts-ignore
lastSeenNode,
// @ts-ignore
publicKey
}
return u
}
module.exports.getAnUser = getAnUser
/**
* @returns {Promise<Common.SchemaTypes.User>}
*/
const getMyUser = async () => {
const oldProfile = await Utils.tryAndWait(
(_, user) => new Promise(res => user.get(Key.PROFILE).load(res)),
v => typeof v !== 'object'
)
const bio = await Utils.tryAndWait(
(_, user) => user.get(Key.BIO).then(),
v => typeof v !== 'string'
)
const lastSeenApp = await Utils.tryAndWait(
(_, user) => user.get(Key.LAST_SEEN_APP).then(),
v => typeof v !== 'number'
)
const lastSeenNode = await Utils.tryAndWait(
(_, user) => user.get(Key.LAST_SEEN_NODE).then(),
v => typeof v !== 'number'
)
const publicKey = await Utils.tryAndWait(
(_, user) => Promise.resolve(user.is && user.is.pub),
v => typeof v !== 'string'
)
/** @type {Common.SchemaTypes.User} */
const u = {
avatar: oldProfile.avatar,
// @ts-ignore
bio,
displayName: oldProfile.displayName,
// @ts-ignore
lastSeenApp,
// @ts-ignore
lastSeenNode,
// @ts-ignore
publicKey
}
return u
}
module.exports.getMyUser = getMyUser

View file

@ -0,0 +1,131 @@
/**
* @format
*/
const Common = require('shock-common')
const Utils = require('../utils')
const Key = require('../key')
const Wall = require('./user')
/**
* @param {string=} publicKey
* @returns {Promise<number>}
*/
const getWallTotalPages = async publicKey => {
const totalPages = await Utils.tryAndWait(
(gun, u) => {
const user = publicKey ? gun.get(`~${publicKey}`) : u
return user
.get(Key.WALL)
.get(Key.NUM_OF_PAGES)
.then()
},
v => typeof v !== 'number'
)
return typeof totalPages === 'number' ? totalPages : 0
}
/**
* @param {number} page
* @param {string=} publicKey
* @throws {TypeError}
* @throws {RangeError}
* @returns {Promise<Common.SchemaTypes.WallPage>}
*/
const getWallPage = async (page, publicKey) => {
const totalPages = await getWallTotalPages(publicKey)
if (page === 0 || totalPages === 0) {
return {
count: 0,
posts: {}
}
}
const actualPageIdx = page < 0 ? totalPages + page : page - 1
if (actualPageIdx > totalPages - 1) {
throw new RangeError(`Requested a page out of bounds`)
}
/**
* @type {Common.SchemaTypes.WallPage}
*/
const thePage = await Utils.tryAndWait(
(g, u) => {
const user = publicKey ? g.get(`~${publicKey}`) : u
return new Promise(res => {
user
.get(Key.WALL)
.get(Key.PAGES)
.get(actualPageIdx.toString())
// @ts-ignore
.load(res)
})
},
maybePage => {
if (typeof maybePage !== 'object' || maybePage === null) {
return true
}
const clean = {
...maybePage
}
// @ts-ignore
for (const [key, post] of Object.entries(clean.posts)) {
// delete unsuccessful writes
if (post === null) {
// @ts-ignore
delete clean.posts[key]
} else {
post.id = key
}
}
// .load() sometimes doesn't load all data on first call
// @ts-ignore
if (Object.keys(clean.posts).length === 0) {
return true
}
return !Common.Schema.isWallPage(clean)
}
)
const clean = {
...thePage
}
for (const [key, post] of Object.entries(clean.posts)) {
// delete unsuccessful writes
if (post === null) {
delete clean.posts[key]
clean.count--
} else {
post.author = publicKey
? // eslint-disable-next-line no-await-in-loop
await Wall.getAnUser(publicKey)
: // eslint-disable-next-line no-await-in-loop
await Wall.getMyUser()
post.id = key
}
}
if (!Common.Schema.isWallPage(clean)) {
throw new Error(
`Fetched page not a wall page, instead got: ${JSON.stringify(clean)}`
)
}
return clean
}
module.exports = {
getWallTotalPages,
getWallPage
}

View file

@ -37,6 +37,15 @@ const lastSeenNode = user => {
logger.error(`Error inside lastSeenNode job: ${ack.err}`)
}
})
user
.get(Key.PROFILE)
.get(Key.LAST_SEEN_NODE)
.put(Date.now(), ack => {
if (ack.err) {
logger.error(`Error inside lastSeenNode job: ${ack.err}`)
}
})
}
}, LAST_SEEN_NODE_INTERVAL)
}

View file

@ -42,3 +42,16 @@ exports.CHANNELS_BACKUP = 'channelsBackup'
exports.LAST_SEEN_APP = 'lastSeenApp'
exports.LAST_SEEN_NODE = 'lastSeenNode'
exports.WALL = 'wall'
exports.NUM_OF_PAGES = 'numOfPages'
exports.PAGES = 'pages'
exports.COUNT = 'count'
exports.CONTENT_ITEMS = 'contentItems'
exports.FOLLOWS = 'follows'
exports.POSTS = 'posts'

View file

@ -12,7 +12,7 @@ const httpsAgent = require("https");
const responseTime = require("response-time");
const uuid = require("uuid/v4");
const Common = require('shock-common')
const isARealUsableNumber = require('lodash/isFinite')
const getListPage = require("../utils/paginate");
const auth = require("../services/auth/auth");
@ -22,6 +22,7 @@ const LightningServices = require("../utils/lightningServices");
const GunDB = require("../services/gunDB/Mediator");
const { unprotectedRoutes, nonEncryptedRoutes } = require("../utils/protectedRoutes");
const GunActions = require("../services/gunDB/contact-api/actions")
const GunGetters = require('../services/gunDB/contact-api/getters')
const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10;
const SESSION_ID = uuid();
@ -33,6 +34,8 @@ module.exports = async (
mySocketsEvents,
{ serverPort, CA, CA_KEY, usetls }
) => {
const {timeout5} = require('../services/gunDB/contact-api/utils')
const Http = Axios.create({
httpsAgent: new httpsAgent.Agent({
ca: await FS.readFile(CA)
@ -227,7 +230,7 @@ module.exports = async (
const deviceId = req.headers["x-shockwallet-device-id"];
logger.debug("Decrypting route...")
try {
if (nonEncryptedRoutes.includes(req.path)) {
if (nonEncryptedRoutes.includes(req.path) || process.env.DISABLE_SHOCK_ENCRYPTION === "true") {
return next();
}
@ -249,7 +252,7 @@ module.exports = async (
return res.status(401).json(error);
}
if (req.method === "GET") {
if (req.method === "GET" || req.method === "DELETE" || !req.body.encryptionKey && !req.body.iv) {
return next();
}
@ -433,6 +436,76 @@ module.exports = async (
GunActions.saveChannelsBackup(JSON.stringify(channelBackups),user,SEA)
});
/*
const feedObj = {
feed: [
{
id:'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
paragraphs:[
"SOme text and stuff 12"
"SOme text and stuff"
],
profilePic:"",
username:"bobni",
media:[
{
type:'VIDEO',
ratio_x: 1024,
ratio_y: 436,
magnetUri:'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent',
},
]
},
{
id:'3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
paragraphs:[
"SOme text and stuff"
],
profilePic:"",
username:"bobni",
media:[
{
type:'VIDEO',
ratio_x: 1920,
ratio_y: 804,
magnetUri:'magnet:?xt=urn:btih:c9e15763f722f23e98a29decdfae341b98d53056&dn=Cosmos+Laundromat&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fcosmos-laundromat.torrent',
},
]
},
{
id:'58694a0f-3da1-471f-bd96-145571e29d72',
paragraphs:[
"SOme text and stuff"
],
profilePic:"",
username:"bobni",
media:[
{
type:'VIDEO',
ratio_x: 1920,
ratio_y: 1080,
magnetUri:'magnet:?xt=urn:btih:dd8255ecdc7ca55fb0bbf81323d87062db1f6d1c&dn=Big+Buck+Bunny&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fbig-buck-bunny.torrent',
},
]
}
]
}
user.get("FEED_POC").put(JSON.stringify(feedObj), ack => {
if (ack.err) {
//rej(new Error(ack.err))
}*/
const feedObj = {
feed :{}
}
user.get("FEED_POC").put(feedObj, ack => {
if (ack.err) {
//rej(ack.err)
logger.log(ack.err)
} else {
logger.log(ack.err)
}
})
//register to listen for channel backups
const onNewChannelBackup = () => {
@ -1712,11 +1785,9 @@ module.exports = async (
res.json(channelBackups);
});
});
const GunEvent = Common.Constants.Event
const Key = require('../services/gunDB/contact-api/key')
const { timeout5 } = require('../services/gunDB/contact-api/utils')
app.get("/api/gun/lndchanbackups", async (req,res) => {
try{
const user = require('../services/gunDB/Mediator').getUser()
@ -1731,9 +1802,21 @@ module.exports = async (
res.json({ok:"err"})
}
})
app.get("/api/gun/feedpoc", async (req,res) =>{
try{
logger.warn("FEED POC")
const user = require('../services/gunDB/Mediator').getUser()
const feedObj = await timeout5(user.get("FEED_POC").then())
logger.warn(feedObj)
res.json({data:feedObj})
} catch (err) {
//res.json({ok:"err"})
}
})
const Events = require('../services/gunDB/contact-api/events')
app.get(`/api/gun/${GunEvent.ON_RECEIVED_REQUESTS}`, (_, res) => {
try {
// spinup
@ -1854,6 +1937,169 @@ module.exports = async (
})
}
})
////////////////////////////////////////////////////////////////////////////////
app.get(`/api/gun/wall/:publicKey?`, async (req, res) => {
try {
const { page } = req.query;
const {publicKey} = req.params
const pageNum = Number(page)
if (!isARealUsableNumber(pageNum)) {
return res.status(400).json({
field: 'page',
errorMessage: 'Not a number'
})
}
const totalPages = await GunGetters.getWallTotalPages(publicKey)
const fetchedPage = await GunGetters.getWallPage(pageNum, publicKey)
return res.status(200).json({
...fetchedPage,
totalPages,
})
} catch (err) {
return res.status(500).json({
errorMessage: err.message
})
}
})
app.post(`/api/gun/wall/`, async (req,res) => {
try{
const {tags,title,contentItems} = req.body
return res.status(200).json(await GunActions.createPost(
tags,
title,
contentItems
))
} catch(e) {
return res.status(500).json({
errorMessage: (typeof e === 'string' ? e : e.message)
|| 'Unknown error.'
})
}
})
app.delete(`/api/gun/wall/:postID`, (_, res) => res.status(200).json({
ok: 'true'
}))
/////////////////////////////////
/**
* @template P
* @typedef {import('express-serve-static-core').RequestHandler<P>} RequestHandler
*/
const ap = /** @type {Application} */ (app);
/**
* @typedef {object} FollowsRouteParams
* @prop {(string|undefined)=} publicKey
*/
/**
* @type {RequestHandler<FollowsRouteParams>}
*/
const apiGunFollowsGet = async (_, res) => {
try {
const currFollows = await GunGetters.Follows.currentFollows()
return res.status(200).json(currFollows)
} catch (err) {
return res.status(500).json({
errorMessage: err.message || 'Unknown ERR at GET /api/follows'
})
}
}
/**
* @type {RequestHandler<FollowsRouteParams>}
*/
const apiGunFollowsPut = async (req, res) => {
try {
const { publicKey } = req.params;
if (!publicKey) {
throw new Error(`Missing publicKey route param.`)
}
await GunActions.follow(req.params.publicKey, false)
// 201 would be extraneous here. Implement it inside app.put
return res.status(200).json({
ok: true
})
} catch (err) {
return res.status(500).json({
errorMessage: err.message || 'Unknown error inside /api/gun/follows/'
})
}
}
/**
* @type {RequestHandler<FollowsRouteParams>}
*/
const apiGunFollowsDelete = async (req, res) => {
try {
const { publicKey } = req.params;
if (!publicKey) {
throw new Error(`Missing publicKey route param.`)
}
await GunActions.unfollow(req.params.publicKey)
return res.status(200).json({
ok: true
})
} catch (err) {
return res.status(500).json({
errorMessage: err.message || 'Unknown error inside /api/gun/follows/'
})
}
}
ap.get('/api/gun/follows/', apiGunFollowsGet)
ap.get('/api/gun/follows/:publicKey', apiGunFollowsGet)
ap.put(`/api/gun/follows/:publicKey`,apiGunFollowsPut)
ap.delete(`/api/gun/follows/:publicKey`, apiGunFollowsDelete)
/**
* @type {RequestHandler<{}>}
*/
const apiGunFeedGet = async (req, res) => {
try {
const { page: pageStr } = req.query;
const page = Number(pageStr)
if (!isARealUsableNumber(page)) {
return res.status(400).json({
field: 'page',
errorMessage: 'page must be a number'
})
}
if (page < 1) {
return res.status(400).json({
field: page,
errorMessage: 'page must be a positive number'
})
}
return res.status(200).json({
posts: await GunGetters.getFeedPage(page)
})
} catch (err) {
return res.status(500).json({
errorMessage: err.message || 'Unknown error inside /api/gun/follows/'
})
}
}
ap.get(`/api/gun/feed`, apiGunFeedGet)
/**
* Return app so that it can be used by express.

View file

@ -9,6 +9,7 @@ const server = program => {
const Http = require('http')
const Express = require('express')
const Crypto = require('crypto')
const Dotenv = require('dotenv')
const LightningServices = require('../utils/lightningServices')
const Encryption = require('../utils/encryptionStore')
const app = Express()
@ -25,6 +26,7 @@ const server = program => {
// load app default configuration data
const defaults = require('../config/defaults')(program.mainnet)
// define useful global variables ======================================
Dotenv.config()
module.useTLS = program.usetls
module.serverPort = program.serverport || defaults.serverPort
module.httpsPort = module.serverPort
@ -41,6 +43,12 @@ const server = program => {
logger.info('Mainnet Mode:', !!program.mainnet)
if (process.env.DISABLE_SHOCK_ENCRYPTION === 'true') {
logger.error('Encryption Mode: false')
} else {
logger.info('Encryption Mode: true')
}
const stringifyData = data => {
if (typeof data === 'object') {
const stringifiedData = JSON.stringify(data)
@ -60,6 +68,46 @@ const server = program => {
.digest('hex')
}
const cacheCheck = ({ req, res, args, send }) => {
if (
(process.env.SHOCK_CACHE === 'true' || !process.env.SHOCK_CACHE) &&
req.method === 'GET'
) {
const dataHash = hashData(args[0]).slice(-8)
res.set('shock-cache-hash', dataHash)
logger.debug('shock-cache-hash:', req.headers['shock-cache-hash'])
logger.debug('Data Hash:', dataHash)
if (
!req.headers['shock-cache-hash'] &&
(process.env.CACHE_HEADERS_MANDATORY === 'true' ||
!process.env.CACHE_HEADERS_MANDATORY)
) {
logger.warn(
"Request is missing 'shock-cache-hash' header, please make sure to include that in each GET request in order to benefit from reduced data usage"
)
return { cached: false, hash: dataHash }
}
if (req.headers['shock-cache-hash'] === dataHash) {
logger.debug('Same Hash Detected!')
args[0] = null
res.status(304)
send.apply(res, args)
return { cached: true, hash: dataHash }
}
return { cached: false, hash: dataHash }
}
return { cached: false, hash: null }
}
/**
* @param {Express.Request} req
* @param {Express.Response} res
* @param {(() => void)} next
*/
const modifyResponseBody = (req, res, next) => {
const deviceId = req.headers['x-shockwallet-device-id']
const oldSend = res.send
@ -72,16 +120,9 @@ const server = program => {
return
}
const dataHash = hashData(args[0]).slice(-8)
res.set('shock-cache-hash', dataHash)
const { cached, hash } = cacheCheck({ req, res, args, send: oldSend })
logger.debug('shock-cache-hash:', req.headers['shock-cache-hash'])
logger.debug('Data Hash:', dataHash)
if (req.headers['shock-cache-hash'] === dataHash) {
logger.debug('Same Hash Detected!')
args[0] = null
res.status(304)
oldSend.apply(res, args)
if (cached) {
return
}
@ -89,10 +130,10 @@ const server = program => {
const authorized = Encryption.isAuthorizedDevice({ deviceId })
const encryptedMessage = authorized
? Encryption.encryptMessage({
message: args[0],
message: args[0] ? args[0] : {},
deviceId,
metadata: {
hash: dataHash
hash
}
})
: args[0]
@ -249,7 +290,9 @@ const server = program => {
// app.use(bodyParser.json({limit: '100000mb'}));
app.use(bodyParser.json({ limit: '50mb' }))
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }))
app.use(modifyResponseBody)
if (process.env.DISABLE_SHOCK_ENCRYPTION !== 'true') {
app.use(modifyResponseBody)
}
serverInstance.listen(module.serverPort, module.serverhost)

View file

@ -633,6 +633,10 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/ramda@types/npm-ramda#dist":
version "0.25.0"
resolved "https://codeload.github.com/types/npm-ramda/tar.gz/9529aa3c8ff70ff84afcbc0be83443c00f30ea90"
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
@ -3168,6 +3172,11 @@ ignore@^4.0.6:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
immer@^6.0.6:
version "6.0.9"
resolved "https://registry.yarnpkg.com/immer/-/immer-6.0.9.tgz#b9dd69b8e69b3a12391e87db1e3ff535d1b26485"
integrity sha512-SyCYnAuiRf67Lvk0VkwFvwtDoEiCMjeamnHvRfnVDyc7re1/rQrNxuL+jJ7lA3WvdC4uznrvbmm+clJ9+XXatg==
import-fresh@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118"
@ -5383,6 +5392,11 @@ ramda@^0.26.1:
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==
ramda@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.0.tgz#915dc29865c0800bf3f69b8fd6c279898b59de43"
integrity sha512-pVzZdDpWwWqEVVLshWUHjNwuVP7SfcmPraYuqocJp1yo2U1R7P+5QAfDhdItkuoGqIBnBYrtPp7rEPqDn9HlZA==
random-bytes@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
@ -5500,6 +5514,19 @@ rechoir@^0.6.2:
dependencies:
resolve "^1.1.6"
redux-thunk@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
redux@^4.0.5:
version "4.0.5"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
dependencies:
loose-envify "^1.4.0"
symbol-observable "^1.2.0"
regenerator-runtime@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
@ -5876,13 +5903,17 @@ shellwords@^0.1.1:
resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
shock-common@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-0.0.1.tgz#72092c565ab65198da13656b5027fbd44881bf72"
integrity sha512-LV2WiJDr1E6TEWel095oLN6gxpGTmsg6CUeGB6DdLHbYEz0qSpcDG4MYp2mZGpj/DejNKwYg1EiX2qf7ArpIkQ==
shock-common@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/shock-common/-/shock-common-4.0.1.tgz#035e7081b6e67f6721e68dcc6b4d1e4c8f2cd96d"
integrity sha512-3xAkG8lyfyZHK8trgOy2aN75uG1ZBm0MPoIEzP4hgXhyT/b80WmQzX3DqVSSmjfhq1Di0sjmNCY7O5Nf6cEmFg==
dependencies:
immer "^6.0.6"
lodash "^4.17.15"
normalizr "^3.6.0"
redux "^4.0.5"
redux-thunk "^2.3.0"
uuid "3.x.x"
signal-exit@^3.0.0, signal-exit@^3.0.2:
version "3.0.2"
@ -6302,6 +6333,11 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
symbol-tree@^3.2.2:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@ -6672,6 +6708,11 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@3.x.x:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^3.3.2:
version "3.3.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"