diff --git a/package.json b/package.json index 13f74847..10e7a609 100644 --- a/package.json +++ b/package.json @@ -78,9 +78,12 @@ "typescript": "^3.6.3" }, "lint-staged": { - "*.{js,ts}": [ + "*.js": [ "prettier --check", "eslint" + ], + "*.ts": [ + "prettier --check" ] }, "husky": { diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 2b91bae7..cd15c802 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -328,6 +328,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 diff --git a/services/gunDB/contact-api/SimpleGUN.ts b/services/gunDB/contact-api/SimpleGUN.ts index 93bb36bf..eb5e4d11 100644 --- a/services/gunDB/contact-api/SimpleGUN.ts +++ b/services/gunDB/contact-api/SimpleGUN.ts @@ -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 @@ -49,6 +52,8 @@ export interface GUNNodeBase { open(this: GUNNode, cb?: OpenListener): GUNNode load(this: GUNNode, cb?: OpenListener): GUNNode + load(this: GUNNode, cb?: LoadListener): GUNNode + off(): void user(): UserGUNNode user(epub: string): GUNNode diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 3afa662c..87c5bddf 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -1171,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() } @@ -1194,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() } @@ -1348,6 +1348,54 @@ const deletePost = async postId => { }) } +/** + * @param {string} publicKey + * @param {boolean} isPrivate Will overwrite previous private status. + * @returns {Promise} + */ +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} + */ +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 = { __createOutgoingFeed, acceptRequest, @@ -1368,5 +1416,7 @@ module.exports = { disconnect, setLastSeenApp, createPost, - deletePost + deletePost, + follow, + unfollow } diff --git a/services/gunDB/contact-api/getters/follows.js b/services/gunDB/contact-api/getters/follows.js new file mode 100644 index 00000000..b74080fa --- /dev/null +++ b/services/gunDB/contact-api/getters/follows.js @@ -0,0 +1,50 @@ +const Common = require('shock-common') +const Logger = require('winston') + +const Key = require('../key') + +/** + * @typedef {Common.Schema.Follow} Follow + */ + +/** + * @returns {Promise>} + */ +exports.currentFollows = () => { + const user = require('../../Mediator').getUser() + + return new Promise(res => { + user.get(Key.FOLLOWS).load(data => { + if (typeof data !== 'object' || data === null) { + Logger.warn( + `GunDb -> getters -> currentFollows() -> Current follows data as fetched from gun is not an object but ${typeof data}. This can happen if Gun lost its cache and it's still retrieving data from the network.` + ) + res({}) + return + } + + const rejected = Object.entries(data).filter( + ([_, follow]) => !Common.Schema.isFollow(follow) + ) + + rejected.forEach(([key, item]) => { + // expected when unfollowed + if (item !== null) { + Logger.warn( + `GunDb -> getters -> currentFollows() -> Item not conforming to schema found inside follows data. Key: ${key}, item: ${JSON.stringify( + item + )}.` + ) + } + }) + + const passed = Object.entries(data).filter(([_, follow]) => + Common.Schema.isFollow(follow) + ) + + const p = /** @type {unknown} */ (passed) + + res(/** @type {Record} */ (p)) + }) + }) +} \ No newline at end of file diff --git a/services/gunDB/contact-api/getters/index.js b/services/gunDB/contact-api/getters/index.js new file mode 100644 index 00000000..9b33cb92 --- /dev/null +++ b/services/gunDB/contact-api/getters/index.js @@ -0,0 +1,42 @@ +/** + * @format + */ +const Key = require('../key') +const Utils = require('../utils') + +/** + * @param {string} pub + * @returns {Promise} + */ +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} + */ +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 +} + +module.exports.Follows = require('./follows') diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index 554ddb92..35269545 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -52,3 +52,4 @@ exports.PAGES = 'pages' exports.COUNT = 'count' exports.CONTENT_ITEMS = 'contentItems' +exports.FOLLOWS = 'follows' diff --git a/src/routes.js b/src/routes.js index b15774bc..bbaa3dde 100644 --- a/src/routes.js +++ b/src/routes.js @@ -13,7 +13,6 @@ const responseTime = require("response-time"); const uuid = require("uuid/v4"); const Common = require('shock-common') - const getListPage = require("../utils/paginate"); const auth = require("../services/auth/auth"); const FS = require("../utils/fs"); @@ -22,6 +21,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 +33,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 +229,7 @@ module.exports = async ( const deviceId = req.headers["x-shockwallet-device-id"]; logger.debug("Decrypting route...") try { - if (nonEncryptedRoutes.includes(req.path) || process.env.DISABLE_SHOCK_ENCRYPTION) { + if (nonEncryptedRoutes.includes(req.path) || process.env.DISABLE_SHOCK_ENCRYPTION === "true") { return next(); } @@ -433,6 +435,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 = () => { @@ -1614,9 +1686,8 @@ module.exports = async ( res.json(channelBackups); }); }); - + const GunEvent = Common.Constants.Event - const {timeout5} = require('../services/gunDB/contact-api/utils') const Key = require('../services/gunDB/contact-api/key') app.get("/api/gun/lndchanbackups", async (req,res) => { @@ -1633,9 +1704,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 @@ -1776,6 +1859,87 @@ module.exports = async ( app.delete(`/api/gun/wall/:postID`,async (req,res) => { //res.status(200).json(await GunActions.deletePost(postID)) }) + ///////////////////////////////// + /** + * @template P + * @typedef {import('express-serve-static-core').RequestHandler

} RequestHandler + */ + + + const ap = /** @type {Application} */ (app); + + /** + * @typedef {object} FollowsRouteParams + * @prop {(string|undefined)=} publicKey + */ + + + /** + * @type {RequestHandler} + */ + const apiGunFollowsGet = (_, res) => { + try { + // const { publicKey } = req.params; + // const currFollows = await GunGetters.currentFollows() + + return res.status(200).json({}) + } catch (err) { + return res.status(500).json({ + errorMessage: err.message || 'Unknown ERR at GET /api/follows' + }) + } + } + + + /** + * @type {RequestHandler} + */ + const apiGunFollowsPut = (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} + */ + const apiGunFollowsDelete = (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) /** * Return app so that it can be used by express. diff --git a/src/server.js b/src/server.js index fca6bab3..c0a8b9b7 100644 --- a/src/server.js +++ b/src/server.js @@ -43,7 +43,7 @@ const server = program => { logger.info('Mainnet Mode:', !!program.mainnet) - if (process.env.DISABLE_SHOCK_ENCRYPTION) { + if (process.env.DISABLE_SHOCK_ENCRYPTION === 'true') { logger.error('Encryption Mode: false') } else { logger.info('Encryption Mode: true') @@ -72,10 +72,7 @@ const server = program => { const deviceId = req.headers['x-shockwallet-device-id'] const oldSend = res.send - if ( - !nonEncryptedRoutes.includes(req.path) && - !process.env.DISABLE_SHOCK_ENCRYPTION - ) { + if (!nonEncryptedRoutes.includes(req.path)) { res.send = (...args) => { if (args[0] && args[0].encryptedData && args[0].encryptionKey) { logger.warn('Response loop detected!') @@ -260,7 +257,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)