Merge branch 'master' into ui-fixies
This commit is contained in:
commit
ca9361df8c
23 changed files with 2066 additions and 1640 deletions
|
|
@ -4,7 +4,6 @@
|
|||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"strict": "off",
|
||||
"id-length": ["error", { "exceptions": ["_"] }],
|
||||
|
||||
"no-console": "off",
|
||||
|
||||
|
|
@ -65,7 +64,18 @@
|
|||
"no-throw-literal": "off",
|
||||
|
||||
// lightning has sync methods and this rule bans them
|
||||
"no-sync": "off"
|
||||
"no-sync": "off",
|
||||
|
||||
"id-length": "off",
|
||||
|
||||
// typescript does this
|
||||
"no-unused-vars": "off",
|
||||
|
||||
// https://github.com/prettier/eslint-config-prettier/issues/132
|
||||
"line-comment-position": "off",
|
||||
|
||||
// if someone does this it's probably intentional
|
||||
"no-useless-concat": "off"
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"google-proto-files": "^1.0.3",
|
||||
"graphviz": "0.0.8",
|
||||
"grpc": "^1.21.1",
|
||||
"gun": "^0.2019.1211",
|
||||
"gun": "git://github.com/amark/gun#c59e0e95f92779ce6bb3aab823d318bc16b20c33",
|
||||
"husky": "^3.0.9",
|
||||
"jsonfile": "^4.0.0",
|
||||
"jsonwebtoken": "^8.3.0",
|
||||
|
|
@ -62,6 +62,7 @@
|
|||
"@types/express": "^4.17.1",
|
||||
"@types/gun": "^0.9.1",
|
||||
"@types/jest": "^24.0.18",
|
||||
"@types/jsonwebtoken": "^8.3.7",
|
||||
"@types/lodash": "^4.14.141",
|
||||
"@types/socket.io": "^2.1.3",
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@
|
|||
* @format
|
||||
*/
|
||||
const Gun = require('gun')
|
||||
// @ts-ignore
|
||||
require('gun/lib/open')
|
||||
const debounce = require('lodash/debounce')
|
||||
const once = require('lodash/once')
|
||||
const Encryption = require('../../../utils/encryptionStore')
|
||||
const logger = require('winston')
|
||||
|
||||
/** @type {import('../contact-api/SimpleGUN').ISEA} */
|
||||
// @ts-ignore
|
||||
|
|
@ -21,7 +23,10 @@ const IS_GUN_AUTH = 'IS_GUN_AUTH'
|
|||
|
||||
mySEA.encrypt = (msg, secret) => {
|
||||
if (typeof msg !== 'string') {
|
||||
throw new TypeError('mySEA.encrypt() -> expected msg to be an string')
|
||||
throw new TypeError(
|
||||
'mySEA.encrypt() -> expected msg to be an string instead got: ' +
|
||||
typeof msg
|
||||
)
|
||||
}
|
||||
|
||||
if (msg.length === 0) {
|
||||
|
|
@ -40,7 +45,10 @@ mySEA.encrypt = (msg, secret) => {
|
|||
|
||||
mySEA.decrypt = (encMsg, secret) => {
|
||||
if (typeof encMsg !== 'string') {
|
||||
throw new TypeError('mySEA.encrypt() -> expected encMsg to be an string')
|
||||
throw new TypeError(
|
||||
'mySEA.encrypt() -> expected encMsg to be an string instead got: ' +
|
||||
typeof encMsg
|
||||
)
|
||||
}
|
||||
|
||||
if (encMsg.length === 0) {
|
||||
|
|
@ -79,6 +87,12 @@ mySEA.decrypt = (encMsg, secret) => {
|
|||
}
|
||||
|
||||
mySEA.secret = (recipientOrSenderEpub, recipientOrSenderSEA) => {
|
||||
if (typeof recipientOrSenderEpub !== 'string') {
|
||||
throw new TypeError('epub has to be an string')
|
||||
}
|
||||
if (typeof recipientOrSenderSEA !== 'object') {
|
||||
throw new TypeError('sea has to be an object')
|
||||
}
|
||||
if (recipientOrSenderEpub === recipientOrSenderSEA.pub) {
|
||||
throw new Error('Do not use pub for mysecret')
|
||||
}
|
||||
|
|
@ -117,6 +131,7 @@ const Event = require('../event-constants')
|
|||
* @typedef {object} SimpleSocket
|
||||
* @prop {(eventName: string, data: Emission) => void} emit
|
||||
* @prop {(eventName: string, handler: (data: any) => void) => void} on
|
||||
* @prop {{ query: { 'x-shockwallet-device-id': string }}} handshake
|
||||
*/
|
||||
|
||||
/* eslint-disable init-declarations */
|
||||
|
|
@ -133,6 +148,11 @@ let user
|
|||
let _currentAlias = ''
|
||||
let _currentPass = ''
|
||||
|
||||
let mySec = ''
|
||||
|
||||
/** @returns {string} */
|
||||
const getMySecret = () => mySec
|
||||
|
||||
let _isAuthenticating = false
|
||||
let _isRegistering = false
|
||||
|
||||
|
|
@ -179,19 +199,16 @@ const authenticate = async (alias, pass) => {
|
|||
if (typeof ack.err === 'string') {
|
||||
throw new Error(ack.err)
|
||||
} else if (typeof ack.sea === 'object') {
|
||||
API.Jobs.onAcceptedRequests(user, mySEA)
|
||||
API.Jobs.onOrders(user, gun, mySEA)
|
||||
|
||||
const mySec = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||
if (typeof mySec !== 'string') {
|
||||
throw new TypeError('mySec not an string')
|
||||
}
|
||||
mySec = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||
|
||||
_currentAlias = user.is ? user.is.alias : ''
|
||||
_currentPass = await mySEA.encrypt(pass, mySec)
|
||||
|
||||
await new Promise(res => setTimeout(res, 5000))
|
||||
|
||||
API.Jobs.onAcceptedRequests(user, mySEA)
|
||||
API.Jobs.onOrders(user, gun, mySEA)
|
||||
|
||||
return ack.sea.pub
|
||||
} else {
|
||||
throw new Error('Unknown error.')
|
||||
|
|
@ -305,6 +322,7 @@ class Mediator {
|
|||
this.socket.on(Action.SET_DISPLAY_NAME, this.setDisplayName)
|
||||
this.socket.on(Action.SEND_PAYMENT, this.sendPayment)
|
||||
this.socket.on(Action.SET_BIO, this.setBio)
|
||||
this.socket.on(Action.DISCONNECT, this.disconnect)
|
||||
|
||||
this.socket.on(Event.ON_AVATAR, this.onAvatar)
|
||||
this.socket.on(Event.ON_BLACKLIST, this.onBlacklist)
|
||||
|
|
@ -319,20 +337,26 @@ class Mediator {
|
|||
this.socket.on(IS_GUN_AUTH, this.isGunAuth)
|
||||
}
|
||||
|
||||
/** @param {SimpleSocket} socket */
|
||||
encryptSocketInstance = socket => {
|
||||
return {
|
||||
/**
|
||||
* @type {SimpleSocket['on']}
|
||||
*/
|
||||
on: (eventName, cb) => {
|
||||
const deviceId = socket.handshake.query['x-shockwallet-device-id']
|
||||
socket.on(eventName, data => {
|
||||
socket.on(eventName, _data => {
|
||||
try {
|
||||
if (Encryption.isNonEncrypted(eventName)) {
|
||||
return cb(data)
|
||||
return cb(_data)
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return cb(data)
|
||||
if (!_data) {
|
||||
return cb(_data)
|
||||
}
|
||||
|
||||
let data = _data
|
||||
|
||||
if (!deviceId) {
|
||||
const error = {
|
||||
field: 'deviceId',
|
||||
|
|
@ -350,16 +374,9 @@ class Mediator {
|
|||
console.error('Unknown Device', error)
|
||||
return false
|
||||
}
|
||||
console.log('Emitting Data...', data)
|
||||
if (typeof data === 'string') {
|
||||
data = JSON.parse(data)
|
||||
}
|
||||
console.log('Event:', eventName)
|
||||
console.log('Data:', data)
|
||||
console.log('Decrypt params:', {
|
||||
deviceId,
|
||||
message: data.encryptedKey
|
||||
})
|
||||
const decryptedKey = Encryption.decryptKey({
|
||||
deviceId,
|
||||
message: data.encryptedKey
|
||||
|
|
@ -377,6 +394,7 @@ class Mediator {
|
|||
}
|
||||
})
|
||||
},
|
||||
/** @type {SimpleSocket['emit']} */
|
||||
emit: (eventName, data) => {
|
||||
try {
|
||||
if (Encryption.isNonEncrypted(eventName)) {
|
||||
|
|
@ -392,8 +410,10 @@ class Mediator {
|
|||
deviceId
|
||||
})
|
||||
: data
|
||||
|
||||
socket.emit(eventName, encryptedMessage)
|
||||
} catch (err) {
|
||||
logger.error(err.message)
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -436,29 +456,6 @@ class Mediator {
|
|||
msg: null,
|
||||
origBody: body
|
||||
})
|
||||
|
||||
// refresh received requests
|
||||
API.Events.onSimplerReceivedRequests(
|
||||
debounce(
|
||||
once(receivedRequests => {
|
||||
if (Config.SHOW_LOG) {
|
||||
console.log('---received requests---')
|
||||
console.log(receivedRequests)
|
||||
console.log('-----------------------')
|
||||
}
|
||||
|
||||
this.socket.emit(Event.ON_RECEIVED_REQUESTS, {
|
||||
msg: receivedRequests,
|
||||
ok: true,
|
||||
origBody: body
|
||||
})
|
||||
}),
|
||||
300
|
||||
),
|
||||
gun,
|
||||
user,
|
||||
mySEA
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
this.socket.emit(Action.ACCEPT_REQUEST, {
|
||||
|
|
@ -508,7 +505,7 @@ class Mediator {
|
|||
|
||||
await throwOnInvalidToken(token)
|
||||
|
||||
await API.Actions.generateHandshakeAddress(user)
|
||||
await API.Actions.generateHandshakeAddress()
|
||||
|
||||
this.socket.emit(Action.GENERATE_NEW_HANDSHAKE_NODE, {
|
||||
ok: true,
|
||||
|
|
@ -562,22 +559,6 @@ class Mediator {
|
|||
msg: null,
|
||||
origBody: body
|
||||
})
|
||||
|
||||
API.Events.onSimplerSentRequests(
|
||||
debounce(
|
||||
once(srs => {
|
||||
this.socket.emit(Event.ON_SENT_REQUESTS, {
|
||||
ok: true,
|
||||
msg: srs,
|
||||
origBody: body
|
||||
})
|
||||
}),
|
||||
350
|
||||
),
|
||||
gun,
|
||||
user,
|
||||
mySEA
|
||||
)
|
||||
} catch (err) {
|
||||
if (Config.SHOW_LOG) {
|
||||
console.log('\n')
|
||||
|
|
@ -815,24 +796,19 @@ class Mediator {
|
|||
|
||||
await throwOnInvalidToken(token)
|
||||
|
||||
API.Events.onChats(
|
||||
chats => {
|
||||
if (Config.SHOW_LOG) {
|
||||
console.log('---chats---')
|
||||
console.log(chats)
|
||||
console.log('-----------------------')
|
||||
}
|
||||
API.Events.onChats(chats => {
|
||||
if (Config.SHOW_LOG) {
|
||||
console.log('---chats---')
|
||||
console.log(chats)
|
||||
console.log('-----------------------')
|
||||
}
|
||||
|
||||
this.socket.emit(Event.ON_CHATS, {
|
||||
msg: chats,
|
||||
ok: true,
|
||||
origBody: body
|
||||
})
|
||||
},
|
||||
gun,
|
||||
user,
|
||||
mySEA
|
||||
)
|
||||
this.socket.emit(Event.ON_CHATS, {
|
||||
msg: chats,
|
||||
ok: true,
|
||||
origBody: body
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
this.socket.emit(Event.ON_CHATS, {
|
||||
|
|
@ -916,24 +892,19 @@ class Mediator {
|
|||
|
||||
await throwOnInvalidToken(token)
|
||||
|
||||
API.Events.onSimplerReceivedRequests(
|
||||
receivedRequests => {
|
||||
if (Config.SHOW_LOG) {
|
||||
console.log('---receivedRequests---')
|
||||
console.log(receivedRequests)
|
||||
console.log('-----------------------')
|
||||
}
|
||||
API.Events.onSimplerReceivedRequests(receivedRequests => {
|
||||
if (Config.SHOW_LOG) {
|
||||
console.log('---receivedRequests---')
|
||||
console.log(receivedRequests)
|
||||
console.log('-----------------------')
|
||||
}
|
||||
|
||||
this.socket.emit(Event.ON_RECEIVED_REQUESTS, {
|
||||
msg: receivedRequests,
|
||||
ok: true,
|
||||
origBody: body
|
||||
})
|
||||
},
|
||||
gun,
|
||||
user,
|
||||
mySEA
|
||||
)
|
||||
this.socket.emit(Event.ON_RECEIVED_REQUESTS, {
|
||||
msg: receivedRequests,
|
||||
ok: true,
|
||||
origBody: body
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
this.socket.emit(Event.ON_RECEIVED_REQUESTS, {
|
||||
|
|
@ -944,6 +915,8 @@ class Mediator {
|
|||
}
|
||||
}
|
||||
|
||||
onSentRequestsSubbed = false
|
||||
|
||||
/**
|
||||
* @param {Readonly<{ token: string }>} body
|
||||
*/
|
||||
|
|
@ -953,24 +926,22 @@ class Mediator {
|
|||
|
||||
await throwOnInvalidToken(token)
|
||||
|
||||
await API.Events.onSimplerSentRequests(
|
||||
sentRequests => {
|
||||
if (Config.SHOW_LOG) {
|
||||
console.log('---sentRequests---')
|
||||
console.log(sentRequests)
|
||||
console.log('-----------------------')
|
||||
}
|
||||
if (!this.onSentRequestsSubbed) {
|
||||
this.onSentRequestsSubbed = true
|
||||
|
||||
this.socket.emit(Event.ON_SENT_REQUESTS, {
|
||||
msg: sentRequests,
|
||||
ok: true,
|
||||
origBody: body
|
||||
})
|
||||
},
|
||||
gun,
|
||||
user,
|
||||
mySEA
|
||||
)
|
||||
API.Events.onSimplerSentRequests(
|
||||
debounce(sentRequests => {
|
||||
console.log(
|
||||
`new Reqss in mediator: ${JSON.stringify(sentRequests)}`
|
||||
)
|
||||
this.socket.emit(Event.ON_SENT_REQUESTS, {
|
||||
msg: sentRequests,
|
||||
ok: true,
|
||||
origBody: body
|
||||
})
|
||||
}, 1000)
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
this.socket.emit(Event.ON_SENT_REQUESTS, {
|
||||
|
|
@ -1062,6 +1033,29 @@ class Mediator {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {Readonly<{ pub: string, token: string }>} body */
|
||||
disconnect = async body => {
|
||||
try {
|
||||
const { pub, token } = body
|
||||
|
||||
await throwOnInvalidToken(token)
|
||||
|
||||
await API.Actions.disconnect(pub)
|
||||
|
||||
this.socket.emit(Action.DISCONNECT, {
|
||||
ok: true,
|
||||
msg: null,
|
||||
origBody: body
|
||||
})
|
||||
} catch (err) {
|
||||
this.socket.emit(Action.DISCONNECT, {
|
||||
ok: false,
|
||||
msg: err.message,
|
||||
origBody: body
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1101,9 +1095,6 @@ const register = async (alias, pass) => {
|
|||
_isRegistering = false
|
||||
|
||||
const mySecret = await mySEA.secret(user._.sea.epub, user._.sea)
|
||||
if (typeof mySecret !== 'string') {
|
||||
throw new Error('Could not generate secret for user.')
|
||||
}
|
||||
|
||||
if (typeof ack.err === 'string') {
|
||||
throw new Error(ack.err)
|
||||
|
|
@ -1123,7 +1114,7 @@ const register = async (alias, pass) => {
|
|||
|
||||
return authenticate(alias, pass).then(async pub => {
|
||||
await API.Actions.setDisplayName('anon' + pub.slice(0, 8), user)
|
||||
await API.Actions.generateHandshakeAddress(user)
|
||||
await API.Actions.generateHandshakeAddress()
|
||||
await API.Actions.generateOrderAddress(user)
|
||||
return pub
|
||||
})
|
||||
|
|
@ -1163,5 +1154,6 @@ module.exports = {
|
|||
instantiateGun,
|
||||
getGun,
|
||||
getUser,
|
||||
mySEA
|
||||
mySEA,
|
||||
getMySecret
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ const Actions = {
|
|||
SEND_PAYMENT: "SEND_PAYMENT",
|
||||
SET_AVATAR: "SET_AVATAR",
|
||||
SET_DISPLAY_NAME: "SET_DISPLAY_NAME",
|
||||
SET_BIO: "SET_BIO"
|
||||
SET_BIO: "SET_BIO",
|
||||
DISCONNECT: "DISCONNECT"
|
||||
};
|
||||
|
||||
module.exports = Actions;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ export type ListenerObj = Record<string, ListenerObjSoul | Primitive | null> & {
|
|||
|
||||
export type ListenerData = Primitive | null | ListenerObj | undefined
|
||||
|
||||
interface OpenListenerDataObj {
|
||||
[k: string]: OpenListenerData
|
||||
}
|
||||
|
||||
export type Listener = (data: ListenerData, key: string) => void
|
||||
export type Callback = (ack: Ack) => void
|
||||
|
||||
|
|
@ -31,14 +35,19 @@ export interface Soul {
|
|||
put: Primitive | null | object | undefined
|
||||
}
|
||||
|
||||
export interface GUNNode {
|
||||
export type OpenListenerData = Primitive | null | OpenListenerDataObj
|
||||
export type OpenListener = (data: OpenListenerData, key: string) => void
|
||||
|
||||
export interface GUNNodeBase {
|
||||
_: Soul
|
||||
get(key: string): GUNNode
|
||||
|
||||
map(): GUNNode
|
||||
put(data: ValidDataValue | GUNNode, cb?: Callback): GUNNode
|
||||
|
||||
on(this: GUNNode, cb: Listener): void
|
||||
once(this: GUNNode, cb?: Listener): GUNNode
|
||||
set(data: ValidDataValue | GUNNode, cb?: Callback): GUNNode
|
||||
|
||||
open(this: GUNNode, cb?: OpenListener): GUNNode
|
||||
|
||||
off(): void
|
||||
user(): UserGUNNode
|
||||
user(epub: string): GUNNode
|
||||
|
|
@ -47,6 +56,12 @@ export interface GUNNode {
|
|||
then<T>(cb: (v: ListenerData) => T): Promise<ListenerData>
|
||||
}
|
||||
|
||||
export interface GUNNode extends GUNNodeBase {
|
||||
get(key: string): GUNNode
|
||||
put(data: ValidDataValue | GUNNode, cb?: Callback): GUNNode
|
||||
set(data: ValidDataValue | GUNNode, cb?: Callback): GUNNode
|
||||
}
|
||||
|
||||
export interface CreateAck {
|
||||
pub: string | undefined
|
||||
err: string | undefined
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const ErrorCode = require('./errorCode')
|
|||
const Getters = require('./getters')
|
||||
const Key = require('./key')
|
||||
const Utils = require('./utils')
|
||||
// const { promisifyGunNode: p } = Utils
|
||||
const { isHandshakeRequest } = require('./schema')
|
||||
/**
|
||||
* @typedef {import('./SimpleGUN').GUNNode} GUNNode
|
||||
|
|
@ -28,14 +29,6 @@ const { isHandshakeRequest } = require('./schema')
|
|||
*/
|
||||
const INITIAL_MSG = '$$__SHOCKWALLET__INITIAL__MESSAGE'
|
||||
|
||||
/**
|
||||
* @returns {Message}
|
||||
*/
|
||||
const __createInitialMessage = () => ({
|
||||
body: INITIAL_MSG,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -57,13 +50,12 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
|||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const mySecret = await SEA.secret(user._.sea.epub, user._.sea)
|
||||
if (typeof mySecret !== 'string') {
|
||||
throw new TypeError(
|
||||
"__createOutgoingFeed() -> typeof mySecret !== 'string'"
|
||||
)
|
||||
}
|
||||
const mySecret = require('../Mediator').getMySecret()
|
||||
const encryptedForMeRecipientPub = await SEA.encrypt(withPublicKey, mySecret)
|
||||
const ourSecret = await SEA.secret(
|
||||
await Utils.pubToEpub(withPublicKey),
|
||||
user._.sea
|
||||
)
|
||||
|
||||
const maybeEncryptedForMeOutgoingFeedID = await Utils.tryAndWait(
|
||||
(_, user) =>
|
||||
|
|
@ -103,12 +95,18 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
|||
throw new TypeError('typeof newOutgoingFeedID !== "string"')
|
||||
}
|
||||
|
||||
/** @type {Message} */
|
||||
const initialMsg = {
|
||||
body: await SEA.encrypt(INITIAL_MSG, ourSecret),
|
||||
timestamp: Date.now()
|
||||
}
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.OUTGOINGS)
|
||||
.get(newOutgoingFeedID)
|
||||
.get(Key.MESSAGES)
|
||||
.set(__createInitialMessage(), ack => {
|
||||
.set(initialMsg, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
|
|
@ -122,12 +120,6 @@ const __createOutgoingFeed = async (withPublicKey, user, SEA) => {
|
|||
mySecret
|
||||
)
|
||||
|
||||
if (typeof encryptedForMeNewOutgoingFeedID === 'undefined') {
|
||||
throw new TypeError(
|
||||
"typeof encryptedForMeNewOutgoingFeedID === 'undefined'"
|
||||
)
|
||||
}
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
||||
|
|
@ -253,10 +245,7 @@ const acceptRequest = async (
|
|||
SEA
|
||||
)
|
||||
|
||||
const mySecret = await SEA.secret(user._.sea.epub, user._.sea)
|
||||
if (typeof mySecret !== 'string') {
|
||||
throw new TypeError("acceptRequest() -> typeof mySecret !== 'string'")
|
||||
}
|
||||
const mySecret = require('../Mediator').getMySecret()
|
||||
const encryptedForMeIncomingID = await SEA.encrypt(incomingID, mySecret)
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
|
|
@ -363,17 +352,15 @@ const blacklist = (publicKey, user) =>
|
|||
})
|
||||
|
||||
/**
|
||||
* @param {UserGUNNode} user
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const generateHandshakeAddress = user =>
|
||||
new Promise((res, rej) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
const generateHandshakeAddress = async () => {
|
||||
const gun = require('../Mediator').getGun()
|
||||
const user = require('../Mediator').getUser()
|
||||
|
||||
const address = uuidv1()
|
||||
const address = uuidv1()
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
user.get(Key.CURRENT_HANDSHAKE_ADDRESS).put(address, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
|
|
@ -383,6 +370,86 @@ const generateHandshakeAddress = user =>
|
|||
})
|
||||
})
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
gun
|
||||
.get(Key.HANDSHAKE_NODES)
|
||||
.get(address)
|
||||
.put({ unused: 0 }, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} pub
|
||||
* @throws {Error}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const cleanup = async pub => {
|
||||
const user = require('../Mediator').getUser()
|
||||
|
||||
const outGoingID = await Utils.recipientToOutgoingID(pub)
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.USER_TO_INCOMING)
|
||||
.get(pub)
|
||||
.put(null, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
||||
.get(pub)
|
||||
.put(null, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.USER_TO_LAST_REQUEST_SENT)
|
||||
.get(pub)
|
||||
.put(null, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (outGoingID) {
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.OUTGOINGS)
|
||||
.get(outGoingID)
|
||||
.put(null, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} recipientPublicKey
|
||||
* @param {GUNNode} gun
|
||||
|
|
@ -396,6 +463,8 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
|||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
await cleanup(recipientPublicKey)
|
||||
|
||||
if (typeof recipientPublicKey !== 'string') {
|
||||
throw new TypeError(
|
||||
`recipientPublicKey is not string, got: ${typeof recipientPublicKey}`
|
||||
|
|
@ -406,6 +475,10 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
|||
throw new TypeError('recipientPublicKey is an string of length 0')
|
||||
}
|
||||
|
||||
if (recipientPublicKey === user.is.pub) {
|
||||
throw new Error('Do not send a request to yourself')
|
||||
}
|
||||
|
||||
console.log('sendHR() -> before recipientEpub')
|
||||
|
||||
/** @type {string} */
|
||||
|
|
@ -413,7 +486,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
|||
|
||||
console.log('sendHR() -> before mySecret')
|
||||
|
||||
const mySecret = await SEA.secret(user._.sea.epub, user._.sea)
|
||||
const mySecret = require('../Mediator').getMySecret()
|
||||
console.log('sendHR() -> before ourSecret')
|
||||
const ourSecret = await SEA.secret(recipientEpub, user._.sea)
|
||||
|
||||
|
|
@ -509,6 +582,11 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
|||
timestamp
|
||||
}
|
||||
|
||||
const encryptedForMeRecipientPublicKey = await SEA.encrypt(
|
||||
recipientPublicKey,
|
||||
mySecret
|
||||
)
|
||||
|
||||
console.log('sendHR() -> before newHandshakeRequestID')
|
||||
/** @type {string} */
|
||||
const newHandshakeRequestID = await new Promise((res, rej) => {
|
||||
|
|
@ -537,32 +615,8 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
|||
})
|
||||
})
|
||||
|
||||
// save request id to REQUEST_TO_USER
|
||||
|
||||
const encryptedForMeRecipientPublicKey = await SEA.encrypt(
|
||||
recipientPublicKey,
|
||||
mySecret
|
||||
)
|
||||
|
||||
// This needs to come before the write to sent requests. Because that write
|
||||
// triggers Jobs.onAcceptedRequests and it in turn reads from request-to-user
|
||||
// This also triggers Events.onSimplerSentRequests
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.REQUEST_TO_USER)
|
||||
.get(newHandshakeRequestID)
|
||||
.put(encryptedForMeRecipientPublicKey, ack => {
|
||||
if (ack.err) {
|
||||
rej(
|
||||
new Error(
|
||||
`Error saving recipient public key to request to user: ${ack.err}`
|
||||
)
|
||||
)
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* @type {StoredReq}
|
||||
|
|
@ -625,11 +679,7 @@ const sendMessage = async (recipientPublicKey, body, user, SEA) => {
|
|||
)
|
||||
}
|
||||
|
||||
const outgoingID = await Utils.recipientToOutgoingID(
|
||||
recipientPublicKey,
|
||||
user,
|
||||
SEA
|
||||
)
|
||||
const outgoingID = await Utils.recipientToOutgoingID(recipientPublicKey)
|
||||
|
||||
if (outgoingID === null) {
|
||||
throw new Error(
|
||||
|
|
@ -668,10 +718,9 @@ const sendMessage = async (recipientPublicKey, body, user, SEA) => {
|
|||
* @param {string} recipientPub
|
||||
* @param {string} msgID
|
||||
* @param {UserGUNNode} user
|
||||
* @param {ISEA} SEA
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const deleteMessage = async (recipientPub, msgID, user, SEA) => {
|
||||
const deleteMessage = async (recipientPub, msgID, user) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
|
@ -700,7 +749,7 @@ const deleteMessage = async (recipientPub, msgID, user, SEA) => {
|
|||
)
|
||||
}
|
||||
|
||||
const outgoingID = await Utils.recipientToOutgoingID(recipientPub, user, SEA)
|
||||
const outgoingID = await Utils.recipientToOutgoingID(recipientPub)
|
||||
|
||||
if (outgoingID === null) {
|
||||
throw new Error(`Could not fetch an outgoing id for user: ${recipientPub}`)
|
||||
|
|
@ -1037,7 +1086,7 @@ const saveSeedBackup = async (mnemonicPhrase, user, SEA) => {
|
|||
throw new TypeError('expected mnemonicPhrase to be an string array')
|
||||
}
|
||||
|
||||
const mySecret = await SEA.secret(user._.sea.epub, user._.sea)
|
||||
const mySecret = require('../Mediator').getMySecret()
|
||||
const encryptedSeed = await SEA.encrypt(mnemonicPhrase.join(' '), mySecret)
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
|
|
@ -1051,8 +1100,21 @@ const saveSeedBackup = async (mnemonicPhrase, user, SEA) => {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} pub
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const disconnect = async pub => {
|
||||
if (!(await Utils.successfulHandshakeAlreadyExists(pub))) {
|
||||
throw new Error('No handshake exists for this pub')
|
||||
}
|
||||
|
||||
await cleanup(pub)
|
||||
|
||||
await generateHandshakeAddress()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
INITIAL_MSG,
|
||||
__createOutgoingFeed,
|
||||
acceptRequest,
|
||||
authenticate,
|
||||
|
|
@ -1067,5 +1129,6 @@ module.exports = {
|
|||
sendPayment,
|
||||
generateOrderAddress,
|
||||
setBio,
|
||||
saveSeedBackup
|
||||
saveSeedBackup,
|
||||
disconnect
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
535
services/gunDB/contact-api/events/index.js
Normal file
535
services/gunDB/contact-api/events/index.js
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
/**
|
||||
* @prettier
|
||||
*/
|
||||
const debounce = require('lodash/debounce')
|
||||
|
||||
const ErrorCode = require('../errorCode')
|
||||
const Key = require('../key')
|
||||
const Schema = require('../schema')
|
||||
const Streams = require('../streams')
|
||||
const Utils = require('../utils')
|
||||
/**
|
||||
* @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode
|
||||
* @typedef {import('../SimpleGUN').GUNNode} GUNNode
|
||||
* @typedef {import('../SimpleGUN').ISEA} ISEA
|
||||
* @typedef {import('../SimpleGUN').ListenerData} ListenerData
|
||||
* @typedef {import('../schema').HandshakeRequest} HandshakeRequest
|
||||
* @typedef {import('../schema').Message} Message
|
||||
* @typedef {import('../schema').Outgoing} Outgoing
|
||||
* @typedef {import('../schema').PartialOutgoing} PartialOutgoing
|
||||
* @typedef {import('../schema').Chat} Chat
|
||||
* @typedef {import('../schema').ChatMessage} ChatMessage
|
||||
* @typedef {import('../schema').SimpleSentRequest} SimpleSentRequest
|
||||
* @typedef {import('../schema').SimpleReceivedRequest} SimpleReceivedRequest
|
||||
*/
|
||||
|
||||
const DEBOUNCE_WAIT_TIME = 500
|
||||
|
||||
/**
|
||||
* @param {(userToIncoming: Record<string, string>) => void} cb
|
||||
* @param {UserGUNNode} user Pass only for testing purposes.
|
||||
* @param {ISEA} SEA
|
||||
* @returns {void}
|
||||
*/
|
||||
const __onUserToIncoming = (cb, user, SEA) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const userToIncoming = {}
|
||||
|
||||
const mySecret = require('../../Mediator').getMySecret()
|
||||
|
||||
user
|
||||
.get(Key.USER_TO_INCOMING)
|
||||
.map()
|
||||
.on(async (encryptedIncomingID, userPub) => {
|
||||
if (typeof encryptedIncomingID !== 'string') {
|
||||
if (encryptedIncomingID === null) {
|
||||
// on disconnect
|
||||
delete userToIncoming[userPub]
|
||||
} else {
|
||||
console.error(
|
||||
'got a non string non null value inside user to incoming'
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (encryptedIncomingID.length === 0) {
|
||||
console.error('got an empty string value')
|
||||
return
|
||||
}
|
||||
|
||||
const incomingID = await SEA.decrypt(encryptedIncomingID, mySecret)
|
||||
|
||||
if (typeof incomingID === 'undefined') {
|
||||
console.warn('could not decrypt incomingID inside __onUserToIncoming')
|
||||
return
|
||||
}
|
||||
|
||||
userToIncoming[userPub] = incomingID
|
||||
|
||||
callb(userToIncoming)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(avatar: string|null) => void} cb
|
||||
* @param {UserGUNNode} user Pass only for testing purposes.
|
||||
* @throws {Error} If user hasn't been auth.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onAvatar = (cb, user) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
// Initial value if avvatar is undefined in gun
|
||||
callb(null)
|
||||
|
||||
user
|
||||
.get(Key.PROFILE)
|
||||
.get(Key.AVATAR)
|
||||
.on(avatar => {
|
||||
if (typeof avatar === 'string' || avatar === null) {
|
||||
callb(avatar)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(blacklist: string[]) => void} cb
|
||||
* @param {UserGUNNode} user
|
||||
* @returns {void}
|
||||
*/
|
||||
const onBlacklist = (cb, user) => {
|
||||
/** @type {string[]} */
|
||||
const blacklist = []
|
||||
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
|
||||
// Initial value if no items are in blacklist in gun
|
||||
callb(blacklist)
|
||||
|
||||
user
|
||||
.get(Key.BLACKLIST)
|
||||
.map()
|
||||
.on(publicKey => {
|
||||
if (typeof publicKey === 'string' && publicKey.length > 0) {
|
||||
blacklist.push(publicKey)
|
||||
callb(blacklist)
|
||||
} else {
|
||||
console.warn('Invalid public key received for blacklist')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(currentHandshakeAddress: string|null) => void} cb
|
||||
* @param {UserGUNNode} user
|
||||
* @returns {void}
|
||||
*/
|
||||
const onCurrentHandshakeAddress = (cb, user) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
|
||||
// If undefined, callback below wont be called. Let's supply null as the
|
||||
// initial value.
|
||||
callb(null)
|
||||
|
||||
user.get(Key.CURRENT_HANDSHAKE_ADDRESS).on(addr => {
|
||||
if (typeof addr !== 'string') {
|
||||
console.error('expected handshake address to be an string')
|
||||
|
||||
callb(null)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
callb(addr)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(displayName: string|null) => void} cb
|
||||
* @param {UserGUNNode} user Pass only for testing purposes.
|
||||
* @throws {Error} If user hasn't been auth.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onDisplayName = (cb, user) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
|
||||
// Initial value if display name is undefined in gun
|
||||
callb(null)
|
||||
|
||||
user
|
||||
.get(Key.PROFILE)
|
||||
.get(Key.DISPLAY_NAME)
|
||||
.on(displayName => {
|
||||
if (typeof displayName === 'string' || displayName === null) {
|
||||
callb(displayName)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(messages: Record<string, Message>) => void} cb
|
||||
* @param {string} userPK Public key of the user from whom the incoming
|
||||
* messages will be obtained.
|
||||
* @param {string} incomingFeedID ID of the outgoing feed from which the
|
||||
* incoming messages will be obtained.
|
||||
* @param {GUNNode} gun (Pass only for testing purposes)
|
||||
* @param {UserGUNNode} user
|
||||
* @param {ISEA} SEA
|
||||
* @returns {void}
|
||||
*/
|
||||
const onIncomingMessages = (cb, userPK, incomingFeedID, gun, user, SEA) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
|
||||
const otherUser = gun.user(userPK)
|
||||
|
||||
/**
|
||||
* @type {Record<string, Message>}
|
||||
*/
|
||||
const messages = {}
|
||||
|
||||
callb(messages)
|
||||
|
||||
otherUser
|
||||
.get(Key.OUTGOINGS)
|
||||
.get(incomingFeedID)
|
||||
.get(Key.MESSAGES)
|
||||
.map()
|
||||
.on(async (data, key) => {
|
||||
if (!Schema.isMessage(data)) {
|
||||
console.warn('non-message received')
|
||||
return
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
const recipientEpub = await Utils.pubToEpub(userPK)
|
||||
|
||||
const secret = await SEA.secret(recipientEpub, user._.sea)
|
||||
|
||||
let { body } = data
|
||||
body = await SEA.decrypt(body, secret)
|
||||
|
||||
messages[key] = {
|
||||
body,
|
||||
timestamp: data.timestamp
|
||||
}
|
||||
|
||||
callb(messages)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Record<string, Outgoing|null>} Outgoings
|
||||
* @typedef {(outgoings: Outgoings) => void} OutgoingsListener
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Outgoings}
|
||||
*/
|
||||
let currentOutgoings = {}
|
||||
|
||||
const getCurrentOutgoings = () => currentOutgoings
|
||||
|
||||
/** @type {Set<OutgoingsListener>} */
|
||||
const outgoingsListeners = new Set()
|
||||
|
||||
const notifyOutgoingsListeners = () => {
|
||||
outgoingsListeners.forEach(l => l(currentOutgoings))
|
||||
}
|
||||
|
||||
let outSubbed = false
|
||||
|
||||
/**
|
||||
* @param {OutgoingsListener} cb
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const onOutgoing = cb => {
|
||||
outgoingsListeners.add(cb)
|
||||
cb(currentOutgoings)
|
||||
|
||||
if (!outSubbed) {
|
||||
const user = require('../../Mediator').getUser()
|
||||
user.get(Key.OUTGOINGS).open(
|
||||
debounce(async data => {
|
||||
try {
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
currentOutgoings = {}
|
||||
notifyOutgoingsListeners()
|
||||
return
|
||||
}
|
||||
|
||||
/** @type {Record<string, Outgoing|null>} */
|
||||
const newOuts = {}
|
||||
|
||||
const SEA = require('../../Mediator').mySEA
|
||||
const mySecret = await Utils.mySecret()
|
||||
|
||||
await Utils.asyncForEach(Object.entries(data), 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(
|
||||
await Utils.pubToEpub(pub),
|
||||
user._.sea
|
||||
)
|
||||
|
||||
if (typeof messages === 'object' && messages !== null) {
|
||||
await Utils.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
|
||||
notifyOutgoingsListeners()
|
||||
} catch (e) {
|
||||
console.log('--------------------------')
|
||||
console.log('Events -> onOutgoing')
|
||||
console.log(e)
|
||||
console.log('--------------------------')
|
||||
}
|
||||
}, 400)
|
||||
)
|
||||
|
||||
outSubbed = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
outgoingsListeners.delete(cb)
|
||||
}
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* @typedef {(chats: Chat[]) => void} ChatsListener
|
||||
*/
|
||||
|
||||
/** @type {Chat[]} */
|
||||
let currentChats = []
|
||||
|
||||
/** @type {Set<ChatsListener>} */
|
||||
const chatsListeners = new Set()
|
||||
|
||||
const notifyChatsListeners = () => {
|
||||
chatsListeners.forEach(l => l(currentChats))
|
||||
}
|
||||
|
||||
const processChats = debounce(() => {
|
||||
const pubToAvatar = Streams.getPubToAvatar()
|
||||
const pubToDn = Streams.getPubToDn()
|
||||
const existingOutgoings = /** @type {[string, Outgoing][]} */ (Object.entries(
|
||||
getCurrentOutgoings()
|
||||
).filter(([_, o]) => o !== null))
|
||||
const pubToFeed = Streams.getPubToFeed()
|
||||
|
||||
/** @type {Chat[]} */
|
||||
const newChats = []
|
||||
|
||||
for (const [outID, out] of existingOutgoings) {
|
||||
if (typeof pubToAvatar[out.with] === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
Streams.onAvatar(() => {}, out.with)
|
||||
}
|
||||
if (typeof pubToDn[out.with] === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
Streams.onDisplayName(() => {}, out.with)
|
||||
}
|
||||
|
||||
/** @type {ChatMessage[]} */
|
||||
let msgs = Object.entries(out.messages).map(([mid, m]) => ({
|
||||
id: mid,
|
||||
outgoing: true,
|
||||
body: m.body,
|
||||
timestamp: m.timestamp
|
||||
}))
|
||||
|
||||
const incoming = pubToFeed[out.with]
|
||||
|
||||
if (Array.isArray(incoming)) {
|
||||
msgs = [...msgs, ...incoming]
|
||||
}
|
||||
|
||||
/** @type {Chat} */
|
||||
const chat = {
|
||||
recipientPublicKey: out.with,
|
||||
didDisconnect: pubToFeed[out.with] === 'disconnected',
|
||||
id: out.with + outID,
|
||||
messages: msgs,
|
||||
recipientAvatar: pubToAvatar[out.with] || null,
|
||||
recipientDisplayName: pubToDn[out.with] || null
|
||||
}
|
||||
|
||||
newChats.push(chat)
|
||||
}
|
||||
|
||||
currentChats = newChats.filter(
|
||||
c =>
|
||||
Array.isArray(pubToFeed[c.recipientPublicKey]) ||
|
||||
pubToFeed[c.recipientPublicKey] === 'disconnected'
|
||||
)
|
||||
|
||||
notifyChatsListeners()
|
||||
}, 750)
|
||||
|
||||
let onChatsSubbed = false
|
||||
|
||||
/**
|
||||
* Massages all of the more primitive data structures into a more manageable
|
||||
* 'Chat' paradigm.
|
||||
* @param {ChatsListener} cb
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const onChats = cb => {
|
||||
if (!chatsListeners.add(cb)) {
|
||||
throw new Error('Tried to subscribe twice')
|
||||
}
|
||||
cb(currentChats)
|
||||
|
||||
if (!onChatsSubbed) {
|
||||
onOutgoing(processChats)
|
||||
Streams.onAvatar(processChats)
|
||||
Streams.onDisplayName(processChats)
|
||||
Streams.onPubToFeed(processChats)
|
||||
|
||||
onChatsSubbed = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (!chatsListeners.delete(cb)) {
|
||||
throw new Error('Tried to unsubscribe twice')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {string|null} */
|
||||
let currentBio = null
|
||||
|
||||
/**
|
||||
* @param {(bio: string|null) => void} cb
|
||||
* @param {UserGUNNode} user Pass only for testing purposes.
|
||||
* @throws {Error} If user hasn't been auth.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onBio = (cb, user) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
// Initial value if avvatar is undefined in gun
|
||||
callb(currentBio)
|
||||
|
||||
user.get(Key.BIO).on(bio => {
|
||||
if (typeof bio === 'string' || bio === null) {
|
||||
currentBio = bio
|
||||
callb(bio)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** @type {string|null} */
|
||||
let currentSeedBackup = null
|
||||
|
||||
/**
|
||||
* @param {(seedBackup: string|null) => void} cb
|
||||
* @param {UserGUNNode} user
|
||||
* @param {ISEA} SEA
|
||||
* @throws {Error} If user hasn't been auth.
|
||||
* @returns {void}
|
||||
*/
|
||||
const onSeedBackup = (cb, user, SEA) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const mySecret = require('../../Mediator').getMySecret()
|
||||
|
||||
const callb = debounce(cb, DEBOUNCE_WAIT_TIME)
|
||||
callb(currentSeedBackup)
|
||||
|
||||
user.get(Key.SEED_BACKUP).on(async seedBackup => {
|
||||
if (typeof seedBackup === 'string') {
|
||||
currentSeedBackup = await SEA.decrypt(seedBackup, mySecret)
|
||||
callb(currentSeedBackup)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
__onUserToIncoming,
|
||||
onAvatar,
|
||||
onBlacklist,
|
||||
onCurrentHandshakeAddress,
|
||||
onDisplayName,
|
||||
onIncomingMessages,
|
||||
onOutgoing,
|
||||
getCurrentOutgoings,
|
||||
onSimplerReceivedRequests: require('./onReceivedReqs').onReceivedReqs,
|
||||
onSimplerSentRequests: require('./onSentReqs').onSentReqs,
|
||||
getCurrentSentReqs: require('./onSentReqs').getCurrentSentReqs,
|
||||
onBio,
|
||||
onSeedBackup,
|
||||
onChats
|
||||
}
|
||||
147
services/gunDB/contact-api/events/onReceivedReqs.js
Normal file
147
services/gunDB/contact-api/events/onReceivedReqs.js
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/** @format */
|
||||
const debounce = require('lodash/debounce')
|
||||
|
||||
const Key = require('../key')
|
||||
const Schema = require('../schema')
|
||||
const Streams = require('../streams')
|
||||
|
||||
/**
|
||||
* @typedef {Readonly<Schema.SimpleReceivedRequest>} SimpleReceivedRequest
|
||||
* @typedef {(reqs: ReadonlyArray<SimpleReceivedRequest>) => void} Listener
|
||||
*/
|
||||
|
||||
/** @type {Set<Listener>} */
|
||||
const listeners = new Set()
|
||||
|
||||
/** @type {string|null} */
|
||||
let currentAddress = null
|
||||
|
||||
/** @type {Record<string, SimpleReceivedRequest>} */
|
||||
let currReceivedReqsMap = {}
|
||||
|
||||
/**
|
||||
* Unprocessed requests in current handshake node.
|
||||
* @type {Record<string, Schema.HandshakeRequest>}
|
||||
*/
|
||||
let currAddressData = {}
|
||||
|
||||
/** @returns {SimpleReceivedRequest[]} */
|
||||
const getReceivedReqs = () => Object.values(currReceivedReqsMap)
|
||||
/** @param {Record<string, SimpleReceivedRequest>} reqs */
|
||||
const setReceivedReqsMap = reqs => {
|
||||
currReceivedReqsMap = reqs
|
||||
listeners.forEach(l => l(getReceivedReqs()))
|
||||
}
|
||||
|
||||
listeners.add(() => {
|
||||
console.log(
|
||||
`new received reqs: ${JSON.stringify(getReceivedReqs(), null, 4)}`
|
||||
)
|
||||
})
|
||||
|
||||
const react = debounce(() => {
|
||||
/** @type {Record<string, SimpleReceivedRequest>} */
|
||||
const newReceivedReqsMap = {}
|
||||
|
||||
const pubToFeed = Streams.getPubToFeed()
|
||||
const pubToAvatar = Streams.getPubToAvatar()
|
||||
const pubToDn = Streams.getPubToDn()
|
||||
|
||||
for (const [id, req] of Object.entries(currAddressData)) {
|
||||
const inContact = Array.isArray(pubToFeed[req.from])
|
||||
|
||||
if (typeof pubToAvatar[req.from] === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
Streams.onAvatar(() => {}, req.from)()
|
||||
}
|
||||
if (typeof pubToDn[req.from] === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
Streams.onDisplayName(() => {}, req.from)()
|
||||
}
|
||||
|
||||
if (!inContact) {
|
||||
newReceivedReqsMap[req.from] = {
|
||||
id,
|
||||
requestorAvatar: pubToAvatar[req.from] || null,
|
||||
requestorDisplayName: pubToDn[req.from] || null,
|
||||
requestorPK: req.from,
|
||||
timestamp: req.timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setReceivedReqsMap(newReceivedReqsMap)
|
||||
}, 750)
|
||||
|
||||
/**
|
||||
* @param {string} addr
|
||||
* @returns {(data: import('../SimpleGUN').OpenListenerData) => void}
|
||||
*/
|
||||
const listenerForAddr = addr => data => {
|
||||
// did invalidate
|
||||
if (addr !== currentAddress) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
currAddressData = {}
|
||||
} else {
|
||||
for (const [id, req] of Object.entries(data)) {
|
||||
// no need to update them just write them once
|
||||
if (Schema.isHandshakeRequest(req) && !currAddressData[id]) {
|
||||
currAddressData[id] = req
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('data for address: ' + addr)
|
||||
console.log(JSON.stringify(data, null, 4))
|
||||
|
||||
react()
|
||||
}
|
||||
|
||||
let subbed = false
|
||||
|
||||
/**
|
||||
* @param {Listener} cb
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const onReceivedReqs = cb => {
|
||||
listeners.add(cb)
|
||||
cb(getReceivedReqs())
|
||||
|
||||
if (!subbed) {
|
||||
require('./index').onCurrentHandshakeAddress(addr => {
|
||||
if (currentAddress === addr) {
|
||||
return
|
||||
}
|
||||
|
||||
currentAddress = addr
|
||||
currAddressData = {}
|
||||
setReceivedReqsMap({})
|
||||
|
||||
if (typeof addr === 'string') {
|
||||
require('../../Mediator')
|
||||
.getGun()
|
||||
.get(Key.HANDSHAKE_NODES)
|
||||
.get(addr)
|
||||
.open(listenerForAddr(addr))
|
||||
}
|
||||
}, require('../../Mediator').getUser())
|
||||
|
||||
Streams.onAvatar(react)
|
||||
Streams.onDisplayName(react)
|
||||
Streams.onPubToFeed(react)
|
||||
|
||||
subbed = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
listeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getReceivedReqs,
|
||||
onReceivedReqs
|
||||
}
|
||||
124
services/gunDB/contact-api/events/onSentReqs.js
Normal file
124
services/gunDB/contact-api/events/onSentReqs.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/** @format */
|
||||
const debounce = require('lodash/debounce')
|
||||
|
||||
const Streams = require('../streams')
|
||||
/**
|
||||
* @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode
|
||||
* @typedef {import('../SimpleGUN').GUNNode} GUNNode
|
||||
* @typedef {import('../SimpleGUN').ISEA} ISEA
|
||||
* @typedef {import('../SimpleGUN').ListenerData} ListenerData
|
||||
* @typedef {import('../schema').HandshakeRequest} HandshakeRequest
|
||||
* @typedef {import('../schema').Message} Message
|
||||
* @typedef {import('../schema').Outgoing} Outgoing
|
||||
* @typedef {import('../schema').PartialOutgoing} PartialOutgoing
|
||||
* @typedef {import('../schema').Chat} Chat
|
||||
* @typedef {import('../schema').ChatMessage} ChatMessage
|
||||
* @typedef {import('../schema').SimpleSentRequest} SimpleSentRequest
|
||||
* @typedef {import('../schema').SimpleReceivedRequest} SimpleReceivedRequest
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {(chats: SimpleSentRequest[]) => void} Listener
|
||||
*/
|
||||
|
||||
/** @type {Set<Listener>} */
|
||||
const listeners = new Set()
|
||||
|
||||
/** @type {SimpleSentRequest[]} */
|
||||
let currentReqs = []
|
||||
|
||||
listeners.add(() => {
|
||||
console.log(`new sent reqs: ${JSON.stringify(currentReqs)}`)
|
||||
})
|
||||
|
||||
const getCurrentSentReqs = () => currentReqs
|
||||
|
||||
const react = debounce(() => {
|
||||
/** @type {SimpleSentRequest[]} */
|
||||
const newReqs = []
|
||||
|
||||
const pubToHAddr = Streams.getAddresses()
|
||||
const storedReqs = Streams.getStoredReqs()
|
||||
const pubToLastSentReqID = Streams.getSentReqIDs()
|
||||
const pubToFeed = Streams.getPubToFeed()
|
||||
const pubToAvatar = Streams.getPubToAvatar()
|
||||
const pubToDN = Streams.getPubToDn()
|
||||
|
||||
console.log(
|
||||
`pubToLastSentREqID: ${JSON.stringify(pubToLastSentReqID, null, 4)}`
|
||||
)
|
||||
|
||||
for (const storedReq of storedReqs) {
|
||||
const { handshakeAddress, recipientPub, sentReqID, timestamp } = storedReq
|
||||
const currAddress = pubToHAddr[recipientPub]
|
||||
|
||||
const lastReqID = pubToLastSentReqID[recipientPub]
|
||||
const isStale = typeof lastReqID !== 'undefined' && lastReqID !== sentReqID
|
||||
const isConnected = Array.isArray(pubToFeed[recipientPub])
|
||||
|
||||
if (isStale || isConnected) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue
|
||||
}
|
||||
|
||||
if (typeof currAddress === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
Streams.onAddresses(() => {}, recipientPub)()
|
||||
}
|
||||
if (typeof pubToAvatar[recipientPub] === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
Streams.onAvatar(() => {}, recipientPub)()
|
||||
}
|
||||
if (typeof pubToDN[recipientPub] === 'undefined') {
|
||||
// eslint-disable-next-line no-empty-function
|
||||
Streams.onDisplayName(() => {}, recipientPub)()
|
||||
}
|
||||
|
||||
newReqs.push({
|
||||
id: sentReqID,
|
||||
recipientAvatar: pubToAvatar[recipientPub] || null,
|
||||
recipientChangedRequestAddress:
|
||||
typeof currAddress !== 'undefined' && handshakeAddress !== currAddress,
|
||||
recipientDisplayName: pubToDN[recipientPub] || null,
|
||||
recipientPublicKey: recipientPub,
|
||||
timestamp
|
||||
})
|
||||
}
|
||||
|
||||
currentReqs = newReqs
|
||||
|
||||
listeners.forEach(l => l(currentReqs))
|
||||
}, 750)
|
||||
|
||||
let subbed = false
|
||||
|
||||
/**
|
||||
* Massages all of the more primitive data structures into a more manageable
|
||||
* 'Chat' paradigm.
|
||||
* @param {Listener} cb
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const onSentReqs = cb => {
|
||||
listeners.add(cb)
|
||||
cb(currentReqs)
|
||||
|
||||
if (!subbed) {
|
||||
Streams.onAddresses(react)
|
||||
Streams.onStoredReqs(react)
|
||||
Streams.onLastSentReqIDs(react)
|
||||
Streams.onPubToFeed(react)
|
||||
Streams.onAvatar(react)
|
||||
Streams.onDisplayName(react)
|
||||
|
||||
subbed = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
listeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
onSentReqs,
|
||||
getCurrentSentReqs
|
||||
}
|
||||
|
|
@ -14,3 +14,16 @@ exports.currentOrderAddress = async (pub) => {
|
|||
|
||||
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
|
||||
|
||||
}
|
||||
|
|
@ -16,14 +16,14 @@ const Utils = require('../utils')
|
|||
* @throws {Error} NOT_AUTH
|
||||
* @param {UserGUNNode} user
|
||||
* @param {ISEA} SEA
|
||||
* @returns {Promise<void>}
|
||||
* @returns {void}
|
||||
*/
|
||||
const onAcceptedRequests = async (user, SEA) => {
|
||||
const onAcceptedRequests = (user, SEA) => {
|
||||
if (!user.is) {
|
||||
throw new Error(ErrorCode.NOT_AUTH)
|
||||
}
|
||||
|
||||
const mySecret = await SEA.secret(user._.sea.epub, user._.sea)
|
||||
const mySecret = require('../../Mediator').getMySecret()
|
||||
|
||||
if (typeof mySecret !== 'string') {
|
||||
console.log("Jobs.onAcceptedRequests() -> typeof mySecret !== 'string'")
|
||||
|
|
@ -33,7 +33,7 @@ const onAcceptedRequests = async (user, SEA) => {
|
|||
user
|
||||
.get(Key.STORED_REQS)
|
||||
.map()
|
||||
.once(async storedReq => {
|
||||
.once(async (storedReq, id) => {
|
||||
try {
|
||||
if (!Schema.isStoredRequest(storedReq)) {
|
||||
throw new TypeError('Stored request not an StoredRequest')
|
||||
|
|
@ -123,10 +123,31 @@ const onAcceptedRequests = async (user, SEA) => {
|
|||
mySecret
|
||||
)
|
||||
|
||||
user
|
||||
.get(Key.USER_TO_INCOMING)
|
||||
.get(recipientPub)
|
||||
.put(encryptedForMeIncomingID)
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.USER_TO_INCOMING)
|
||||
.get(recipientPub)
|
||||
.put(encryptedForMeIncomingID, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await new Promise((res, rej) => {
|
||||
user
|
||||
.get(Key.STORED_REQS)
|
||||
.get(id)
|
||||
.put(null, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// ensure this listeners gets called at least once
|
||||
res()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ exports.RECIPIENT_TO_OUTGOING = 'recipientToOutgoing'
|
|||
exports.USER_TO_INCOMING = 'userToIncoming'
|
||||
|
||||
exports.STORED_REQS = 'storedReqs'
|
||||
exports.REQUEST_TO_USER = 'requestToUser'
|
||||
|
||||
exports.BLACKLIST = 'blacklist'
|
||||
|
||||
|
|
|
|||
|
|
@ -65,10 +65,13 @@ exports.isChatMessage = item => {
|
|||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -102,6 +105,14 @@ exports.isChat = item => {
|
|||
return false
|
||||
}
|
||||
|
||||
if (typeof obj.didDisconnect !== 'boolean') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof obj.id !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
return obj.messages.every(msg => exports.isChatMessage(msg))
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +146,8 @@ exports.isStoredRequest = item => {
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +213,6 @@ exports.isSimpleSentRequest = item => {
|
|||
* @prop {string|null} requestorAvatar
|
||||
* @prop {string|null} requestorDisplayName
|
||||
* @prop {string} requestorPK
|
||||
* @prop {string} response
|
||||
* @prop {number} timestamp
|
||||
*/
|
||||
|
||||
|
|
@ -238,10 +250,6 @@ exports.isSimpleReceivedRequest = item => {
|
|||
return false
|
||||
}
|
||||
|
||||
if (typeof obj.response !== 'string') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof obj.timestamp !== 'number') {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
53
services/gunDB/contact-api/streams/addresses.js
Normal file
53
services/gunDB/contact-api/streams/addresses.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/** @format */
|
||||
const Key = require('../key')
|
||||
/**
|
||||
* @typedef {Record<string, string|null|undefined>} Addresses
|
||||
*/
|
||||
|
||||
/** @type {Addresses} */
|
||||
const pubToAddress = {}
|
||||
|
||||
/** @type {Set<() => void>} */
|
||||
const listeners = new Set()
|
||||
|
||||
listeners.add(() => {
|
||||
console.log(`pubToAddress: ${JSON.stringify(pubToAddress, null, 4)}`)
|
||||
})
|
||||
|
||||
const notify = () => listeners.forEach(l => l())
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const subbedPublicKeys = new Set()
|
||||
|
||||
/**
|
||||
* @param {() => void} cb
|
||||
* @param {string=} pub
|
||||
*/
|
||||
const onAddresses = (cb, pub) => {
|
||||
listeners.add(cb)
|
||||
cb()
|
||||
if (pub && subbedPublicKeys.add(pub)) {
|
||||
require('../../Mediator')
|
||||
.getGun()
|
||||
.user(pub)
|
||||
.get(Key.CURRENT_HANDSHAKE_ADDRESS)
|
||||
.on(addr => {
|
||||
if (typeof addr === 'string' || addr === null) {
|
||||
pubToAddress[pub] = addr
|
||||
} else {
|
||||
pubToAddress[pub] = null
|
||||
}
|
||||
notify()
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
listeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
const getAddresses = () => pubToAddress
|
||||
|
||||
module.exports = {
|
||||
onAddresses,
|
||||
getAddresses
|
||||
}
|
||||
191
services/gunDB/contact-api/streams/index.js
Normal file
191
services/gunDB/contact-api/streams/index.js
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/** @format */
|
||||
const Key = require('../key')
|
||||
const Schema = require('../schema')
|
||||
const Utils = require('../utils')
|
||||
/**
|
||||
* @typedef {Record<string, string|null|undefined>} Avatars
|
||||
* @typedef {(avatars: Avatars) => void} AvatarListener
|
||||
*/
|
||||
|
||||
/** @type {Avatars} */
|
||||
const pubToAvatar = {}
|
||||
|
||||
const getPubToAvatar = () => pubToAvatar
|
||||
|
||||
/** @type {Set<AvatarListener>} */
|
||||
const avatarListeners = new Set()
|
||||
|
||||
const notifyAvatarListeners = () => {
|
||||
avatarListeners.forEach(l => l(pubToAvatar))
|
||||
}
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const pubsWithAvatarListeners = new Set()
|
||||
|
||||
/**
|
||||
* @param {AvatarListener} cb
|
||||
* @param {string=} pub
|
||||
*/
|
||||
const onAvatar = (cb, pub) => {
|
||||
avatarListeners.add(cb)
|
||||
cb(pubToAvatar)
|
||||
if (pub && pubsWithAvatarListeners.add(pub)) {
|
||||
require('../../Mediator')
|
||||
.getGun()
|
||||
.user(pub)
|
||||
.get(Key.PROFILE)
|
||||
.get(Key.AVATAR)
|
||||
.on(av => {
|
||||
if (typeof av === 'string' || av === null) {
|
||||
pubToAvatar[pub] = av || null
|
||||
} else {
|
||||
pubToAvatar[pub] = null
|
||||
}
|
||||
notifyAvatarListeners()
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
avatarListeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Record<string, string|null|undefined>} DisplayNames
|
||||
* @typedef {(avatars: Avatars) => void} DisplayNameListener
|
||||
*/
|
||||
|
||||
/** @type {DisplayNames} */
|
||||
const pubToDisplayName = {}
|
||||
|
||||
const getPubToDn = () => pubToDisplayName
|
||||
|
||||
/** @type {Set<DisplayNameListener>} */
|
||||
const displayNameListeners = new Set()
|
||||
|
||||
const notifyDisplayNameListeners = () => {
|
||||
displayNameListeners.forEach(l => l(pubToDisplayName))
|
||||
}
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const pubsWithDisplayNameListeners = new Set()
|
||||
|
||||
/**
|
||||
* @param {DisplayNameListener} cb
|
||||
* @param {string=} pub
|
||||
*/
|
||||
const onDisplayName = (cb, pub) => {
|
||||
displayNameListeners.add(cb)
|
||||
cb(pubToDisplayName)
|
||||
if (pub && pubsWithDisplayNameListeners.add(pub)) {
|
||||
require('../../Mediator')
|
||||
.getGun()
|
||||
.user(pub)
|
||||
.get(Key.PROFILE)
|
||||
.get(Key.DISPLAY_NAME)
|
||||
.on(dn => {
|
||||
if (typeof dn === 'string' || dn === null) {
|
||||
pubToDisplayName[pub] = dn || null
|
||||
} else {
|
||||
pubToDisplayName[pub] = null
|
||||
}
|
||||
notifyDisplayNameListeners()
|
||||
})
|
||||
}
|
||||
return () => {
|
||||
displayNameListeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('../schema').StoredRequest} StoredRequest
|
||||
* @typedef {(reqs: StoredRequest[]) => void} StoredRequestsListener
|
||||
*/
|
||||
|
||||
/** @type {Set<StoredRequestsListener>} */
|
||||
const storedRequestsListeners = new Set()
|
||||
|
||||
/**
|
||||
* @type {StoredRequest[]}
|
||||
*/
|
||||
let encryptedStoredReqs = []
|
||||
|
||||
/**
|
||||
* @type {StoredRequest[]}
|
||||
*/
|
||||
let currentStoredReqs = []
|
||||
|
||||
const getStoredReqs = () => currentStoredReqs
|
||||
|
||||
const processStoredReqs = async () => {
|
||||
const ereqs = encryptedStoredReqs
|
||||
encryptedStoredReqs = []
|
||||
const mySecret = await Utils.mySecret()
|
||||
const SEA = require('../../Mediator').mySEA
|
||||
const finalReqs = await Utils.asyncMap(ereqs, async er => {
|
||||
/** @type {StoredRequest} */
|
||||
const r = {
|
||||
handshakeAddress: await SEA.decrypt(er.handshakeAddress, mySecret),
|
||||
recipientPub: await SEA.decrypt(er.recipientPub, mySecret),
|
||||
sentReqID: await SEA.decrypt(er.sentReqID, mySecret),
|
||||
timestamp: er.timestamp
|
||||
}
|
||||
|
||||
return r
|
||||
})
|
||||
currentStoredReqs = finalReqs
|
||||
storedRequestsListeners.forEach(l => l(currentStoredReqs))
|
||||
}
|
||||
|
||||
let storedReqsSubbed = false
|
||||
|
||||
/**
|
||||
* @param {StoredRequestsListener} cb
|
||||
*/
|
||||
const onStoredReqs = cb => {
|
||||
storedRequestsListeners.add(cb)
|
||||
|
||||
if (!storedReqsSubbed) {
|
||||
require('../../Mediator')
|
||||
.getUser()
|
||||
.get(Key.STORED_REQS)
|
||||
.open(d => {
|
||||
if (typeof d === 'object' && d !== null) {
|
||||
encryptedStoredReqs = /** @type {StoredRequest[]} */ (Object.values(
|
||||
d
|
||||
).filter(i => Schema.isStoredRequest(i)))
|
||||
}
|
||||
|
||||
processStoredReqs()
|
||||
})
|
||||
|
||||
storedReqsSubbed = true
|
||||
}
|
||||
|
||||
cb(currentStoredReqs)
|
||||
|
||||
return () => {
|
||||
storedRequestsListeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
onAvatar,
|
||||
getPubToAvatar,
|
||||
onDisplayName,
|
||||
getPubToDn,
|
||||
|
||||
onPubToIncoming: require('./pubToIncoming').onPubToIncoming,
|
||||
getPubToIncoming: require('./pubToIncoming').getPubToIncoming,
|
||||
setPubToIncoming: require('./pubToIncoming').setPubToIncoming,
|
||||
|
||||
onPubToFeed: require('./pubToFeed').onPubToFeed,
|
||||
getPubToFeed: require('./pubToFeed').getPubToFeed,
|
||||
|
||||
onStoredReqs,
|
||||
getStoredReqs,
|
||||
onAddresses: require('./addresses').onAddresses,
|
||||
getAddresses: require('./addresses').getAddresses,
|
||||
onLastSentReqIDs: require('./lastSentReqID').onLastSentReqIDs,
|
||||
getSentReqIDs: require('./lastSentReqID').getSentReqIDs,
|
||||
PubToIncoming: require('./pubToIncoming')
|
||||
}
|
||||
50
services/gunDB/contact-api/streams/lastSentReqID.js
Normal file
50
services/gunDB/contact-api/streams/lastSentReqID.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/** @format */
|
||||
const Key = require('../key')
|
||||
|
||||
/** @type {Record<string, string|null|undefined>} */
|
||||
let pubToLastSentReqID = {}
|
||||
|
||||
/** @type {Set<() => void>} */
|
||||
const listeners = new Set()
|
||||
const notify = () => listeners.forEach(l => l())
|
||||
|
||||
let subbed = false
|
||||
|
||||
/**
|
||||
* @param {() => void} cb
|
||||
*/
|
||||
const onLastSentReqIDs = cb => {
|
||||
listeners.add(cb)
|
||||
cb()
|
||||
|
||||
if (!subbed) {
|
||||
require('../../Mediator')
|
||||
.getUser()
|
||||
.get(Key.USER_TO_LAST_REQUEST_SENT)
|
||||
.open(data => {
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
for (const [pub, id] of Object.entries(data)) {
|
||||
if (typeof id === 'string' || id === null) {
|
||||
pubToLastSentReqID[pub] = id
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pubToLastSentReqID = {}
|
||||
}
|
||||
|
||||
notify()
|
||||
})
|
||||
subbed = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
listeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
const getSentReqIDs = () => pubToLastSentReqID
|
||||
|
||||
module.exports = {
|
||||
onLastSentReqIDs,
|
||||
getSentReqIDs
|
||||
}
|
||||
251
services/gunDB/contact-api/streams/pubToFeed.js
Normal file
251
services/gunDB/contact-api/streams/pubToFeed.js
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/** @format */
|
||||
const uuidv1 = require('uuid/v1')
|
||||
const logger = require('winston')
|
||||
const debounce = require('lodash/debounce')
|
||||
|
||||
const Schema = require('../schema')
|
||||
const Key = require('../key')
|
||||
const Utils = require('../utils')
|
||||
/**
|
||||
* @typedef {import('../schema').ChatMessage} Message
|
||||
* @typedef {import('../SimpleGUN').OpenListenerData} OpenListenerData
|
||||
*/
|
||||
|
||||
const PubToIncoming = require('./pubToIncoming')
|
||||
|
||||
/**
|
||||
* @typedef {Record<string, Message[]|null|undefined|'disconnected'>} Feeds
|
||||
* @typedef {(feeds: Feeds) => void} FeedsListener
|
||||
*/
|
||||
|
||||
/** @type {Set<FeedsListener>} */
|
||||
const feedsListeners = new Set()
|
||||
|
||||
/**
|
||||
* @type {Feeds}
|
||||
*/
|
||||
let pubToFeed = {}
|
||||
|
||||
const getPubToFeed = () => pubToFeed
|
||||
|
||||
feedsListeners.add(() => {
|
||||
console.log(`new pubToFeed: ${JSON.stringify(getPubToFeed())}`)
|
||||
})
|
||||
|
||||
/** @param {Feeds} ptf */
|
||||
const setPubToFeed = ptf => {
|
||||
pubToFeed = ptf
|
||||
feedsListeners.forEach(l => {
|
||||
l(pubToFeed)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* If at one point we subscribed to a feed, record it here. Keeps track of it
|
||||
* for unsubbing.
|
||||
*
|
||||
* Since we can't really unsub in GUN, what we do is that each listener created
|
||||
* checks the last incoming feed, if it was created for other feed that is not
|
||||
* the latest, it becomes inactive.
|
||||
* @type {Record<string, string|undefined|null>}
|
||||
*/
|
||||
const pubToLastIncoming = {}
|
||||
|
||||
/**
|
||||
* Any pub-feed pair listener will write its update id here when fired up. Avoid
|
||||
* race conditions between different listeners and between different invocations
|
||||
* of the same listener.
|
||||
* @type {Record<string, string>}
|
||||
*/
|
||||
const pubToLastUpdate = {}
|
||||
|
||||
/**
|
||||
* Performs a sub to a pub feed pair that will only emit if it is the last
|
||||
* subbed feed for that pub, according to `pubToLastIncoming`. This listener is
|
||||
* not in charge of writing to the cache.
|
||||
* @param {[ string , string ]} param0
|
||||
* @returns {(data: OpenListenerData) => void}
|
||||
*/
|
||||
const onOpenForPubFeedPair = ([pub, feed]) =>
|
||||
debounce(async data => {
|
||||
// did invalidate
|
||||
if (pubToLastIncoming[pub] !== feed) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
// did disconnect
|
||||
data === null ||
|
||||
// interpret as disconnect
|
||||
typeof data !== 'object'
|
||||
) {
|
||||
// invalidate this listener. If a reconnection happens it will be for a
|
||||
// different pub-feed pair.
|
||||
pubToLastIncoming[pub] = null
|
||||
setImmediate(() => {
|
||||
logger.info(
|
||||
`onOpenForPubFeedPair -> didDisconnect -> pub: ${pub} - feed: ${feed}`
|
||||
)
|
||||
})
|
||||
// signal disconnect to listeners listeners should rely on pubToFeed for
|
||||
// disconnect status instead of pub-to-incoming. Only the latter will
|
||||
// detect remote disconnection
|
||||
setPubToFeed({
|
||||
...getPubToFeed(),
|
||||
[pub]: /** @type {'disconnected'} */ ('disconnected')
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const incoming = /** @type {Schema.Outgoing} */ (data)
|
||||
|
||||
// incomplete data, let's not assume anything
|
||||
if (
|
||||
typeof incoming.with !== 'string' ||
|
||||
typeof incoming.messages !== 'object'
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
/** @type {Schema.ChatMessage[]} */
|
||||
const newMsgs = Object.entries(incoming.messages)
|
||||
// filter out messages with incomplete data
|
||||
.filter(([_, msg]) => Schema.isMessage(msg))
|
||||
.map(([id, msg]) => {
|
||||
/** @type {Schema.ChatMessage} */
|
||||
const m = {
|
||||
// we'll decrypt later
|
||||
body: msg.body,
|
||||
id,
|
||||
outgoing: false,
|
||||
timestamp: msg.timestamp
|
||||
}
|
||||
|
||||
return m
|
||||
})
|
||||
|
||||
if (newMsgs.length === 0) {
|
||||
setPubToFeed({
|
||||
...getPubToFeed(),
|
||||
[pub]: []
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const thisUpdate = uuidv1()
|
||||
pubToLastUpdate[pub] = thisUpdate
|
||||
|
||||
const user = require('../../Mediator').getUser()
|
||||
const SEA = require('../../Mediator').mySEA
|
||||
|
||||
const ourSecret = await SEA.secret(await Utils.pubToEpub(pub), user._.sea)
|
||||
|
||||
const decryptedMsgs = await Utils.asyncMap(newMsgs, async m => {
|
||||
/** @type {Schema.ChatMessage} */
|
||||
const decryptedMsg = {
|
||||
...m,
|
||||
body: await SEA.decrypt(m.body, ourSecret)
|
||||
}
|
||||
|
||||
return decryptedMsg
|
||||
})
|
||||
|
||||
// this listener got invalidated while we were awaiting the async operations
|
||||
// above.
|
||||
if (pubToLastUpdate[pub] !== thisUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
setPubToFeed({
|
||||
...getPubToFeed(),
|
||||
[pub]: decryptedMsgs
|
||||
})
|
||||
}, 750)
|
||||
|
||||
const react = () => {
|
||||
const pubToIncoming = PubToIncoming.getPubToIncoming()
|
||||
|
||||
const gun = require('../../Mediator').getGun()
|
||||
|
||||
/** @type {Feeds} */
|
||||
const newPubToFeed = {}
|
||||
|
||||
for (const [pub, inc] of Object.entries(pubToIncoming)) {
|
||||
/**
|
||||
* empty string -> null
|
||||
* @type {string|null}
|
||||
*/
|
||||
const newIncoming = inc || null
|
||||
|
||||
if (
|
||||
// if disconnected, the same incoming feed will try to overwrite the
|
||||
// nulled out pubToLastIncoming[pub] entry. Making the listener for that
|
||||
// pub feed pair fire up again, etc. Now. When the user disconnects from
|
||||
// this side of things. He will overwrite the pub to incoming with null.
|
||||
// Let's allow that.
|
||||
newIncoming === pubToLastIncoming[pub] &&
|
||||
!(pubToFeed[pub] === 'disconnected' && newIncoming === null)
|
||||
) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue
|
||||
}
|
||||
|
||||
// will invalidate stale listeners (a listener for an outdated incoming feed
|
||||
// id)
|
||||
pubToLastIncoming[pub] = newIncoming
|
||||
// Invalidate pending writes from stale listener(s) for the old incoming
|
||||
// address.
|
||||
pubToLastUpdate[pub] = uuidv1()
|
||||
newPubToFeed[pub] = newIncoming ? [] : null
|
||||
|
||||
// sub to this incoming feed
|
||||
if (typeof newIncoming === 'string') {
|
||||
// perform sub to pub-incoming_feed pair
|
||||
// leave all of the sideffects from this for the next tick
|
||||
setImmediate(() => {
|
||||
gun
|
||||
.user(pub)
|
||||
.get(Key.OUTGOINGS)
|
||||
.get(newIncoming)
|
||||
.open(onOpenForPubFeedPair([pub, newIncoming]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(newPubToFeed).length > 0) {
|
||||
setPubToFeed({
|
||||
...getPubToFeed(),
|
||||
...newPubToFeed
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let subbed = false
|
||||
|
||||
/**
|
||||
* Array.isArray(pubToFeed[pub]) means a Handshake is in place, look for
|
||||
* incoming messages here.
|
||||
* pubToIncoming[pub] === null means a disconnection took place.
|
||||
* typeof pubToIncoming[pub] === 'undefined' means none of the above.
|
||||
* @param {FeedsListener} cb
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const onPubToFeed = cb => {
|
||||
feedsListeners.add(cb)
|
||||
cb(getPubToFeed())
|
||||
|
||||
if (!subbed) {
|
||||
PubToIncoming.onPubToIncoming(react)
|
||||
subbed = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
feedsListeners.delete(cb)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPubToFeed,
|
||||
setPubToFeed,
|
||||
onPubToFeed
|
||||
}
|
||||
93
services/gunDB/contact-api/streams/pubToIncoming.js
Normal file
93
services/gunDB/contact-api/streams/pubToIncoming.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/** @format */
|
||||
const uuidv1 = require('uuid/v1')
|
||||
const debounce = require('lodash/debounce')
|
||||
|
||||
const { USER_TO_INCOMING } = require('../key')
|
||||
const { asyncForEach } = require('../utils')
|
||||
/** @typedef {import('../SimpleGUN').OpenListenerData} OpenListenerData */
|
||||
|
||||
/**
|
||||
* @typedef {Record<string, string|null|undefined>} PubToIncoming
|
||||
*/
|
||||
|
||||
/** @type {Set<() => void>} */
|
||||
const listeners = new Set()
|
||||
|
||||
/** @type {PubToIncoming} */
|
||||
let pubToIncoming = {}
|
||||
|
||||
const getPubToIncoming = () => pubToIncoming
|
||||
/**
|
||||
* @param {PubToIncoming} pti
|
||||
* @returns {void}
|
||||
*/
|
||||
const setPubToIncoming = pti => {
|
||||
pubToIncoming = pti
|
||||
listeners.forEach(l => l())
|
||||
}
|
||||
|
||||
let latestUpdate = uuidv1()
|
||||
|
||||
const onOpen = debounce(async uti => {
|
||||
const SEA = require('../../Mediator').mySEA
|
||||
const mySec = require('../../Mediator').getMySecret()
|
||||
const thisUpdate = uuidv1()
|
||||
latestUpdate = thisUpdate
|
||||
|
||||
if (typeof uti !== 'object' || uti === null) {
|
||||
setPubToIncoming({})
|
||||
return
|
||||
}
|
||||
|
||||
/** @type {PubToIncoming} */
|
||||
const newPubToIncoming = {}
|
||||
|
||||
await asyncForEach(Object.entries(uti), async ([pub, encFeedID]) => {
|
||||
if (encFeedID === null) {
|
||||
newPubToIncoming[pub] = null
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof encFeedID === 'string') {
|
||||
newPubToIncoming[pub] = await SEA.decrypt(encFeedID, mySec)
|
||||
}
|
||||
})
|
||||
|
||||
// avoid old data from overwriting new data if decrypting took longer to
|
||||
// process for the older open() call than for the newer open() call
|
||||
if (latestUpdate === thisUpdate) {
|
||||
setPubToIncoming(newPubToIncoming)
|
||||
}
|
||||
}, 750)
|
||||
|
||||
let subbed = false
|
||||
|
||||
/**
|
||||
* @param {() => void} cb
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const onPubToIncoming = cb => {
|
||||
if (!listeners.add(cb)) {
|
||||
throw new Error('Tried to subscribe twice')
|
||||
}
|
||||
|
||||
cb()
|
||||
|
||||
if (!subbed) {
|
||||
const user = require('../../Mediator').getUser()
|
||||
user.get(USER_TO_INCOMING).open(onOpen)
|
||||
subbed = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (!listeners.delete(cb)) {
|
||||
throw new Error('Tried to unsubscribe twice')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPubToIncoming,
|
||||
setPubToIncoming,
|
||||
onPubToIncoming
|
||||
}
|
||||
8
services/gunDB/contact-api/utils/PGUNNode.ts
Normal file
8
services/gunDB/contact-api/utils/PGUNNode.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/** @format */
|
||||
import { GUNNode, GUNNodeBase, ValidDataValue } from '../SimpleGUN'
|
||||
|
||||
export interface PGUNNode extends GUNNodeBase {
|
||||
get(key: string): PGUNNode
|
||||
put(data: ValidDataValue | GUNNode): Promise<void>
|
||||
set(data: ValidDataValue | GUNNode): Promise<void>
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
const ErrorCode = require('./errorCode')
|
||||
const Key = require('./key')
|
||||
const ErrorCode = require('../errorCode')
|
||||
const Key = require('../key')
|
||||
|
||||
/**
|
||||
* @typedef {import('./SimpleGUN').GUNNode} GUNNode
|
||||
* @typedef {import('./SimpleGUN').ISEA} ISEA
|
||||
* @typedef {import('./SimpleGUN').UserGUNNode} UserGUNNode
|
||||
* @typedef {import('../SimpleGUN').GUNNode} GUNNode
|
||||
* @typedef {import('../SimpleGUN').ISEA} ISEA
|
||||
* @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -16,6 +16,11 @@ const Key = require('./key')
|
|||
*/
|
||||
const delay = ms => new Promise(res => setTimeout(res, ms))
|
||||
|
||||
/**
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const mySecret = () => Promise.resolve(require('../../Mediator').getMySecret())
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Promise<T>} promise
|
||||
|
|
@ -41,8 +46,8 @@ const timeout10 = promise => {
|
|||
const tryAndWait = promGen =>
|
||||
timeout10(
|
||||
promGen(
|
||||
require('../Mediator/index').getGun(),
|
||||
require('../Mediator/index').getUser()
|
||||
require('../../Mediator/index').getGun(),
|
||||
require('../../Mediator/index').getUser()
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -74,39 +79,11 @@ const pubToEpub = async pub => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} reqID
|
||||
* @param {ISEA} SEA
|
||||
* @param {string} mySecret
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
const reqToRecipientPub = async (reqID, SEA, mySecret) => {
|
||||
const maybeEncryptedForMeRecipientPub = await tryAndWait(async (_, user) => {
|
||||
const reqToUser = user.get(Key.REQUEST_TO_USER)
|
||||
const data = await reqToUser.get(reqID).then()
|
||||
|
||||
if (typeof data !== 'string') {
|
||||
throw new TypeError("typeof maybeEncryptedForMeRecipientPub !== 'string'")
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
const encryptedForMeRecipientPub = maybeEncryptedForMeRecipientPub
|
||||
|
||||
const recipientPub = await SEA.decrypt(encryptedForMeRecipientPub, mySecret)
|
||||
|
||||
if (typeof recipientPub !== 'string') {
|
||||
throw new TypeError("typeof recipientPub !== 'string'")
|
||||
}
|
||||
|
||||
return recipientPub
|
||||
}
|
||||
|
||||
/**
|
||||
* Should only be called with a recipient pub that has already been contacted.
|
||||
* If returns null, a disconnect happened.
|
||||
* @param {string} recipientPub
|
||||
* @returns {Promise<string>}
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
const recipientPubToLastReqSentID = async recipientPub => {
|
||||
const lastReqSentID = await tryAndWait(async (_, user) => {
|
||||
|
|
@ -114,7 +91,7 @@ const recipientPubToLastReqSentID = async recipientPub => {
|
|||
const data = await userToLastReqSent.get(recipientPub).then()
|
||||
|
||||
if (typeof data !== 'string') {
|
||||
throw new TypeError("typeof latestReqSentID !== 'string'")
|
||||
return null
|
||||
}
|
||||
|
||||
return data
|
||||
|
|
@ -134,31 +111,33 @@ const successfulHandshakeAlreadyExists = async recipientPub => {
|
|||
return userToIncoming.get(recipientPub).then()
|
||||
})
|
||||
|
||||
return typeof maybeIncomingID === 'string'
|
||||
const maybeOutgoingID = await tryAndWait((_, user) => {
|
||||
const recipientToOutgoing = user.get(Key.RECIPIENT_TO_OUTGOING)
|
||||
|
||||
return recipientToOutgoing.get(recipientPub).then()
|
||||
})
|
||||
|
||||
return (
|
||||
typeof maybeIncomingID === 'string' && typeof maybeOutgoingID === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} recipientPub
|
||||
* @param {UserGUNNode} user
|
||||
* @param {ISEA} SEA
|
||||
* @returns {Promise<string|null>}
|
||||
*/
|
||||
const recipientToOutgoingID = async (recipientPub, user, SEA) => {
|
||||
const mySecret = await SEA.secret(user._.sea.epub, user._.sea)
|
||||
|
||||
if (typeof mySecret !== 'string') {
|
||||
throw new TypeError('could not get mySecret')
|
||||
}
|
||||
|
||||
const maybeEncryptedOutgoingID = await tryAndWait((_, user) =>
|
||||
user
|
||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
||||
.get(recipientPub)
|
||||
.then()
|
||||
)
|
||||
const recipientToOutgoingID = async recipientPub => {
|
||||
const maybeEncryptedOutgoingID = await require('../../Mediator/index')
|
||||
.getUser()
|
||||
.get(Key.RECIPIENT_TO_OUTGOING)
|
||||
.get(recipientPub)
|
||||
.then()
|
||||
|
||||
if (typeof maybeEncryptedOutgoingID === 'string') {
|
||||
const outgoingID = await SEA.decrypt(maybeEncryptedOutgoingID, mySecret)
|
||||
const outgoingID = await require('../../Mediator/index').mySEA.decrypt(
|
||||
maybeEncryptedOutgoingID,
|
||||
await mySecret()
|
||||
)
|
||||
|
||||
return outgoingID || null
|
||||
}
|
||||
|
|
@ -166,52 +145,6 @@ const recipientToOutgoingID = async (recipientPub, user, SEA) => {
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} reqResponse
|
||||
* @param {string} recipientPub
|
||||
* @param {UserGUNNode} user
|
||||
* @param {ISEA} SEA
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const reqWasAccepted = async (reqResponse, recipientPub, user, SEA) => {
|
||||
try {
|
||||
const recipientEpub = await pubToEpub(recipientPub)
|
||||
const ourSecret = await SEA.secret(recipientEpub, user._.sea)
|
||||
if (typeof ourSecret !== 'string') {
|
||||
throw new TypeError('typeof ourSecret !== "string"')
|
||||
}
|
||||
|
||||
const decryptedResponse = await SEA.decrypt(reqResponse, ourSecret)
|
||||
|
||||
if (typeof decryptedResponse !== 'string') {
|
||||
throw new TypeError('typeof decryptedResponse !== "string"')
|
||||
}
|
||||
|
||||
const myFeedID = await recipientToOutgoingID(recipientPub, user, SEA)
|
||||
|
||||
if (typeof myFeedID === 'string' && decryptedResponse === myFeedID) {
|
||||
return false
|
||||
}
|
||||
|
||||
const recipientFeedID = decryptedResponse
|
||||
|
||||
const maybeFeed = await tryAndWait(gun =>
|
||||
gun
|
||||
.user(recipientPub)
|
||||
.get(Key.OUTGOINGS)
|
||||
.get(recipientFeedID)
|
||||
.then()
|
||||
)
|
||||
|
||||
const feedExistsOnRecipient =
|
||||
typeof maybeFeed === 'object' && maybeFeed !== null
|
||||
|
||||
return feedExistsOnRecipient
|
||||
} catch (err) {
|
||||
throw new Error(`reqWasAccepted() -> ${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} userPub
|
||||
|
|
@ -228,6 +161,18 @@ const currHandshakeAddress = async userPub => {
|
|||
return typeof maybeAddr === 'string' ? maybeAddr : 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
|
||||
|
|
@ -266,8 +211,8 @@ const asyncFilter = async (arr, cb) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {import('./SimpleGUN').ListenerData} listenerData
|
||||
* @returns {listenerData is import('./SimpleGUN').ListenerObj}
|
||||
* @param {import('../SimpleGUN').ListenerData} listenerData
|
||||
* @returns {listenerData is import('../SimpleGUN').ListenerObj}
|
||||
*/
|
||||
const dataHasSoul = listenerData =>
|
||||
typeof listenerData === 'object' && listenerData !== null
|
||||
|
|
@ -278,6 +223,22 @@ const dataHasSoul = listenerData =>
|
|||
*/
|
||||
const defaultName = pub => 'anon' + pub.slice(0, 8)
|
||||
|
||||
/**
|
||||
* @param {string} pub
|
||||
* @param {string} incomingID
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const didDisconnect = async (pub, incomingID) => {
|
||||
const feed = await require('../../Mediator/index')
|
||||
.getGun()
|
||||
.user(pub)
|
||||
.get(Key.OUTGOINGS)
|
||||
.get(incomingID)
|
||||
.then()
|
||||
|
||||
return feed === null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
asyncMap,
|
||||
asyncFilter,
|
||||
|
|
@ -285,11 +246,13 @@ module.exports = {
|
|||
defaultName,
|
||||
delay,
|
||||
pubToEpub,
|
||||
reqToRecipientPub,
|
||||
recipientPubToLastReqSentID,
|
||||
successfulHandshakeAlreadyExists,
|
||||
recipientToOutgoingID,
|
||||
reqWasAccepted,
|
||||
currHandshakeAddress,
|
||||
tryAndWait
|
||||
tryAndWait,
|
||||
mySecret,
|
||||
promisifyGunNode: require('./promisifygun'),
|
||||
didDisconnect,
|
||||
asyncForEach
|
||||
}
|
||||
47
services/gunDB/contact-api/utils/promisifygun.js
Normal file
47
services/gunDB/contact-api/utils/promisifygun.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @format
|
||||
* @typedef {import("../SimpleGUN").ValidDataValue} ValidDataValue
|
||||
* @typedef {import('../SimpleGUN').GUNNode} GUNNode
|
||||
* @typedef {import('./PGUNNode').PGUNNode} PGUNNode
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {GUNNode} node
|
||||
* @returns {PGUNNode}
|
||||
*/
|
||||
const promisify = node => {
|
||||
const oldPut = node.put.bind(node)
|
||||
const oldSet = node.set.bind(node)
|
||||
const oldGet = node.get.bind(node)
|
||||
|
||||
const _pnode = /** @type {unknown} */ (node)
|
||||
const pnode = /** @type {PGUNNode} */ (_pnode)
|
||||
|
||||
pnode.put = data =>
|
||||
new Promise((res, rej) => {
|
||||
oldPut(data, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
pnode.set = data =>
|
||||
new Promise((res, rej) => {
|
||||
oldSet(data, ack => {
|
||||
if (ack.err) {
|
||||
rej(new Error(ack.err))
|
||||
} else {
|
||||
res()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
pnode.get = key => promisify(oldGet(key))
|
||||
|
||||
return pnode
|
||||
}
|
||||
|
||||
module.exports = promisify
|
||||
220
yarn.lock
220
yarn.lock
|
|
@ -385,24 +385,24 @@
|
|||
asn1js "^2.0.22"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@peculiar/json-schema@^1.1.5":
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.5.tgz#376e0e978d2bd7132487a5679ab375a34313e73f"
|
||||
integrity sha512-y5XYA3pf9+c+YKVpWnPtQbNmlNCs2ehNHyMLJvq4K5Fjwc1N64YGy7MNecKW3uYLga+sqbGTQSUdOdlnaRRbpA==
|
||||
"@peculiar/json-schema@^1.1.6":
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/json-schema/-/json-schema-1.1.9.tgz#b746e046b787607a1b2804f64437fda2527b3e62"
|
||||
integrity sha512-F2ST2y/IQPgY+1QMw1Q33sqJbGDCeO3lGqI69SL3Hgo0++7iHqprUB1QyxB/A7bN3tuM65MBxoM2JLbwh42lsQ==
|
||||
dependencies:
|
||||
tslib "^1.9.3"
|
||||
tslib "^1.10.0"
|
||||
|
||||
"@peculiar/webcrypto@^1.0.19":
|
||||
version "1.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.0.19.tgz#45dd199c57ee655f9efd0332aca6fd53801d89f7"
|
||||
integrity sha512-+lF69A18LJBLp0/gJIQatCARLah6cTUmLwY0Cdab0zsk+Z53BcpjKQyyP4LIN8oW601ZXv28mWEQ4Cm7MllF6w==
|
||||
"@peculiar/webcrypto@^1.0.22":
|
||||
version "1.0.22"
|
||||
resolved "https://registry.yarnpkg.com/@peculiar/webcrypto/-/webcrypto-1.0.22.tgz#9dae652fce6bacd9df15bc91965797cee33adf67"
|
||||
integrity sha512-NP6H6ZGXUvJnQJCWzUgnRcQv+9nMCNwLUDhTwOxRUwPFvtHauMOl0oPTKUjbhInCMaE55gJqB4yc0YKbde6Exw==
|
||||
dependencies:
|
||||
"@peculiar/asn1-schema" "^1.0.3"
|
||||
"@peculiar/json-schema" "^1.1.5"
|
||||
"@peculiar/json-schema" "^1.1.6"
|
||||
asn1js "^2.0.26"
|
||||
pvtsutils "^1.0.6"
|
||||
pvtsutils "^1.0.9"
|
||||
tslib "^1.10.0"
|
||||
webcrypto-core "^1.0.14"
|
||||
webcrypto-core "^1.0.17"
|
||||
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
|
|
@ -579,6 +579,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
||||
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
|
||||
|
||||
"@types/jsonwebtoken@^8.3.7":
|
||||
version "8.3.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.3.7.tgz#ab79ad55b9435834d24cca3112f42c08eedb1a54"
|
||||
integrity sha512-B5SSifLkjB0ns7VXpOOtOUlynE78/hKcY8G8pOAhkLJZinwofIBYqz555nRj2W9iDWZqFhK5R+7NZDaRmKWAoQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/lodash@^4.14.141":
|
||||
version "4.14.141"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.141.tgz#d81f4d0c562abe28713406b571ffb27692a82ae6"
|
||||
|
|
@ -604,11 +611,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce"
|
||||
integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ==
|
||||
|
||||
"@types/node@^10.14.17":
|
||||
version "10.14.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.19.tgz#f52742c7834a815dedf66edfc8a51547e2a67342"
|
||||
integrity sha512-j6Sqt38ssdMKutXBUuAcmWF8QtHW1Fwz/mz4Y+Wd9mzpBiVFirjpNQf363hG5itkG+yGaD+oiLyb50HxJ36l9Q==
|
||||
|
||||
"@types/normalize-package-data@^2.4.0":
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||
|
|
@ -680,6 +682,22 @@
|
|||
lodash.unescape "4.0.1"
|
||||
semver "5.5.0"
|
||||
|
||||
"@unimodules/core@*":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@unimodules/core/-/core-5.0.0.tgz#e1e3ca3f91f3d27dbc93c6eebc03a40c711da755"
|
||||
integrity sha512-PswccfzFIviX61Lm8h6/QyC94bWe+6cARwhzgzTCKa6aR6azmi4732ExhX4VxfQjJNHB0szYVXGXVEDsFkj+tQ==
|
||||
dependencies:
|
||||
compare-versions "^3.4.0"
|
||||
|
||||
"@unimodules/react-native-adapter@*":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@unimodules/react-native-adapter/-/react-native-adapter-5.0.0.tgz#af9835821a2bf38390b9f09f3231c0b7546ee510"
|
||||
integrity sha512-qb5p5wUQoi3TRa/33aLLHSnS7sewV99oBxIo9gnzNI3VFzbOm3rsbTjOJNcR2hx0raUolTtnQT75VbgagVQx4w==
|
||||
dependencies:
|
||||
invariant "^2.2.4"
|
||||
lodash "^4.5.0"
|
||||
prop-types "^15.6.1"
|
||||
|
||||
abab@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.2.tgz#a2fba1b122c69a85caa02d10f9270c7219709a9d"
|
||||
|
|
@ -873,7 +891,12 @@ ascli@~1:
|
|||
colour "~0.7.1"
|
||||
optjs "~3.2.2"
|
||||
|
||||
asn1@^0.2.4, asn1@~0.2.3:
|
||||
asmcrypto.js@^0.22.0:
|
||||
version "0.22.0"
|
||||
resolved "https://registry.yarnpkg.com/asmcrypto.js/-/asmcrypto.js-0.22.0.tgz#38fc1440884d802c7bd37d1d23c2b26a5cd5d2d2"
|
||||
integrity sha512-usgMoyXjMbx/ZPdzTSXExhMPur2FTdz/Vo5PVx2gIaBcdAAJNOFlsdgqveM8Cff7W0v+xrf9BwjOV26JSAF9qA==
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
|
||||
|
|
@ -907,7 +930,7 @@ async-each@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
|
||||
integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
|
||||
|
||||
async-limiter@^1.0.0, async-limiter@~1.0.0:
|
||||
async-limiter@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||
|
|
@ -945,6 +968,20 @@ axios@0.19.0, axios@^0.19.0:
|
|||
follow-redirects "1.5.10"
|
||||
is-buffer "^2.0.2"
|
||||
|
||||
b64-lite@^1.3.1, b64-lite@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/b64-lite/-/b64-lite-1.4.0.tgz#e62442de11f1f21c60e38b74f111ac0242283d3d"
|
||||
integrity sha512-aHe97M7DXt+dkpa8fHlCcm1CnskAHrJqEfMI0KN7dwqlzml/aUe1AGt6lk51HzrSfVD67xOso84sOpr+0wIe2w==
|
||||
dependencies:
|
||||
base-64 "^0.1.0"
|
||||
|
||||
b64u-lite@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/b64u-lite/-/b64u-lite-1.1.0.tgz#a581b7df94cbd4bed7cbb19feae816654f0b1bf0"
|
||||
integrity sha512-929qWGDVCRph7gQVTC6koHqQIpF4vtVaSbwLltFQo44B1bYUquALswZdBKFfrJCPEnsCOvWkJsPdQYZ/Ukhw8A==
|
||||
dependencies:
|
||||
b64-lite "^1.4.0"
|
||||
|
||||
babel-code-frame@^6.26.0:
|
||||
version "6.26.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
|
||||
|
|
@ -1088,6 +1125,11 @@ balanced-match@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
base-64@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
|
||||
integrity sha1-eAqZyE59YAJgNhURxId2E78k9rs=
|
||||
|
||||
base-x@^3.0.2:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.7.tgz#1c5a7fafe8f66b4114063e8da102799d4e7c408f"
|
||||
|
|
@ -1100,6 +1142,11 @@ base64-arraybuffer@0.1.5:
|
|||
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
|
||||
integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
|
||||
|
||||
base64-js@*, base64-js@^1.0.2, base64-js@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
|
||||
|
||||
base64id@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6"
|
||||
|
|
@ -1275,6 +1322,14 @@ buffer-from@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
buffer@^5.4.3:
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
|
||||
integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
|
||||
bytebuffer@~5:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-5.0.1.tgz#582eea4b1a873b6d020a48d58df85f0bba6cfddd"
|
||||
|
|
@ -1539,6 +1594,11 @@ commander@~2.20.0:
|
|||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.1.tgz#3863ce3ca92d0831dcf2a102f5fb4b5926afd0f9"
|
||||
integrity sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==
|
||||
|
||||
compare-versions@^3.4.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
|
||||
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
|
||||
|
||||
component-bind@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1"
|
||||
|
|
@ -2273,6 +2333,13 @@ expect@^24.9.0:
|
|||
jest-message-util "^24.9.0"
|
||||
jest-regex-util "^24.9.0"
|
||||
|
||||
expo-random@*:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/expo-random/-/expo-random-8.0.0.tgz#bbcc7a189d29ae9b709b36a6cf84256c22cc16f6"
|
||||
integrity sha512-ukDC3eGSEliBsnobX1bQRAwti9GE8ZEW53AHFf1fVy+JuAhhZm+M5HW7T0ptdbLjm46VpTDGIO4vq+qxeXAl7g==
|
||||
dependencies:
|
||||
base64-js "^1.3.0"
|
||||
|
||||
express-session@^1.15.1:
|
||||
version "1.16.2"
|
||||
resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.2.tgz#59f36d7770e94872d19b163b6708a2d16aa6848c"
|
||||
|
|
@ -2711,15 +2778,15 @@ grpc@^1.21.1:
|
|||
node-pre-gyp "^0.13.0"
|
||||
protobufjs "^5.0.3"
|
||||
|
||||
gun@^0.2019.1211:
|
||||
version "0.2019.1211"
|
||||
resolved "https://registry.yarnpkg.com/gun/-/gun-0.2019.1211.tgz#37aa58217f86256b5d6f126450c8860f7bca384e"
|
||||
integrity sha512-wueemUJBtNfVcZDeXK7wYbth+eu7tNFo5T54gAbA3N0VAN3zoPNE12saeekzrkVeMiKVxG8/kiR35BhykQ+CJg==
|
||||
"gun@git://github.com/amark/gun#c59e0e95f92779ce6bb3aab823d318bc16b20c33":
|
||||
version "0.2020.116"
|
||||
resolved "git://github.com/amark/gun#c59e0e95f92779ce6bb3aab823d318bc16b20c33"
|
||||
dependencies:
|
||||
ws "~>7.1.0"
|
||||
buffer "^5.4.3"
|
||||
ws "^7.1.2"
|
||||
optionalDependencies:
|
||||
"@peculiar/webcrypto" "^1.0.19"
|
||||
emailjs "^2.2.0"
|
||||
isomorphic-webcrypto "^2.3.2"
|
||||
text-encoding "^0.7.0"
|
||||
|
||||
handlebars@^4.1.2:
|
||||
|
|
@ -2902,6 +2969,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
|
|||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
||||
|
||||
ignore-by-default@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
|
||||
|
|
@ -3297,6 +3369,24 @@ isobject@^3.0.0, isobject@^3.0.1:
|
|||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||
|
||||
isomorphic-webcrypto@^2.3.2:
|
||||
version "2.3.4"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-webcrypto/-/isomorphic-webcrypto-2.3.4.tgz#6d18ee7f795ab2f5e9fd7a5b489abaf9ee2e6af5"
|
||||
integrity sha512-SnOCsm0Vls8jeWP4c26ItHFajzfDlRPcKK4YRUv6jukYGzJwl2tKNwSIAiCh1INdoRttaKhJrLc3HBer1om4HA==
|
||||
dependencies:
|
||||
"@peculiar/webcrypto" "^1.0.22"
|
||||
asmcrypto.js "^0.22.0"
|
||||
b64-lite "^1.3.1"
|
||||
b64u-lite "^1.0.1"
|
||||
msrcrypto "^1.5.6"
|
||||
str2buf "^1.3.0"
|
||||
webcrypto-shim "^0.1.4"
|
||||
optionalDependencies:
|
||||
"@unimodules/core" "*"
|
||||
"@unimodules/react-native-adapter" "*"
|
||||
expo-random "*"
|
||||
react-native-securerandom "^0.1.1"
|
||||
|
||||
isstream@0.1.x, isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
|
@ -4015,7 +4105,7 @@ lodash@=4.17.4:
|
|||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
||||
integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=
|
||||
|
||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5:
|
||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.5.0:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
|
@ -4030,7 +4120,7 @@ long@~3:
|
|||
resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b"
|
||||
integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=
|
||||
|
||||
loose-envify@^1.0.0:
|
||||
loose-envify@^1.0.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
|
|
@ -4232,6 +4322,11 @@ ms@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
msrcrypto@^1.5.6:
|
||||
version "1.5.8"
|
||||
resolved "https://registry.yarnpkg.com/msrcrypto/-/msrcrypto-1.5.8.tgz#be419be4945bf134d8af52e9d43be7fa261f4a1c"
|
||||
integrity sha512-ujZ0TRuozHKKm6eGbKHfXef7f+esIhEckmThVnz7RNyiOJd7a6MXj2JGBoL9cnPDW+JMG16MoTUh5X+XXjI66Q==
|
||||
|
||||
mute-stream@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
|
||||
|
|
@ -4341,13 +4436,6 @@ node-pre-gyp@^0.13.0:
|
|||
semver "^5.3.0"
|
||||
tar "^4"
|
||||
|
||||
node-rsa@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.0.7.tgz#85b7a6d6fa8ee624be6402a6b41be49272d58055"
|
||||
integrity sha512-idwRXma6scFufZmbaKkHpJoLL93yynRefP6yur13wZ5i9FR35ex451KCoF2OORDeJanyRVahmjjiwmUlCnTqJA==
|
||||
dependencies:
|
||||
asn1 "^0.2.4"
|
||||
|
||||
nodemon@^1.19.3:
|
||||
version "1.19.3"
|
||||
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.3.tgz#db71b3e62aef2a8e1283a9fa00164237356102c0"
|
||||
|
|
@ -4446,7 +4534,7 @@ oauth-sign@~0.9.0:
|
|||
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
||||
object-assign@^4, object-assign@^4.1.0:
|
||||
object-assign@^4, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
|
@ -4887,6 +4975,15 @@ prompts@^2.0.1:
|
|||
kleur "^3.0.3"
|
||||
sisteransi "^1.0.3"
|
||||
|
||||
prop-types@^15.6.1:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
protobufjs@^5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-5.0.3.tgz#e4dfe9fb67c90b2630d15868249bcc4961467a17"
|
||||
|
|
@ -4957,12 +5054,11 @@ punycode@^2.1.0, punycode@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
pvtsutils@^1.0.4, pvtsutils@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.0.6.tgz#e3883fd77abdd4c124131f6a49f3914cd9f21290"
|
||||
integrity sha512-0yNrOdJyLE7FZzmeEHTKanwBr5XbmDAd020cKa4ZiTYuGMBYBZmq7vHOhcOqhVllh6gghDBbaz1lnVdOqiB7cw==
|
||||
pvtsutils@^1.0.9:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.0.9.tgz#0eb6106f27878ccaa55e7dfbf6bd2c75af461dee"
|
||||
integrity sha512-/kDsuCKPqJuIzn37w6+iN+TiSrN+zrwPEd7FjT61oNbRvceGdsS94fMEWZ4/h6QZU5EZhBMiV+79IYedroP/Yw==
|
||||
dependencies:
|
||||
"@types/node" "^10.14.17"
|
||||
tslib "^1.10.0"
|
||||
|
||||
pvutils@latest:
|
||||
|
|
@ -5015,11 +5111,23 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
|
|||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-is@^16.8.1:
|
||||
version "16.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
|
||||
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==
|
||||
|
||||
react-is@^16.8.4:
|
||||
version "16.10.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.10.1.tgz#0612786bf19df406502d935494f0450b40b8294f"
|
||||
integrity sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw==
|
||||
|
||||
react-native-securerandom@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-securerandom/-/react-native-securerandom-0.1.1.tgz#f130623a412c338b0afadedbc204c5cbb8bf2070"
|
||||
integrity sha1-8TBiOkEsM4sK+t7bwgTFy7i/IHA=
|
||||
dependencies:
|
||||
base64-js "*"
|
||||
|
||||
read-pkg-up@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
|
||||
|
|
@ -5697,6 +5805,11 @@ stealthy-require@^1.1.1:
|
|||
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
|
||||
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
|
||||
|
||||
str2buf@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/str2buf/-/str2buf-1.3.0.tgz#a4172afff4310e67235178e738a2dbb573abead0"
|
||||
integrity sha512-xIBmHIUHYZDP4HyoXGHYNVmxlXLXDrtFHYT0eV6IOdEj3VO9ccaF1Ejl9Oq8iFjITllpT8FhaXb4KsNmw+3EuA==
|
||||
|
||||
string-length@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed"
|
||||
|
|
@ -6221,14 +6334,19 @@ walker@^1.0.7, walker@~1.0.5:
|
|||
dependencies:
|
||||
makeerror "1.0.x"
|
||||
|
||||
webcrypto-core@^1.0.14:
|
||||
version "1.0.14"
|
||||
resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.0.14.tgz#c015088fbc9c235ebd8b35047a131c5ff58f7152"
|
||||
integrity sha512-iGZQcH/o3Jv6mpvCbzan6uAcUcLTTnUCil6RVYakcNh5/QXIKRRC06EFxHru9lHgVKucZy3gG4OBiup0IsOr0g==
|
||||
webcrypto-core@^1.0.17:
|
||||
version "1.0.17"
|
||||
resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.0.17.tgz#a9354bc0b1ba6735e882f4137ede2c4366e6ad9b"
|
||||
integrity sha512-7jxTLgtM+TahBPErx/Dd2XvxFDfWJrHxjVeTSvIa4LSgiYrmCPlC2INiAMAfb8MbtHiwJKKqF5sPS0AWNjBbXw==
|
||||
dependencies:
|
||||
pvtsutils "^1.0.4"
|
||||
pvtsutils "^1.0.9"
|
||||
tslib "^1.10.0"
|
||||
|
||||
webcrypto-shim@^0.1.4:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/webcrypto-shim/-/webcrypto-shim-0.1.5.tgz#13e34a010ccc544edecfe8a2642204502841bcf0"
|
||||
integrity sha512-mE+E00gulvbLjHaAwl0kph60oOLQRsKyivEFgV9DMM/3Y05F1vZvGq12hAcNzHRnYxyEOABBT/XMtwGSg5xA7A==
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
|
|
@ -6383,6 +6501,11 @@ ws@^5.2.0:
|
|||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@^7.1.2:
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e"
|
||||
integrity sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==
|
||||
|
||||
ws@~6.1.0:
|
||||
version "6.1.4"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
|
||||
|
|
@ -6390,13 +6513,6 @@ ws@~6.1.0:
|
|||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
|
||||
ws@~>7.1.0:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.1.2.tgz#c672d1629de8bb27a9699eb599be47aeeedd8f73"
|
||||
integrity sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg==
|
||||
dependencies:
|
||||
async-limiter "^1.0.0"
|
||||
|
||||
xdg-basedir@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue