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