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.
1125 lines
29 KiB
1125 lines
29 KiB
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: taskId, |
|
subTaskId, |
|
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) { |
|
const act = { |
|
type: 'task-prioritized', |
|
taskId: taskId, |
|
inId: inId, |
|
position: position, |
|
...(echelon && { echelon: echelon }), |
|
blame: currentMemberId |
|
} |
|
console.log(act) |
|
return await postEvent(act) |
|
} |
|
|
|
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 titleMissionCard(taskId, newTitle) { |
|
return await postEvent({ |
|
type: 'task-guilded', |
|
taskId: taskId, |
|
guild: newTitle, |
|
blame: currentMemberId |
|
}) |
|
} |
|
|
|
// 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, |
|
}) |
|
}*/
|
|
|