update to hubClient getting favorite hubs

This commit is contained in:
Andre Staltz 2024-01-16 13:58:32 +02:00
parent 14de10b5d3
commit 85737b4898
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
5 changed files with 166 additions and 110 deletions

View File

@ -1,16 +1,16 @@
// @ts-ignore
const MultiserverAddress = require('multiserver-address')
// @ts-ignore
const ip = require('ip')
const p = require('promisify-tuple')
/**
* @typedef {{ pubkey: string }} SHSE
* @typedef {ReturnType<import('ppppp-promise').init>} PPPPPPromise
* @typedef {{connect: (addr: string, cb: CB<any>) => void}} PPPPPNet
* @typedef {ReturnType<import('ppppp-net').init>} PPPPPNet
* @typedef {ReturnType<import('ppppp-hub-client/plugin').init>} PPPPPHubClient
* @typedef {import('ppppp-hub-client/plugin').HubMultiaddr} HubMultiaddr
* @typedef {{
* type: 'join',
* address: string,
* multiaddr: string,
* }} JoinCommand
* @typedef {`join/${string}/${string}/${string}/${string}/${string}/${string}`} JoinCommandStr
* @typedef {{
@ -20,7 +20,7 @@ const p = require('promisify-tuple')
* @typedef {`follow/${string}`} FollowCommandStr
* @typedef {{
* type: 'tunnel-connect',
* address: string,
* multiaddr: string,
* }} TunnelConnectCommand
* @typedef {`tunnel-connect/${string}/${string}`} TunnelConnectCommandStr
* @typedef {{
@ -41,6 +41,12 @@ const p = require('promisify-tuple')
* | PromiseFollowCommand
* | PromiseAccountAddCommand
* } Command
* @typedef {{
* shse: SHSE;
* promise: PPPPPPromise;
* hubClient: PPPPPHubClient;
* net: PPPPPNet;
* }} Peer
*/
/**
@ -96,9 +102,8 @@ function parseJoinCommand(pieces, uri) {
pieces.shift()
pieces.shift()
pieces.shift()
const shse = `shse:${cred.replace('.', ':')}`
const address = `net:${host}:${port}~${shse}` // TODO: add ws address here
return { type: 'join', address }
const multiaddr = `/${hostFormat}/${host}/${transport}/${port}/${transform}/${cred}`
return { type: 'join', multiaddr }
}
/**
@ -137,8 +142,8 @@ function parseTunnelConnectCommand(pieces, uri) {
pieces.shift()
pieces.shift()
pieces.shift()
const address = `tunnel:${hubPubkey}:${targetPubkey}~shse:${targetPubkey}`
return { type: 'tunnel-connect', address }
const multiaddr = `/tunnel/${hubPubkey}.${targetPubkey}/shse/${targetPubkey}`
return { type: 'tunnel-connect', multiaddr }
}
/**
@ -240,11 +245,91 @@ function parse(uri) {
}
/**
* @param {{
* shse: SHSE;
* promise: PPPPPPromise;
* net: PPPPPNet;
* }} peer
* Among the join commands, find the first valid hub, and return its
* - web protocol (http or https)
* - web hostname
* - pubkey for shse
* @param {Array<JoinCommandStr>} joinCommands
* @return {['http' | 'https', string, string]}
*/
function getFirstHub(joinCommands) {
for (const joinCmd of joinCommands) {
const [, hostFormat, host, , , shse, cred] = joinCmd.split('/')
if (shse !== 'shse') continue
const [pubkey, token] = cred.split('.')
if (hostFormat === 'dns') {
return ['https', host, pubkey]
} else {
return ['http', host, pubkey]
}
}
throw new Error('No join commands found')
}
/**
*
* @param {Peer} peer
* @param {number} amountHubs
* @param {HubMultiaddr=} hardcodedHub
* @returns {Promise<[Error] | [null, Array<JoinCommandStr>]>}
*/
async function makeJoinCommands(peer, amountHubs, hardcodedHub) {
/**@type {Array<JoinCommandStr>}*/
const joinCommands = []
// Get multiaddr of hubs
const [err1, hubMultiaddrs] = hardcodedHub
? [null, [hardcodedHub]]
: await p(peer.hubClient.getHubs)(amountHubs)
// prettier-ignore
if (err1) return [new Error('Failed to get hubs while creating invite', { cause: err1 })]
// For each hub, connect and create token
const hubErrors = []
for (const multiaddr of hubMultiaddrs) {
const [err2, rpc] = await p(peer.net.connect)(multiaddr)
if (err2) {
// prettier-ignore
hubErrors.push(new Error('Failed to connect to hub while creating invite', { cause: err2 }))
continue
}
// @ts-ignore
if (!rpc.hub) continue
// @ts-ignore
const [err3, hubToken] = await p(rpc.hub.createToken)()
if (err3) {
// prettier-ignore
hubErrors.push(new Error('Failed to create hub token while creating invite', { cause: err3 }))
continue
}
if (/shse\/([^.]+)$/.test(multiaddr) === false) {
// prettier-ignore
hubErrors.push(new Error(`Invalid hub multiaddr "${multiaddr}" missing shse portion while creating invite`))
continue
}
const joinCommand = `join${multiaddr}.${hubToken}`
// @ts-ignore
joinCommands.push(joinCommand)
}
// If there are no successful join commands, return error
if (joinCommands.length === 0) {
if (hubErrors.length === 0) {
// prettier-ignore
return [new Error('Failed to coordinate with hubs while creating invite, for unknown reasons')]
}
const cause = new AggregateError(hubErrors)
// prettier-ignore
return [new Error('Failed to coordinate with hubs while creating invite', { cause })]
}
return [null, joinCommands]
}
/**
* @param {Peer} peer
* @param {unknown} config
*/
function initInvite(peer, config) {
@ -252,7 +337,7 @@ function initInvite(peer, config) {
* @param {{
* hubs?: number,
* id: string,
* _hubMsAddr?: string,
* _hubMultiaddr?: HubMultiaddr
* }} opts
*
* @param {CB<{uri: string, url: string}>} cb
@ -265,59 +350,37 @@ function initInvite(peer, config) {
// prettier-ignore
return cb(new Error(`invite.createForFriend opts.id is required for type "follow"`))
}
const hubs = opts.hubs ?? 1
if (typeof hubs !== 'number') {
const amountHubs = opts.hubs ?? 1
if (typeof amountHubs !== 'number') {
// prettier-ignore
return cb(new Error(`invite.createForFriend opts.hubs should be a number but was ${hubs}`))
return cb(new Error(`invite.createForFriend opts.hubs should be a number but was ${amountHubs}`))
}
if (!opts._hubMsAddr) {
// prettier-ignore
// FIXME: load hubs from ppppp-net
return cb(new Error(`invite.createForFriend expected opts._hubMsAddr because loading from connDB not yet supported`))
}
// Connect to hub and create token
const [err, rpc] = await p(peer.net.connect)(opts._hubMsAddr)
if (err) return cb(err)
const [err2, hubToken] = await p(rpc.hub.createToken)()
if (err2) return cb(err2)
// Parse multiserver address
// prettier-ignore
const ERROR_MSG = `Invalid multiserver address ${opts._hubMsAddr} for invite.createForFriend`
const msAddr = MultiserverAddress.decode(opts._hubMsAddr)
const [netShsAddr, wsShsAddr] = msAddr
if (!netShsAddr) return cb(new Error(ERROR_MSG))
const [net, shse] = netShsAddr
if (!net) return cb(new Error(ERROR_MSG))
if (net.name !== 'net') return cb(new Error(ERROR_MSG))
const [host, port] = net.data
const hostFormat = ip.isV4Format(host)
? 'ip4'
: ip.isV6Format('ipv6')
? 'ip6'
: 'dns'
if (!shse) return cb(new Error(ERROR_MSG))
if (shse.name !== 'shse') return cb(new Error(ERROR_MSG))
const [pubkey] = shse.data
// Create "join hub" commands
const [err1, joinCommands] = await makeJoinCommands(
peer,
amountHubs,
opts._hubMultiaddr
)
if (err1) return cb(err1)
const [protocol, hostname] = getFirstHub(joinCommands)
// Create follow promise
const [err3, token] = await p(peer.promise.create)({
const [err2, token] = await p(peer.promise.create)({
account: opts.id,
type: 'follow',
})
if (err3) return cb(err3)
/** @type {JoinCommandStr} */
const joinCommand = `join/${hostFormat}/${host}/tcp/${port}/shse/${pubkey}.${hubToken}`
/** @type {FollowCommandStr} */
const followCommand = `follow/${opts.id}`
if (err2) return cb(err2)
/** @type {PromiseFollowCommandStr} */
const promiseCommand = `promise.follow/account.${opts.id}/${token}`
const uri = `ppppp://invite/${joinCommand}/${followCommand}/${promiseCommand}`
const url = `http://${host}/invite#${encodeURIComponent(uri)}`
// Create follow command
/** @type {FollowCommandStr} */
const followCommand = `follow/${opts.id}`
// prettier-ignore
const uri = `ppppp://invite/${joinCommands.join('/')}/${followCommand}/${promiseCommand}`
const url = `${protocol}://${hostname}/invite#${encodeURIComponent(uri)}`
cb(null, { uri, url })
}
@ -325,7 +388,7 @@ function initInvite(peer, config) {
* @param {{
* hubs?: number,
* id: string,
* _hubMsAddr?: string,
* _hubMultiaddr?: HubMultiaddr
* }} opts
*
* @param {CB<{uri: string, url: string}>} cb
@ -338,55 +401,35 @@ function initInvite(peer, config) {
// prettier-ignore
return cb(new Error(`invite.createForMyself opts.id is required for type "follow"`))
}
const hubs = opts.hubs ?? 1
if (typeof hubs !== 'number') {
const amountHubs = opts.hubs ?? 1
if (typeof amountHubs !== 'number') {
// prettier-ignore
return cb(new Error(`invite.createForMyself opts.hubs should be a number but was ${hubs}`))
return cb(new Error(`invite.createForMyself opts.hubs should be a number but was ${amountHubs}`))
}
if (!opts._hubMsAddr) {
// prettier-ignore
return cb(new Error(`invite.createForMyself expected opts._hubMsAddr because loading from connDB not yet supported`))
}
// Connect to hub and create token
const [err, rpc] = await p(peer.net.connect)(opts._hubMsAddr)
if (err) return cb(err)
const [err2, hubToken] = await p(rpc.hub.createToken)()
if (err2) return cb(err2)
// Parse multiserver address
// prettier-ignore
const ERROR_MSG = `Invalid multiserver address ${opts._hubMsAddr} for invite.createForMyself`
const msAddr = MultiserverAddress.decode(opts._hubMsAddr)
const [netShsAddr, wsShsAddr] = msAddr
if (!netShsAddr) return cb(new Error(ERROR_MSG))
const [net, shse] = netShsAddr
if (!net) return cb(new Error(ERROR_MSG))
if (net.name !== 'net') return cb(new Error(ERROR_MSG))
const [host, port] = net.data
const hostFormat = ip.isV4Format(host)
? 'ip4'
: ip.isV6Format('ipv6')
? 'ip6'
: 'dns'
if (!shse) return cb(new Error(ERROR_MSG))
if (shse.name !== 'shse') return cb(new Error(ERROR_MSG))
const [pubkey] = shse.data
// Create "join hub" commands
const [err1, joinCommands] = await makeJoinCommands(
peer,
amountHubs,
opts._hubMultiaddr
)
if (err1) return cb(err1)
const [protocol, hostname, pubkey] = getFirstHub(joinCommands)
// Create account-add promise
const promise = { type: 'account-add', account: opts.id }
const [err3, token] = await p(peer.promise.create)(promise)
if (err3) return cb(err3)
/** @type {JoinCommandStr} */
const joinCommand = `join/${hostFormat}/${host}/tcp/${port}/shse/${pubkey}.${hubToken}`
/** @type {TunnelConnectCommandStr} */
const tunnelCommand = `tunnel-connect/${pubkey}/${peer.shse.pubkey}`
/** @type {PromiseAccountAddCommandStr} */
const promiseCommand = `promise.account-add/account.${opts.id}/${token}`
const uri = `ppppp://invite/${joinCommand}/${tunnelCommand}/${promiseCommand}`
const url = `http://${host}/invite#${encodeURIComponent(uri)}`
// Create tunnel-connect command
/** @type {TunnelConnectCommandStr} */
const tunnelCommand = `tunnel-connect/${pubkey}/${peer.shse.pubkey}`
// prettier-ignore
const uri = `ppppp://invite/${joinCommands.join('/')}/${tunnelCommand}/${promiseCommand}`
const url = `${protocol}://${hostname}/invite#${encodeURIComponent(uri)}`
cb(null, { uri, url })
}
@ -394,7 +437,7 @@ function initInvite(peer, config) {
}
exports.name = 'invite'
exports.needs = ['shse', 'promise', 'net']
exports.needs = ['shse', 'promise', 'net', 'hubClient']
exports.manifest = {
createForFriend: 'async',
createForMyself: 'async',

View File

@ -24,7 +24,6 @@
},
"dependencies": {
"ip": "~1.1.8",
"multiserver-address": "~1.0.1",
"promisify-tuple": "1.2.0"
},
"devDependencies": {
@ -32,7 +31,9 @@
"c8": "^7.11.0",
"husky": "^4.3.0",
"ppppp-caps": "github:staltz/ppppp-caps",
"ppppp-hub-client": "github:staltz/ppppp-hub-client",
"ppppp-keypair": "github:staltz/ppppp-keypair",
"ppppp-net": "github:staltz/ppppp-net",
"ppppp-promise": "github:staltz/ppppp-promise",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3",

View File

@ -25,7 +25,7 @@ test('createForFriend()', async (t) => {
return {
connect(address, cb) {
connectCalled = true
assert.equal(address, 'net:example.com:8008~shse:HUB_PUBKEY')
assert.equal(address, '/dns/example.com/tcp/8008/shse/HUB_PUBKEY')
const mockRpc = {
hub: {
createToken(cb) {
@ -56,11 +56,17 @@ test('createForFriend()', async (t) => {
},
}
const mockHubClient = {
name: 'hubClient',
init() {}
}
const local = require('secret-stack/bare')()
.use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack'))
.use(mockNet)
.use(mockPromise)
.use(mockHubClient)
.use(require('../lib'))
.call(null, {
shse: {
@ -78,7 +84,7 @@ test('createForFriend()', async (t) => {
})
const { uri, url } = await p(local.invite.createForFriend)({
_hubMsAddr: 'net:example.com:8008~shse:HUB_PUBKEY',
_hubMultiaddr: '/dns/example.com/tcp/8008/shse/HUB_PUBKEY',
id: 'MOCK_ID',
})
assert.equal(
@ -87,7 +93,7 @@ test('createForFriend()', async (t) => {
)
assert.equal(
url,
`http://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fdns%2Fexample.com%2Ftcp%2F8008%2Fshse%2FHUB_PUBKEY.MOCK_TOKEN%2Ffollow%2FMOCK_ID%2Fpromise.follow%2Faccount.MOCK_ID%2FMOCK_PROMISE`
`https://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fdns%2Fexample.com%2Ftcp%2F8008%2Fshse%2FHUB_PUBKEY.MOCK_TOKEN%2Ffollow%2FMOCK_ID%2Fpromise.follow%2Faccount.MOCK_ID%2FMOCK_PROMISE`
)
assert.ok(connectCalled)

View File

@ -25,7 +25,7 @@ test('createForMyself()', async (t) => {
return {
connect(address, cb) {
connectCalled = true
assert.equal(address, 'net:example.com:8008~shse:HUB_PUBKEY')
assert.equal(address, '/dns/example.com/tcp/8008/shse/HUB_PUBKEY')
const mockRpc = {
hub: {
createToken(cb) {
@ -56,11 +56,17 @@ test('createForMyself()', async (t) => {
},
}
const mockHubClient = {
name: 'hubClient',
init() {},
}
const local = require('secret-stack/bare')()
.use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack'))
.use(mockNet)
.use(mockPromise)
.use(mockHubClient)
.use(require('../lib'))
.call(null, {
shse: { caps },
@ -76,7 +82,7 @@ test('createForMyself()', async (t) => {
})
const { uri, url } = await p(local.invite.createForMyself)({
_hubMsAddr: 'net:example.com:8008~shse:HUB_PUBKEY',
_hubMultiaddr: '/dns/example.com/tcp/8008/shse/HUB_PUBKEY',
id: 'MOCK_ID',
})
assert.equal(
@ -85,7 +91,7 @@ test('createForMyself()', async (t) => {
)
assert.equal(
url,
`http://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fdns%2Fexample.com%2Ftcp%2F8008%2Fshse%2FHUB_PUBKEY.MOCK_TOKEN%2Ftunnel-connect%2FHUB_PUBKEY%2F${local.shse.pubkey}%2Fpromise.account-add%2Faccount.MOCK_ID%2FMOCK_PROMISE`
`https://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fdns%2Fexample.com%2Ftcp%2F8008%2Fshse%2FHUB_PUBKEY.MOCK_TOKEN%2Ftunnel-connect%2FHUB_PUBKEY%2F${local.shse.pubkey}%2Fpromise.account-add%2Faccount.MOCK_ID%2FMOCK_PROMISE`
)
assert.ok(connectCalled)

View File

@ -21,7 +21,7 @@ test('parse() good friend invite', (t) => {
assert.deepEqual(commands, [
{
type: 'join',
address: 'net:example.com:8080~shse:PUBKEY:TOKEN',
multiaddr: '/dns/example.com/tcp/8080/shse/PUBKEY.TOKEN',
},
{
type: 'follow',
@ -42,11 +42,11 @@ test('parse() good myself invite', (t) => {
assert.deepEqual(commands, [
{
type: 'join',
address: 'net:example.com:8080~shse:PUBKEY:TOKEN',
multiaddr: '/dns/example.com/tcp/8080/shse/PUBKEY.TOKEN',
},
{
type: 'tunnel-connect',
address: 'tunnel:HUB_PUBKEY:OLD_PUBKEY~shse:OLD_PUBKEY',
multiaddr: '/tunnel/HUB_PUBKEY.OLD_PUBKEY/shse/OLD_PUBKEY',
},
{
type: 'promise.account-add',
@ -63,7 +63,7 @@ test('parse() good tokenless join invite', (t) => {
assert.deepEqual(commands, [
{
type: 'join',
address: 'net:example.com:8080~shse:PUBKEY',
multiaddr: '/dns/example.com/tcp/8080/shse/PUBKEY',
},
])
})