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.
1128 lines
29 KiB
1128 lines
29 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 { isObject } from './util.js'
|
||
|
import { aoEnv } from './settings.js'
|
||
|
|
||
|
// The AO API server endpoint this ao-cli client will attempt to connect to
|
||
|
export const AO_DEFAULT_HOSTNAME = 'localhost:8003'
|
||
|
const HOSTNAME = aoEnv('AO_CLI_TARGET_HOSTNAME') || AO_DEFAULT_HOSTNAME
|
||
|
const [HOST, PORT] = HOSTNAME.split(':')
|
||
|
|
||
|
// The AO API server websocket endpoint this ao-cli client will attempt to connect to
|
||
|
const AO_SOCKET_URL = 'http://' + AO_DEFAULT_HOSTNAME // was process.env.NODE_ENV === 'development' ? 'http://' + AO_DEFAULT_HOSTNAME : '/'
|
||
|
export const socket = io(AO_SOCKET_URL, {
|
||
|
autoConnect: false
|
||
|
})
|
||
|
|
||
|
// The current connected state of the websocket, can be 'attemptingAuthentication' | 'authenticationSuccess' | 'authenticationFailed'
|
||
|
export let socketStatus
|
||
|
|
||
|
// Load the current session cookies from the AO .env file
|
||
|
let currentMemberId = aoEnv('AO_CLI_SESSION_MEMBERID')
|
||
|
let currentSessionId = aoEnv('AO_CLI_SESSION_ID')
|
||
|
let currentSessionToken = aoEnv('AO_CLI_SESSION_TOKEN')
|
||
|
|
||
|
// Performs a GET request to the specified endpoint, sending the given payload
|
||
|
export async function getRequest(endpoint, payload = null, alternateHost = null, verbose = true) {
|
||
|
const target = alternateHost || HOSTNAME
|
||
|
try {
|
||
|
if(payload) {
|
||
|
return await request
|
||
|
.get(target + endpoint)
|
||
|
.send(payload)
|
||
|
} else {
|
||
|
return await request.get(target + endpoint)
|
||
|
}
|
||
|
} catch (err) {
|
||
|
if(verbose) console.log('request failed', err)
|
||
|
return null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Performs a POST request to the specified endpoint, sending the given payload
|
||
|
export async function postRequest(endpoint, payload = null, verbose = true) {
|
||
|
if (!currentSessionToken) {
|
||
|
if(verbose) console.log('Session token not set, API not ready.')
|
||
|
return new Promise(() => null)
|
||
|
}
|
||
|
try {
|
||
|
if(payload) {
|
||
|
return await request
|
||
|
.post(HOSTNAME + endpoint)
|
||
|
.send(payload)
|
||
|
.set('authorization', currentSessionToken)
|
||
|
.set('session', currentSessionId)
|
||
|
} else {
|
||
|
return await request.post(HOSTNAME + endpoint)
|
||
|
.set('authorization', currentSessionToken)
|
||
|
.set('session', currentSessionId)
|
||
|
}
|
||
|
} catch (err) {
|
||
|
if(verbose) console.log('request failed', err)
|
||
|
return null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Performs a post request to the /event endpoint, sending the given JSON object as the event
|
||
|
export async function postEvent(event, verbose) {
|
||
|
return await postRequest('/events', event, verbose)
|
||
|
}
|
||
|
|
||
|
// Attempts login with the given username and password combo. If successful, returns the generated session and token (login cookies).
|
||
|
export async function createSession(user, pass) {
|
||
|
const session = uuidV1()
|
||
|
let sessionKey = createHash(session + createHash(pass))
|
||
|
const token = hmacHex(session, sessionKey)
|
||
|
const result = await request
|
||
|
.post(HOSTNAME + '/session')
|
||
|
.set('authorization', token)
|
||
|
.set('session', session)
|
||
|
.set('name', user)
|
||
|
.on('error', () => false)
|
||
|
currentMemberId = result.body.memberId
|
||
|
currentSessionToken = token
|
||
|
currentSessionId = session
|
||
|
return { session, token, memberId: currentMemberId }
|
||
|
}
|
||
|
|
||
|
export async function logout() {
|
||
|
return await postRequest('/logout')
|
||
|
}
|
||
|
|
||
|
// AO p2p over tor features
|
||
|
export async function nameAo(newName) {
|
||
|
return await postEvent({
|
||
|
type: 'ao-named',
|
||
|
alias: newName
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// When you call startSocketListeners, it will either attempt to connect and authenticate on a web socket, or fail and return.
|
||
|
// onAuthenticated is a function that will be called when the client authenticates on the web socket (logs in/connects).
|
||
|
// In your onAuthenticated function, you should trigger fetchState or other initial fetching of state from server.
|
||
|
// eventCallback is a function (ev) => {} that will be called whenever an event is received on the socket.
|
||
|
// In this way, initial state can be fetched and then updates received after that can be used to update the local state model.
|
||
|
export async function startSocketListeners(onAuthenticated, onEvent, verbose = true) {
|
||
|
if(typeof onAuthenticated !== 'function' || typeof onEvent !== 'function') {
|
||
|
console.log('startSocketListeners requires two callback functions as arguments.')
|
||
|
return
|
||
|
}
|
||
|
socket.connect()
|
||
|
socket.on('connect', () => {
|
||
|
if(verbose) console.log('websocket connected')
|
||
|
socketStatus = 'attemptingAuthentication'
|
||
|
if(!currentSessionId || !currentSessionToken) {
|
||
|
if(verbose) console.log('No current session, must log in to authenticate and use socket.')
|
||
|
return
|
||
|
}
|
||
|
|
||
|
socket.emit('authentication', {
|
||
|
session: currentSessionId,
|
||
|
token: currentSessionToken,
|
||
|
})
|
||
|
})
|
||
|
socket.on('authenticated', () => {
|
||
|
if(verbose) console.log('websocket authenticated')
|
||
|
socketStatus = 'authenticationSuccess'
|
||
|
socket.on('eventstream', onEvent)
|
||
|
onAuthenticated()
|
||
|
})
|
||
|
socket.on('disconnect', reason => {
|
||
|
if(verbose) console.log('websocket disconnected')
|
||
|
socketStatus = 'authenticationFailed'
|
||
|
socket.connect()
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Requests the public bootstrap list by making a public (not logged in) GET request to this or the specified server
|
||
|
export async function getAoBootstrapList(serverOnion = null) {
|
||
|
const result = await getRequest('/bootstrap', undefined, serverOnion, false)
|
||
|
if(!result || !result.ok || result.body.addresses.length < 1) {
|
||
|
return null
|
||
|
}
|
||
|
return result.body.addresses
|
||
|
}
|
||
|
|
||
|
// Gets the bootsrap list from the specified or our server, then recursively bootstraps from each address on the list
|
||
|
// The AO network is small right now so this shouldn't cause any problems for a while
|
||
|
export async function bootstrap(serverOnion = null) {
|
||
|
if(!serverOnion) serverOnion = HOSTNAME
|
||
|
let alreadyQueried = [ serverOnion ]
|
||
|
let onionList = await getAoBootstrapList(serverOnion)
|
||
|
if(!onionList) {
|
||
|
return null
|
||
|
}
|
||
|
for(let i = 0; i < onionList.length; i++) {
|
||
|
const onion = onionList[i]
|
||
|
let more = await bootstrap(onion)
|
||
|
if(!more) continue
|
||
|
more = more.filter(onion => !onionList.concat(alreadyQueried).includes(onion))
|
||
|
onionList.concat(more)
|
||
|
}
|
||
|
return onionList
|
||
|
}
|
||
|
|
||
|
export async function shadowchat(room, message, username) {
|
||
|
return await postEvent({
|
||
|
type: 'shadowchat',
|
||
|
room: room,
|
||
|
name: username,
|
||
|
message: message,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function connectToAo(address, secret) {
|
||
|
return await postEvent({
|
||
|
type: 'ao-outbound-connected',
|
||
|
address: address,
|
||
|
secret: secret
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function deleteAoConnection(address) {
|
||
|
return await postEvent({
|
||
|
type: 'ao-disconnected',
|
||
|
address: address
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function relayEventToOtherAo(address, event) {
|
||
|
return await postEvent({
|
||
|
type: 'ao-relay',
|
||
|
address: address,
|
||
|
ev: event
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function linkCardOnAo(taskId, address) {
|
||
|
return await postEvent({
|
||
|
type: 'ao-linked',
|
||
|
address: address,
|
||
|
taskId: taskId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Avatar and presence features
|
||
|
export async function bark() {
|
||
|
return await postEvent({
|
||
|
type: 'doge-barked',
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function hopped(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'doge-hopped',
|
||
|
memberId: currentMemberId,
|
||
|
taskId: taskId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function mute() {
|
||
|
return await updateMemberField('muted', true)
|
||
|
}
|
||
|
|
||
|
export async function unmute() {
|
||
|
return await updateMemberField('muted', false)
|
||
|
}
|
||
|
|
||
|
// Memes feature
|
||
|
export async function 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
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function downloadMeme(memeHash) {
|
||
|
return request
|
||
|
.get(HOSTNAME + '/download/' + memeHash)
|
||
|
.set('Authorization', currentSessionToken)
|
||
|
.then(res => {
|
||
|
// console.log('got meme! res is ', res)
|
||
|
return res
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function 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
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function cacheMeme(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'meme-cached',
|
||
|
taskId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Cards feature
|
||
|
// Returns the card and other cards as specified by the alsoGetRelevant arg
|
||
|
// If multiple cards are returned, they will be returned in their global deck order (global creation order on server)
|
||
|
export async function getCard(taskId, alsoGetRelevant = 'subcards') {
|
||
|
taskId = taskId.trim().toLowerCase()
|
||
|
let payload = { taskId: taskId }
|
||
|
const result = await postRequest('/fetchTaskByID', payload, false) // todo: change to flat text, not JSON (?)
|
||
|
if(!result || !result.body) {
|
||
|
//console.log('Error fetching task.')
|
||
|
return null
|
||
|
}
|
||
|
if(alsoGetRelevant) {
|
||
|
let relevantCards = await getAllRelevantCards(result.body, alsoGetRelevant)
|
||
|
return [result.body, ...relevantCards]
|
||
|
}
|
||
|
return [result.body]
|
||
|
}
|
||
|
|
||
|
// Cards feature
|
||
|
export async function getCardByName(taskName, alsoGetRelevant = 'subcards') {
|
||
|
taskName = taskName.trim()
|
||
|
let payload = { taskName: taskName }
|
||
|
const result = await postRequest('/fetchTaskByName_exact', payload, false) // todo: change to flat text, not JSON (?)
|
||
|
if(!result || !result.body || result.statusCode === 204 || result.statusCode === 400) {
|
||
|
//console.log('Error fetching task.')
|
||
|
return null
|
||
|
}
|
||
|
if(alsoGetRelevant) {
|
||
|
let relevantCards = await getAllRelevantCards(result.body, alsoGetRelevant)
|
||
|
return [result.body, ...relevantCards]
|
||
|
}
|
||
|
return [result.body]
|
||
|
}
|
||
|
|
||
|
// Fetches all cards related to the given card object, i.e., cards that could be seen or navigated to immediately from that card
|
||
|
// scope = 'priority' returns only the first/top priority card within the specified card
|
||
|
// scope = 'priorities' returns only the priorities within the specified card
|
||
|
// scope = 'subcards' returns all subcards (priorities, pinned, subTasks, completed)
|
||
|
// Further scopes are not currently needed because the server also includes some related cards with each send
|
||
|
// If existingTasks: Map<string, Task> is provided, those cards will be skipped
|
||
|
// Returns the new cards that were fetched (not any existingTasks), plus cards the server chooses to also include
|
||
|
export async function getAllRelevantCards(
|
||
|
seedTask,
|
||
|
scope = 'priorities',
|
||
|
existingTasks
|
||
|
) {
|
||
|
if(existingTasks === undefined) {
|
||
|
existingTasks = new Map()
|
||
|
}
|
||
|
let taskIdsToFetch
|
||
|
|
||
|
// Choose which taskIds we are going to request from the server
|
||
|
switch (scope) {
|
||
|
case 'priority':
|
||
|
taskIdsToFetch = new Set([seedTask.priorities.at(-1)])
|
||
|
break
|
||
|
case 'priorities':
|
||
|
taskIdsToFetch = new Set(seedTask.priorities)
|
||
|
break
|
||
|
case 'subcards':
|
||
|
taskIdsToFetch = new Set(
|
||
|
seedTask.priorities.concat(seedTask.subTasks, seedTask.completed)
|
||
|
)
|
||
|
if (seedTask.pins && seedTask.pins.length >= 1) {
|
||
|
seedTask.pins.forEach(pin => {
|
||
|
taskIdsToFetch.add(pin.taskId)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Filter out the taskIds for tasks we already have
|
||
|
taskIdsToFetch = [...taskIdsToFetch].filter(taskId => {
|
||
|
if (!taskId) {
|
||
|
return false
|
||
|
}
|
||
|
const existingTask = existingTasks.get(taskId)
|
||
|
return !existingTask
|
||
|
})
|
||
|
if(taskIdsToFetch.length < 1) {
|
||
|
return []
|
||
|
}
|
||
|
|
||
|
// Fetch the cards
|
||
|
try {
|
||
|
const result = await postRequest('/fetchTasks', { taskIds: taskIdsToFetch })
|
||
|
// Filter again (overlapping queries or intelligent server can cause duplicates to be returned)
|
||
|
const newTasksOnly = result.body.filter(
|
||
|
fetchedTask => !existingTasks.get(fetchedTask.taskId)
|
||
|
)
|
||
|
return newTasksOnly
|
||
|
} catch (error) {
|
||
|
console.log('Error fetching relevant tasks:', { taskIdsToFetch, error })
|
||
|
return null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export async function createCard(
|
||
|
name,
|
||
|
anonymous = false,
|
||
|
prioritized = false
|
||
|
) {
|
||
|
return await postEvent({
|
||
|
type: 'task-created',
|
||
|
name: name,
|
||
|
color: 'blue',
|
||
|
deck: (anonymous || !currentMemberId) ? [] : [currentMemberId],
|
||
|
inId: anonymous ? null : currentMemberId || null,
|
||
|
prioritized: prioritized,
|
||
|
}, false)
|
||
|
}
|
||
|
|
||
|
// This is different from only setting a card property because when a card's color changes, it is bumped to the top of the .subTasks color pile
|
||
|
export async function colorCard(taskId, color) {
|
||
|
return await postEvent({
|
||
|
type: 'task-colored',
|
||
|
taskId: taskId,
|
||
|
color: color,
|
||
|
inId: null, // add this when we have context, mutation works on server
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Set arbitrary metadata on a card
|
||
|
export async function setCardProperty(taskId, property, value) {
|
||
|
return await postEvent({
|
||
|
type: 'task-property-set',
|
||
|
taskId: taskId,
|
||
|
property: property,
|
||
|
value: value,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Card send feature
|
||
|
export async function passCard(taskId, toMemberId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-passed',
|
||
|
taskId: taskId,
|
||
|
toMemberId: toMemberId,
|
||
|
fromMemberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Send an immediate bark and notification to the member if possible, reminding them that they have an unread message from you (no automated pings)
|
||
|
export async function remindMember(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-reminded',
|
||
|
toMemberId: memberId,
|
||
|
fromMemberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Cards-in-cards feature
|
||
|
export async function playCard(from = null, to) {
|
||
|
return await postEvent({
|
||
|
type: 'task-played',
|
||
|
from: from,
|
||
|
to: to,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function discardCardFromCard(taskId, inId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-de-sub-tasked',
|
||
|
taskId: inId,
|
||
|
subTask: taskId,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Empties a card's priorities and subtasks
|
||
|
export async function emptyCard(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-emptied',
|
||
|
taskId: taskId,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function swapCard(inId, taskId1, taskId2) {
|
||
|
return await postEvent({
|
||
|
type: 'task-swapped',
|
||
|
taskId: inId,
|
||
|
swapId1: taskId1,
|
||
|
swapId2: taskId2,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function bumpCard(taskId, inId, direction) {
|
||
|
return await postEvent({
|
||
|
type: 'task-bumped',
|
||
|
taskId: inId,
|
||
|
bumpId: taskId,
|
||
|
direction: direction,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Deck features
|
||
|
export async function grabCard(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-grabbed',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function grabPile(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'pile-grabbed',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function dropCard(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-dropped',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function removeCards(taskIds) {
|
||
|
return await postEvent({
|
||
|
type: 'tasks-removed',
|
||
|
taskIds: taskIds,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function dropPile(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'pile-dropped',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Priority feature
|
||
|
export async function prioritizeCard(taskId, inId, position = 0, echelon = null) {
|
||
|
return await postEvent({
|
||
|
type: 'task-prioritized',
|
||
|
taskId: taskId,
|
||
|
inId: inId,
|
||
|
position: position,
|
||
|
...(echelon && { echelon: echelon }),
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function prioritizePile(inId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-prioritized',
|
||
|
inId: inId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function refocusCard(taskId, inId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-refocused',
|
||
|
taskId: taskId,
|
||
|
inId: inId,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function refocusPile(inId) {
|
||
|
return await postEvent({
|
||
|
type: 'pile-refocused',
|
||
|
inId: inId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function allocatePriority(inId, taskId, points = 1) {
|
||
|
return await postEvent({
|
||
|
type: 'task-allocated',
|
||
|
taskId: inId,
|
||
|
allocatedId: taskId,
|
||
|
amount: points,
|
||
|
blame: currentMemberId
|
||
|
//inId: inId,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Guilds feature
|
||
|
export async function tagCard(taskId, newTitle) {
|
||
|
return await postEvent({
|
||
|
type: 'task-guilded',
|
||
|
taskId: taskId,
|
||
|
guild: newTitle,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function getCardsForTag(tag) {
|
||
|
return (await postRequest('/fetchTasksByGuild', { guild: tag })).body.cards
|
||
|
}
|
||
|
|
||
|
// Checkmarks feature
|
||
|
export async function completeCard(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-claimed',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function uncheckCard(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-unclaimed',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function setClaimInterval(taskId, newClaimInterval) {
|
||
|
return await setCardProperty(taskId, claimInterval, newClaimInterval)
|
||
|
}
|
||
|
|
||
|
// Hardware resources feature
|
||
|
export async function createResource(
|
||
|
resourceId,
|
||
|
name,
|
||
|
charged,
|
||
|
secret,
|
||
|
trackStock
|
||
|
) {
|
||
|
return await postEvent({
|
||
|
type: 'resource-created',
|
||
|
resourceId: resourceId,
|
||
|
name: name,
|
||
|
charged: charged,
|
||
|
secret: secret,
|
||
|
trackStock: trackStock,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function useResource(resourceId, amount, charged, notes = '') {
|
||
|
return await postEvent({
|
||
|
type: 'resource-used',
|
||
|
resourceId: resourceId,
|
||
|
memberId: currentMemberId,
|
||
|
amount: amount,
|
||
|
charged: charged,
|
||
|
notes: notes
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function stockResource(resourceId, amount, paid, notes = '') {
|
||
|
return await postEvent({
|
||
|
type: 'resource-stocked',
|
||
|
resourceId: resourceId,
|
||
|
memberId: currentMemberId,
|
||
|
amount: amount,
|
||
|
paid: paid,
|
||
|
notes: notes
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function purgeResource(resourceId) {
|
||
|
return await postEvent({
|
||
|
type: 'resource-purged',
|
||
|
resourceId: resourceId,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function bookResource(taskId, startTime, endTime) {
|
||
|
return await postEvent({
|
||
|
type: 'resource-booked',
|
||
|
resourceId: taskId,
|
||
|
memberId: currentMemberId,
|
||
|
startTs: startTime,
|
||
|
endTs: endTime
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Member account features
|
||
|
export async function updateMemberField(field, newValue) {
|
||
|
if (field === 'secret') {
|
||
|
newValue = createHash(newValue)
|
||
|
}
|
||
|
return await postEvent({
|
||
|
type: 'member-field-updated',
|
||
|
memberId: currentMemberId,
|
||
|
field: field,
|
||
|
newfield: newValue
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Member admin features
|
||
|
export async function createMember(name, fob = '') {
|
||
|
const secret = createHash(name)
|
||
|
return await postEvent({
|
||
|
type: 'member-created',
|
||
|
name,
|
||
|
secret,
|
||
|
fob
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function activateMember(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-activated',
|
||
|
memberId: memberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function deactivateMember(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-deactivated',
|
||
|
memberId: memberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// senpai function
|
||
|
export async function resetPassword(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-secret-reset',
|
||
|
kohaiId: memberId,
|
||
|
senpaiId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// senpai function
|
||
|
export async function promoteMember(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-promoted',
|
||
|
kohaiId: memberId,
|
||
|
senpaiId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// senpai function
|
||
|
export async function banMember(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-banned',
|
||
|
kohaiId: memberId,
|
||
|
senpaiId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// senpai function
|
||
|
export async function unbanMember(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-unbanned',
|
||
|
kohaiId: memberId,
|
||
|
senpaiId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// senpai function
|
||
|
export async function purgeMember(memberId) {
|
||
|
return await postEvent({
|
||
|
type: 'member-purged',
|
||
|
memberId: memberId,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Each member has a list of tickers. Each ticker is a string.
|
||
|
// Sets the ticker at position tickerListIndex to symbol coinSymbol.
|
||
|
export async function setTicker(fromCoin, toCoin, tickerListIndex) {
|
||
|
return await postEvent({
|
||
|
type: 'member-ticker-set',
|
||
|
memberId: currentMemberId,
|
||
|
fromCoin: fromCoin,
|
||
|
toCoin: toCoin,
|
||
|
index: tickerListIndex
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Timeclock features
|
||
|
export async function clockTime(seconds, taskId, date) {
|
||
|
return await postEvent({
|
||
|
type: 'task-time-clocked',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId,
|
||
|
seconds: seconds,
|
||
|
date: date
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function startTimeClock(taskId, inId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-started',
|
||
|
taskId: taskId,
|
||
|
inId: inId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function stopTimeClock(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'task-stopped',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Group membership features
|
||
|
export async function assignMembership(taskId, memberId, level) {
|
||
|
return await postEvent({
|
||
|
type: 'task-membership',
|
||
|
taskId: taskId,
|
||
|
memberId: memberId,
|
||
|
level: level,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function stashCard(taskId, inId, level) {
|
||
|
return await postEvent({
|
||
|
type: 'task-stashed',
|
||
|
taskId: taskId,
|
||
|
inId: inId,
|
||
|
level: level,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function unstashCard(taskId, inId, level) {
|
||
|
return await postEvent({
|
||
|
type: 'task-unstashed',
|
||
|
taskId: taskId,
|
||
|
inId: inId,
|
||
|
level: level,
|
||
|
blame: currentMemberId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Unreads feature
|
||
|
export async function visitCard(taskId, inChat = false, notify = false) {
|
||
|
return await postEvent({
|
||
|
type: 'task-visited',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId,
|
||
|
area: inChat ? 1 : 0,
|
||
|
notify: notify
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
export async function markSeen(taskId) {
|
||
|
const task = aoStore.hashMap.get(taskId)
|
||
|
const act = {
|
||
|
type: 'task-seen',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId,
|
||
|
}
|
||
|
// console.log('card marked seen')
|
||
|
return await postEvent(act)
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
// Pinboard feature
|
||
|
export async function resizeGrid(taskId, newHeight, newWidth, newSize) {
|
||
|
return await postEvent({
|
||
|
type: 'grid-resized',
|
||
|
taskId: taskId,
|
||
|
height: newHeight,
|
||
|
width: newWidth,
|
||
|
size: newSize || 9
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function createCardWithGrid(name, height, width) {
|
||
|
return await postEvent({
|
||
|
type: 'grid-created',
|
||
|
name: name,
|
||
|
height: height,
|
||
|
width: width,
|
||
|
color: 'blue',
|
||
|
deck: [currentMemberId]
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function addGridToCard(taskId, height, width, spread = 'pyramid') {
|
||
|
return await postEvent({
|
||
|
type: 'grid-added',
|
||
|
taskId: taskId,
|
||
|
spread: spread,
|
||
|
height: height,
|
||
|
width: width
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function removeGridFromCard(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'grid-removed',
|
||
|
taskId: taskId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
export async function search(querystring, take = 10, skip = 0) {
|
||
|
const qs = encodeURIComponent(querystring)
|
||
|
const params = `?take=${take}&skip=${skip}`
|
||
|
return await postRequest('/search/' + qs + params)
|
||
|
}
|
||
|
|
||
|
export async function requestBtcQr(taskId) {
|
||
|
return await postEvent({
|
||
|
type: 'address-updated',
|
||
|
taskId
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function requestLightningInvoice(taskId, amount = 0) {
|
||
|
return await postEvent({
|
||
|
type: 'invoice-created',
|
||
|
taskId,
|
||
|
amount: amount
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Proposals features
|
||
|
export async function signCard(taskId, opinion = 1) {
|
||
|
return await postEvent({
|
||
|
type: 'task-signed',
|
||
|
taskId: taskId,
|
||
|
memberId: currentMemberId,
|
||
|
opinion: opinion
|
||
|
})
|
||
|
}
|
||
|
|
||
|
export async function setQuorum(quorum) {
|
||
|
return await postEvent({
|
||
|
type: 'quorum-set',
|
||
|
quorum: quorum
|
||
|
})
|
||
|
}
|
||
|
|
||
|
/*reaction(
|
||
|
() => {
|
||
|
//return aoStore.state.socketState
|
||
|
},
|
||
|
socketState => console.log('AO: client/api.ts: socketState: ' + socketState)
|
||
|
)
|
||
|
console.log("NODE_ENV is", process.env.NODE_ENV)*/
|
||
|
|
||
|
//const api = new AoApi(socket)
|
||
|
//export default api
|
||
|
|
||
|
/*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)
|
||
|
}*/
|
||
|
|
||
|
/*
|
||
|
export 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)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
export 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 function 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,
|
||
|
taskId: taskId,
|
||
|
x: x,
|
||
|
y: y,
|
||
|
memberId: currentMemberId,
|
||
|
}
|
||
|
resolve(
|
||
|
request
|
||
|
.post('/events')
|
||
|
.set('Authorization', currentSessionToken)
|
||
|
.send(gridAct)
|
||
|
)
|
||
|
})
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
}*/
|
||
|
|
||
|
/*async function unpinCardFromGrid(
|
||
|
x,
|
||
|
y,
|
||
|
inId
|
||
|
) {
|
||
|
return await postEvent({
|
||
|
type: 'grid-unpin',
|
||
|
x,
|
||
|
y,
|
||
|
inId,
|
||
|
})
|
||
|
}*/
|