An interactive command-line interface (CLI) tool to help you install, use, and administer an AO instance.
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.

1317 lines
27 KiB

2 years ago
import request from 'superagent'
import { v1 as uuidV1 } from 'uuid'
import { io } from 'socket.io-client'
import { createHash, hmacHex } from './crypto.js'
//import { runInAction, reaction } from 'mobx'
let currentMemberId
let currentSessionId
let currentSessionToken
const HOST = '127.0.0.1'
const PORT = 8003
const HOSTNAME = HOST + ':' + PORT
export function setCurrentMemberId(newMemberId) {
currentMemberId = newMemberId
}
// Performs a post request to the specified endpoint, sending the given payload
function postRequest(endpoint, payload) {
if(!currentSessionToken) {
console.log("Session token not set, API not ready.")
return new Promise(() => null)
}
let partialRequest = request
.post(HOSTNAME + endpoint)
.set('authorization', currentSessionToken)
.set('session', currentSessionId)
if(payload) {
return partialRequest.send(payload)
} else {
return partialRequest
}
}
// Performs a post request to the /event endpoint, sending the given JSON object as the event
function postEvent(event) {
return postRequest('/event', event)
}
// Helper functions copied from crypto.js and utils.js
const isObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]'
// The AO API Object (split into separate functions if possible)
class AoApi {
constructor(socket) {}
async createSession(user, pass) {
const session = uuidV1()
let sessionKey = createHash(session + createHash(pass))
const token = hmacHex(session, sessionKey)
return request
.post(HOSTNAME + '/session')
.set('authorization', token)
.set('session', session)
.set('name', user)
.on('error', () => false)
.then(res => {
if(typeof window !== 'undefined') {
// clear any existing stale data from localstorage
window.localStorage.removeItem('user')
window.localStorage.removeItem('token')
window.localStorage.removeItem('session')
// set new session info
window.localStorage.setItem('user', user)
window.localStorage.setItem('token', token)
window.localStorage.setItem('session', session)
} else {
currentSessionToken = token
currentSessionId = session // Not used in this api.js yet
}
return true
})
}
/*async fetchState() {
const session = window.localStorage.getItem('session')
const token = window.localStorage.getItem('token')
const user = window.localStorage.getItem('user')
if (session && token && user) {
return request
.post('/state')
.set('Authorization', token)
.then(res => {
aoStore.state.user = user
// console.log(
// 'AO: client/api.ts: fetchState: initial state: ',
// res.body
// )
let dataPackageToSendToClient = res.body
// Get the memberId
let memberId
dataPackageToSendToClient.stateToSend.sessions.forEach(sessionItem => {
if (session === sessionItem.session) {
memberId = sessionItem.ownerId
}
})
// See if we got our member card
let foundMemberCard
if(!memberId) {
console.log("memberId missing when loading state")
} else {
dataPackageToSendToClient.stateToSend.tasks.forEach(task => {
if(task.taskId === memberId) {
foundMemberCard = task
}
})
}
if(foundMemberCard) {
console.log("State includes member card:", foundMemberCard)
} else {
console.log("State does not include member card")
}
aoStore.initializeState(dataPackageToSendToClient.stateToSend)
let metaData = dataPackageToSendToClient.metaData
aoStore.memberDeckSize = metaData.memberDeckSize
aoStore.bookmarksTaskId = metaData.bookmarksTaskId
return true
})
.catch(() => false)
}
return Promise.resolve(false)
}*/
async nameAo(newName) {
const act = {
type: 'ao-named',
alias: newName,
}
return postEvent(act).then(res => {
return res
})
}
async connectToAo(
address,
secret
) {
const act = {
type: 'ao-outbound-connected',
address: address,
secret: secret,
}
return postEvent(act).then(res => {
return res
})
}
async deleteAoConnection(
address,
) {
const act = {
type: 'ao-disconnected',
address: address,
}
return postEvent(act).then(res => {
return res
})
}
async linkCardOnAo(
taskId,
address
) {
const act = {
type: 'ao-linked',
address: address,
taskIdId,
}
return postEvent(act).then(res => {
return res
})
}
async relayEventToOtherAo(
address,
event
) {
const act = {
type: 'ao-relay',
address: address,
ev: event,
}
return postEvent(act).then(res => {
return res
})
}
async setQuorum(quorum) {
const act = {
type: 'quorum-set',
quorum: quorum,
}
// console.log('act is ', act)
return postEvent(act).then(res => {
return res
})
}
async bark() {
const act = {
type: 'doge-barked',
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async hopped(taskId) {
const act = {
type: 'doge-hopped',
memberId: currentMemberId,
taskIdId,
}
return postEvent(act).then(res => {
return res
})
}
async mute() {
const act = {
type: 'member-field-updated',
memberId: currentMemberId,
field: 'muted',
newfield: true,
}
return postEvent(act).then(res => {
return res
})
}
async unmute() {
const act = {
type: 'member-field-updated',
memberId: currentMemberId,
field: 'muted',
newfield: false,
}
return postEvent(act).then(res => {
return res
})
}
/*
async createCard(
name,
anonymous
) {
const act = {
type: 'task-created',
name: name,
color: 'blue',
deck: anonymous
? []
: aoStore.member && currentMemberId
? [currentMemberId]
: [],
inId: anonymous ? null : aoStore.memberCard.taskId || null,
prioritized: false,
}
// console.log('AO: client/api.ts: createCard: ', {
// act,
// 'aoStore.memberCard': aoStore.memberCard,
// })
return postEvent(act).then(res => {
return res
})
}
*/
/*
async createCardIfDoesNotExist(
name,
color,
anonymous
) {
return new Promise((resolve, reject) => {
aoStore.getTaskByName_async(name, (task) => {
if (isObject(task)) {
console.log("task exists!")
resolve(task)
} else {
const act = {
type: 'task-created',
name: name,
color: color || 'blue',
deck: [currentMemberId],
inId: null,
prioritized: false,
}
postEvent(act).then((res) => {
aoStore.getTaskByName_async(name, (task) => {
resolve(task)
})
})
}
})
})
}
*/
async playCard(
from = null,
to
) {
const act = {
type: 'task-played',
from: from,
to: to,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
/*
async createAndPlayCard(name, color, anonymous, to) {
return new Promise((resolve, reject) => {
this.createCardIfDoesNotExist(name, color, anonymous).then(success => {
aoStore.getTaskByName_async(name, found => {
to.taskId = found.taskId
resolve(this.playCard(null, to))
})
})
})
}
*/
/*async findOrCreateCardInCard(
name,
inId,
prioritizedpostEvent(act) = false,
color = 'blue',
anonymous
) {
return new Promise((resolve, reject) => {
aoStore.getTaskByName_async(name, found => {
console.log('gotTaskByName name was ', name, 'and found is ', found)
let act
if (found) {
if (prioritized) {
resolve(this.prioritizeCard(found.taskId, inId))
return
} else {
act = {
type: 'task-sub-tasked',
taskId: inId,
subTask: found.taskId,
memberId: anonymous ? null : currentMemberId,
}
}
} else {
act = {
type: 'task-created',
name: name,
color: color,
deck: anonymous ? [] : [currentMemberId],
inId: inId,
prioritized: prioritized,
}
}
resolve(
postEvent(act).then(res => {
return res
})
)
})
})
}*/
async discardCardFromCard(
taskId,
inId
) {
const act = {
type: 'task-de-sub-tasked',
taskId: inId,
subTaskId,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
// Empties a card's priorities and subtasks
async emptyCard(taskId) {
const act = {
type: 'task-emptied',
taskIdId,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async colorCard(taskId, color) {
const act = {
type: 'task-colored',
taskIdId,
color: color,
inId: null, // add this when we have context, mutation works on server
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async grabCard(taskId) {
const act = {
type: 'task-grabbed',
taskIdId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async grabPile(taskId) {
const act = {
type: 'pile-grabbed',
taskIdId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async dropCard(taskId) {
const act = {
type: 'task-dropped',
taskIdId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async removeCards(taskIds) {
const act = {
type: 'tasks-removed',
taskIdsIds,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async dropPile(taskId) {
const act = {
type: 'pile-dropped',
taskIdId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async passCard(
taskId,
toMemberId
) {
const act = {
type: 'task-passed',
taskIdId,
toMemberId: toMemberId,
fromMemberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async remindMember(
memberId
) {
const act = {
type: 'member-reminded',
toMemberId: memberId,
fromMemberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async swapCard(
inId,
taskId1,
taskId2
) {
const act = {
type: 'task-swapped',
taskId: inId,
swapId1Id1,
swapId2Id2,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async bumpCard(
taskId,
inId,
direction
) {
const act = {
type: 'task-bumped',
taskId: inId,
bumpIdId,
direction: direction,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
/*async prioritizeCard(
taskId,
inId,
position = 0
) {
const act = {
type: 'task-prioritized',
taskIdId,
inId: inId,
position: position,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}*/
async prioritizePile(inId) {
const act = {
type: 'task-prioritized',
inId: inId,
}
return postEvent(act).then(res => {
return res
})
}
async refocusCard(taskId, inId) {
const act = {
type: 'task-refocused',
taskIdId,
inId: inId,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async refocusPile(inId) {
const act = {
type: 'pile-refocused',
inId: inId,
}
return postEvent(act).then(res => {
return res
})
}
async allocatePriority(
inId,
taskId,
points = 1
) {
const act = {
type: 'task-allocated',
taskId: inId,
allocatedIdId,
amount: points,
blame: currentMemberId,
//inId: inId,
}
return postEvent(act).then(res => {
return res
})
}
async titleMissionCard(
taskId,
newTitle
) {
const act = {
type: 'task-guilded',
taskIdId,
guild: newTitle,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async setCardProperty(
taskId,
property,
value
) {
const act = {
type: 'task-property-set',
taskIdId,
property: property,
value: value,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async completeCard(taskId) {
const act = {
type: 'task-claimed',
taskIdId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async uncheckCard(taskId) {
const act = {
type: 'task-unclaimed',
taskIdId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async setClaimInterval(
taskId,
newClaimInterval
) {
const act = {
type: 'task-property-set',
taskIdId,
property: 'claimInterval',
value: newClaimInterval,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async createResource(
resourceId,
name,
charged,
secret,
trackStock
) {
const act = {
type: 'resource-created',
resourceId: resourceId,
name: name,
charged: charged,
secret: secret,
trackStock: trackStock,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async useResource(
resourceId,
amount,
charged,
notes = ''
) {
const act = {
type: 'resource-used',
resourceId: resourceId,
memberId: currentMemberId,
amount: amount,
charged: charged,
notes: notes,
}
return postEvent(act).then(res => {
return res
})
}
async stockResource(
resourceId,
amount,
paid,
notes = ''
) {
const act = {
type: 'resource-stocked',
resourceId: resourceId,
memberId: currentMemberId,
amount: amount,
paid: paid,
notes: notes,
}
return postEvent(act).then(res => {
return res
})
}
async purgeResource(resourceId) {
const act = {
type: 'resource-purged',
resourceId: resourceId,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async bookResource(
taskId,
startTime,
endTime
) {
const act = {
type: 'resource-booked',
resourceIdId,
memberId: currentMemberId,
startTs: startTime,
endTs: endTime,
}
return postEvent(act).then(res => {
return res
})
}
async createMember(
name,
fob = ''
) {
const secret = createHash(name)
const act = {
type: 'member-created',
name,
secret,
fob,
}
return postEvent(act).then(res => {
return res
})
}
async activateMember(memberId) {
const act = {
type: 'member-activated',
memberId: memberId,
}
return postEvent(act).then(res => {
return res
})
}
async deactivateMember(memberId) {
const act = {
type: 'member-deactivated',
memberId: memberId,
}
return postEvent(act).then(res => {
return res
})
}
// senpai function
async resetPassword(memberId) {
const act = {
type: 'member-secret-reset',
kohaiId: memberId,
senpaiId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
// senpai function
async promoteMember(memberId) {
const act = {
type: 'member-promoted',
kohaiId: memberId,
senpaiId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
// senpai function
async banMember(memberId) {
const act = {
type: 'member-banned',
kohaiId: memberId,
senpaiId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
// senpai function
async unbanMember(memberId) {
const act = {
type: 'member-unbanned',
kohaiId: memberId,
senpaiId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
// senpai function
async purgeMember(memberId) {
const act = {
type: 'member-purged',
memberId: memberId,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async updateMemberField(
field,
newValue
) {
if (field === 'secret') {
newValue = createHash(newValue)
}
const act = {
type: 'member-field-updated',
memberId: currentMemberId,
field: field,
newfield: newValue,
}
return postEvent(act).then(res => {
return res
})
}
// Each member has a list of tickers. Each ticker is a string.
// Sets the ticker at position tickerListIndex to symbol coinSymbol.
async setTicker(
fromCoin,
toCoin,
tickerListIndex
) {
const act = {
type: 'member-ticker-set',
memberId: currentMemberId,
fromCoin: fromCoin,
toCoin: toCoin,
index: tickerListIndex,
}
return postEvent(act).then(res => {
return res
})
}
async clockTime(seconds, taskId, date) {
const act = {
type: 'task-time-clocked',
taskIdId,
memberId: currentMemberId,
seconds: seconds,
date: date,
}
return postEvent(act).then(res => {
return res
})
}
async startTimeClock(taskId, inId) {
const act = {
type: 'task-started',
taskIdId,
inId: inId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async stopTimeClock(taskId) {
const act = {
type: 'task-stopped',
taskIdId,
memberId: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async signCard(taskId, opinion = 1) {
const act = {
type: 'task-signed',
taskIdId,
memberId: currentMemberId,
opinion: opinion,
}
return postEvent(act).then(res => {
return res
})
}
async assignMembership(
taskId,
memberId,
level
) {
const act = {
type: 'task-membership',
taskIdId,
memberId: memberId,
level: level,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async stashCard(
taskId,
inId,
level
) {
const act = {
type: 'task-stashed',
taskIdId,
inId: inId,
level: level,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async unstashCard(
taskId,
inId,
level
) {
const act = {
type: 'task-unstashed',
taskIdId,
inId: inId,
level: level,
blame: currentMemberId,
}
return postEvent(act).then(res => {
return res
})
}
async visitCard(
taskId,
inChat = false,
notify = false
) {
const act = {
type: 'task-visited',
taskIdId,
memberId: currentMemberId,
area: inChat ? 1 : 0,
notify: notify,
}
return postEvent(act).then(res => {
return res
})
}
/*
async markSeen(taskId) {
const task = aoStore.hashMap.get(taskId)
const act = {
type: 'task-seen',
taskId: taskId,
memberId: currentMemberId,
}
// console.log('card marked seen')
return postEvent(act).then(res => {
return res
})
}
*/
async resizeGrid(
taskId,
newHeight,
newWidth,
newSize
) {
const act = {
type: 'grid-resized',
taskIdId,
height: newHeight,
width: newWidth,
size: newSize || 9,
}
return postEvent(act).then(res => {
return res
})
}
async createCardWithGrid(
name,
height,
width
) {
const act = {
type: 'grid-created',
name: name,
height: height,
width: width,
color: 'blue',
deck: [currentMemberId],
}
return postEvent(act).then(res => {
return res
})
}
async addGridToCard(
taskId,
height,
width,
spread = 'pyramid'
) {
const act = {
type: 'grid-added',
taskIdId,
spread: spread,
height: height,
width: width,
}
return postEvent(act).then(res => {
return res
})
}
async removeGridFromCard(taskId) {
const act = {
type: 'grid-removed',
taskIdId,
}
return postEvent(act).then(res => {
return res
})
}
/*async pinCardToGrid(
x,
y,
name,
inId
) {
return new Promise((resolve, reject) => {
aoStore.getTaskByName_async(name, (task) => {
console.log('gotTaskByName name was ', name, 'and found is ', task)
// console.log("AO: client/api.ts: pinCardToGrid: ", {x, y, name, inId, task})
if (isObject(task)) {
const fromLocation
const toLocation: CardLocation = {
taskId.taskId,
inId: inId,
coords: { y, x }
}
playCard()
} else {
const act = {
type: 'task-created',
name: name,
color: 'blue',
deck: [currentMemberId],
inId: inId,
prioritized: false,
}
postEvent(act).then(res => {
const taskId = JSON.parse(res.text).event.taskId
const gridAct = {
type: 'grid-pin',
inId: inId,
taskIdId,
x: x,
y: y,
memberId: currentMemberId,
}
resolve(
request
.post('/events')
.set('Authorization', currentSessionToken)
.send(gridAct)
)
})
}
})
})
}*/
/*async unpinCardFromGrid(
x,
y,
inId
) {
const act = {
type: 'grid-unpin',
x,
y,
inId,
}
return postEvent(act).then(res => {
return res
})
}*/
async fetchMeme(memeHash, progressCallback) {
return request
.get(HOSTNAME + '/meme/' + memeHash)
.responseType('blob')
.set('Authorization', currentSessionToken)
.on('progress', function (e) {
progressCallback(e.percent)
})
.then(res => {
console.log('got meme! res is ', res)
return res.body
})
}
async downloadMeme(memeHash) {
return request
.get(HOSTNAME + '/download/' + memeHash)
.set('Authorization', currentSessionToken)
.then(res => {
// console.log('got meme! res is ', res)
return res
})
}
async uploadMemes(formData, progressCallback) {
return postRequest('/upload', formData)
.on('progress', function (e) {
console.log('Percentage done: ', e)
if (e && e.hasOwnProperty('percent') && e.percent >= 0) {
progressCallback(e.percent)
}
})
.on('error', err => {
console.log('Upload failed with error:', err)
return false
})
.then(res => {
console.log('sent files. res is', res)
return res
})
}
async cacheMeme(taskId) {
const act = {
type: 'meme-cached',
taskId,
}
return postEvent(act).then(res => {
return res
})
}
async logout() {
//aoStore.resetState()
if(typeof window !== 'undefined') window.localStorage.clear()
//clear cookie
return postRequest('/logout', {})
.then(res => {
return res
})
}
// This function encodes whatever is passed by the search box as a URIComponent and passes it to a search endpoint, returning the response when supplied
async search(querystring, take = 10, skip = 0) {
const qs = encodeURIComponent(querystring)
const params = `?take=${take}&skip=${skip}`
return postRequest('/search/' + qs + params)
.then(res => {
return res
})
}
async requestBtcQr(taskId) {
const act = {
type: 'address-updated',
taskId,
}
return postEvent(act).then(res => {
return res
})
}
async requestLightningInvoice(taskId, amount = 0) {
const act = {
type: 'invoice-created',
taskId,
amount: amount,
}
return postEvent(act).then(res => {
return res
})
}
/*startSocketListeners() {
this.socket.connect()
this.socket.on('connect', () => {
console.log('connected', { 'aoStore.state': aoStore.state })
runInAction(() => {
aoStore.state.socketState = 'attemptingAuthentication'
const loadedSession = window.localStorage.getItem('session')
if(loadedSession) {
aoStore.state.session = loadedSession
}
const loadedToken = window.localStorage.getItem('token')
if(loadedToken) {
currentSessionToken = loadedToken
}
})
console.log(
'emit auth: session: ' +
window.localStorage.getItem('session') +
', token: ' +
window.localStorage.getItem('token')
)
this.socket.emit('authentication', {
session: window.localStorage.getItem('session'),
token: window.localStorage.getItem('token'),
})
})
this.socket.on('authenticated', () => {
console.log('authenticated')
this.fetchState().then(() => {
runInAction(() => {
aoStore.state.socketState = 'authenticationSuccess'
})
})
this.socket.on('eventstream', ev => {
console.log('AO: client/api.ts: socketListener: event:', ev)
aoStore.applyEvent(ev)
})
})
this.socket.on('disconnect', reason => {
console.log('disconnected')
runInAction(() => {
aoStore.state.socketState = 'authenticationFailed'
})
this.socket.connect()
})
}*/
}
/*reaction(
() => {
//return aoStore.state.socketState
},
socketState => console.log('AO: client/api.ts: socketState: ' + socketState)
)
console.log("NODE_ENV is", process.env.NODE_ENV)*/
const socketUrl = process.env.NODE_ENV === 'development' ? 'http://localhost:8003' : '/'
const socket = io(socketUrl, {
autoConnect: false,
})
const api = new AoApi(socket)
export default api