Merge pull request #24 from shocknet/chat-improvements

Chat improvements
This commit is contained in:
Daniel Lugo 2020-02-15 17:14:12 -04:00 committed by GitHub
commit b4073bff1c
23 changed files with 2066 additions and 1640 deletions

View file

@ -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": {

View file

@ -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",

View file

@ -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,8 +796,7 @@ class Mediator {
await throwOnInvalidToken(token)
API.Events.onChats(
chats => {
API.Events.onChats(chats => {
if (Config.SHOW_LOG) {
console.log('---chats---')
console.log(chats)
@ -828,11 +808,7 @@ class Mediator {
ok: true,
origBody: body
})
},
gun,
user,
mySEA
)
})
} catch (err) {
console.log(err)
this.socket.emit(Event.ON_CHATS, {
@ -916,8 +892,7 @@ class Mediator {
await throwOnInvalidToken(token)
API.Events.onSimplerReceivedRequests(
receivedRequests => {
API.Events.onSimplerReceivedRequests(receivedRequests => {
if (Config.SHOW_LOG) {
console.log('---receivedRequests---')
console.log(receivedRequests)
@ -929,11 +904,7 @@ class Mediator {
ok: true,
origBody: body
})
},
gun,
user,
mySEA
)
})
} 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
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
})
},
gun,
user,
mySEA
}, 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
}

View file

@ -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;

View file

@ -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

View file

@ -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()
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

View 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
}

View 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
}

View 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
}

View file

@ -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
}

View file

@ -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
)
await new Promise((res, rej) => {
user
.get(Key.USER_TO_INCOMING)
.get(recipientPub)
.put(encryptedForMeIncomingID)
.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()

View file

@ -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'

View file

@ -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
}

View 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
}

View 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')
}

View 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
}

View 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
}

View 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
}

View 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>
}

View file

@ -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
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
}

View 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
View file

@ -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"