You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
215 lines
8.1 KiB
215 lines
8.1 KiB
2 years ago
|
// Each AO API server can connect peer-to-peer over Tor. Tor addresses are unique and data is end-to-end encrypted.
|
||
|
import inquirer from 'inquirer'
|
||
|
import { headerStyle } from './styles.js'
|
||
|
import { aoEnv, setAoEnv } from './settings.js'
|
||
|
import { isLoggedIn } from './session.js'
|
||
|
import { isInstalled } from './features/tor.js'
|
||
|
import { connectToAo, getAoBootstrapList, bootstrap } from './api.js'
|
||
|
import { roger } from './welcome.js'
|
||
|
|
||
|
// Prints a menu to connect your AO to other AOs and manage connections
|
||
|
export async function connectMenu() {
|
||
|
console.log(`\n${headerStyle('AO P2P')}`)
|
||
|
const PUBLIC_BOOTSTRAP_ENABLED = aoEnv('PUBLIC_BOOTSTRAP_ENABLED')
|
||
|
let publicBootstrapMenuItem = { name: 'Enable p2p bootstrap', value: 'enable_bootstrap' }
|
||
|
if(PUBLIC_BOOTSTRAP_ENABLED) {
|
||
|
publicBootstrapMenuItem = { name: 'Disable p2p bootstrap', value: 'disable_bootstrap' }
|
||
|
}
|
||
|
const connectChoices = [
|
||
|
{ name: 'Connect to AO', value: 'connect', short: 'p2p connect'},
|
||
|
{ name: 'View connections', value: 'connections' },
|
||
|
{ name: 'Bootstrap now', value: 'bootstrap' },
|
||
|
publicBootstrapMenuItem,
|
||
|
{ name: 'Back to AO Menu', value: false }
|
||
|
]
|
||
|
let answer
|
||
|
try {
|
||
|
answer = await inquirer.prompt({
|
||
|
name: 'connect_menu',
|
||
|
type: 'list',
|
||
|
message: 'Please choose:',
|
||
|
choices: connectChoices,
|
||
|
pageSize: connectChoices.length,
|
||
|
})
|
||
|
} catch(error) {
|
||
|
if (error === 'EVENT_INTERRUPTED') {
|
||
|
console.log('\nESC')
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
switch(answer.connect_menu) {
|
||
|
case 'connect':
|
||
|
await connectInteractive()
|
||
|
break
|
||
|
case 'connections':
|
||
|
const onionList = await getAoBootstrapList()
|
||
|
console.log('The AO server has connections to:')
|
||
|
console.log(onionList.join('\n'))
|
||
|
break
|
||
|
case 'bootstrap':
|
||
|
const bootstrappedOnionList = await bootstrap()
|
||
|
if(!bootstrappedOnionList || bootstrappedOnionList.length < 1) {
|
||
|
console.log('Failed to fetch AO bootstrap server list.')
|
||
|
} else {
|
||
|
console.log('All known .onion addresses in neighborhood:')
|
||
|
console.log(bootstrappedOnionList.join('\n'))
|
||
|
}
|
||
|
break
|
||
|
case 'enable_bootstrap':
|
||
|
console.log('In order to join AO public chatrooms, AO uses the hyperswarm protocol. Joining hyperswarm may expose your IP address to other users. (For high-security installations, don\'t use public bootstrap: you can still add tor addresses to your address book manually and join those chatrooms by name.)')
|
||
|
setAoEnv('PUBLIC_BOOTSTRAP_ENABLED', true)
|
||
|
//message: Type \'public\' and press Enter to enable:')
|
||
|
break
|
||
|
case 'disable_bootstrap':
|
||
|
setAoEnv('PUBLIC_BOOTSTRAP_ENABLED', false)
|
||
|
console.log(roger(), 'Disabled public bootstrapping.')
|
||
|
break
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Tells the AO server you are connected to connect to the AO server at the given .onion with the given connection secret string
|
||
|
// Any client logged in to the AO can tell it to connect to another AO. This could be a security issue if there is a bad actor server.
|
||
|
async function connectInteractive() {
|
||
|
const loggedIn = isLoggedIn()
|
||
|
console.log('The AO server you are logged in to can connect peer-to-peer via tor to another AO server to join chatrooms, send cards, and sync file attachments. Tor is Tor Onion Routing, a secure, end-to-end encrypted way of routing anonymized internet traffic.')
|
||
|
if(!isInstalled()) {
|
||
|
console.log('It looks like your tor server isn\'t instaled and running. Go to Configure->Tor to set it up.')
|
||
|
return false
|
||
|
}
|
||
|
const memberId = aoEnv('AO_CLI_SESSION_MEMBERID')
|
||
|
if(!memberId) {
|
||
|
console.log('Not logged in.')
|
||
|
return false
|
||
|
}
|
||
|
console.log('To connect to another AO, you need it\'s Tor address, which ends in .onion, and its server secret. This information can be found on that AO\'s website. For convenience, it is combined in a single connection string separate by a colon. Please enter the entire string.')
|
||
|
const validateOnion = (onion) => {
|
||
|
const parts = onion.split('.')
|
||
|
if(parts.length != 2 || parts[0].length != 56 || parts[1] !== 'onion') {
|
||
|
console.log('\nInvalid onion address, an onion address is 56 chararcters followed by \'.onion\'. Press ESC to go back.')
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
const validateConnectionString = (connectionString) => {
|
||
|
const parts = connectionString.split(':')
|
||
|
if(parts.length != 2) {
|
||
|
console.log('Your connection string has too many or two few parts. It should have two part separated by a colon. Press ESC to go back.')
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if(!validateOnion(parts[0])) {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
if(parts[1].length != 64) {
|
||
|
console.log('The connection secret (second half of connection string) must be exactly 64 characters. Press ESC to go back.')
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
let answer
|
||
|
try {
|
||
|
answer = await inquirer.prompt({
|
||
|
name: 'connection_string',
|
||
|
type: 'input',
|
||
|
message: 'Enter connection string of other AO:',
|
||
|
validate: validateConnectionString
|
||
|
})
|
||
|
} catch(error) {
|
||
|
if (error === 'EVENT_INTERRUPTED') {
|
||
|
console.log('ESC')
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
const [onion, secret] = answer.connection_string.split(':')
|
||
|
console.log('onion is', onion, 'and secret is', secret)
|
||
|
console.log('Attempting connect...')
|
||
|
const result = await connectToAo(onion, secret)
|
||
|
console.log('result is', result.body)
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// Fetches a list of your AO server's p2p connections and displays it
|
||
|
async function connectionsMenu() {
|
||
|
console.log(`\n${headerStyle('AO P2P Connections')}`)
|
||
|
let connectionsChoices = []
|
||
|
|
||
|
const memberId = aoEnv('AO_CLI_SESSION_MEMBERID')
|
||
|
if(!memberId) {
|
||
|
console.log('Not logged in.')
|
||
|
return false
|
||
|
}
|
||
|
const fetchedCards = await getCard(taskId, 'priorities')
|
||
|
if(!fetchedCards || fetchedCards.length < 1) {
|
||
|
console.log('Failed to fetch member card, this is bad.')
|
||
|
return false
|
||
|
}
|
||
|
const card = fetchedCards[0]
|
||
|
const priorityCards = fetchedCards.slice(1) // First card is member card itself
|
||
|
let priorities = card.priorities.slice()
|
||
|
priorities.reverse()
|
||
|
console.log('You have', priorityCards.length, 'priorities:')
|
||
|
prioritiesChoices = priorities.map((priorityTaskId, i) => {
|
||
|
const priorityCard = priorityCards.find(p => p.taskId === priorityTaskId)
|
||
|
if(!priorityCard) {
|
||
|
return 'Missing card, repair your database'
|
||
|
}
|
||
|
return {
|
||
|
name: priorityCard.name,
|
||
|
value: { index: i, card: priorityCard },
|
||
|
short: priorityCard.name.substring(0, 70) + priorityCard.name.length >= 70 ? '...' : ''
|
||
|
}
|
||
|
})
|
||
|
prioritiesChoices.push(
|
||
|
{ name: 'Create priority', value: 'create_here', short: 'new priority' },
|
||
|
{ name: 'Back to Deck', value: false, short: 'back' }
|
||
|
)
|
||
|
let answer
|
||
|
try {
|
||
|
answer = await inquirer.prompt({
|
||
|
name: 'priorities_menu',
|
||
|
type: 'rawlist',
|
||
|
message: 'Please choose:',
|
||
|
choices: prioritiesChoices,
|
||
|
loop: false
|
||
|
})
|
||
|
} catch(error) {
|
||
|
if (error === 'EVENT_INTERRUPTED') {
|
||
|
console.log('\nESC')
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
switch(answer.priorities_menu) {
|
||
|
case false:
|
||
|
return false
|
||
|
case 'create_here':
|
||
|
let previousCardCreatedText
|
||
|
do {
|
||
|
console.log('previousCardCreatedText is', previousCardCreatedText)
|
||
|
previousCardCreatedText = await createCardInteractive()
|
||
|
} while(previousCardCreatedText != '\n')
|
||
|
return true
|
||
|
case 'Missing card, repair your database':
|
||
|
console.log('Database repair yet implemented, sorry.')
|
||
|
return true
|
||
|
}
|
||
|
let chosenTask = answer.priorities_menu.card
|
||
|
const chosenTaskId = chosenTask.taskId
|
||
|
let previousAnswer
|
||
|
do {
|
||
|
previousAnswer = await priorityCardMenu(chosenTask, answer.priorities_menu.index)
|
||
|
if(previousAnswer) {
|
||
|
const fetchedCards = await getCard(chosenTaskId, false)
|
||
|
if(!fetchedCards || fetchedCards.length < 1) {
|
||
|
console.log('The card has disappeared. Maybe it was deleted, or cards held by no one are automatically cleaned up every five minutes.')
|
||
|
return false
|
||
|
}
|
||
|
chosenTask = fetchedCards[0]
|
||
|
}
|
||
|
} while(previousAnswer !== false)
|
||
|
console.log('Card menu not yet implemented.')
|
||
|
return true
|
||
|
}
|