import { LTOApi } from "./api" import { v4 as uuidv4 } from 'uuid'; import { RefStore } from "../../state/state"; import { debug } from "loglevel"; export const TxnStates = ["PENDING","INFLIGHT","WORKING","ERROR","SUCCESS"] as const export type TxnState = typeof TxnStates[number] export interface TxnDetails { item_uid: string | "galders" count:number origin:string target:string origin_path:string target_path:string origin_account:string target_account:string } export interface Envelope { req: REQ resp: RESP state: TxnState } export abstract class Order { action_id: string details?:TxnDetails created:Date state: TxnState constructor(details?:TxnDetails) { this.state = "PENDING" this.details = details this.created = new Date() this.action_id = uuidv4(); } mark(t:TxnState) { this.state = t } abstract tick(r:RefStore, api:LTOApi):Promise abstract status():string abstract progress():[number, number] abstract error():string abstract order_type:OrderType parse(i:any):Order { this.action_id = i.action_id this.details = i.details this.created = new Date(i.created) this.state = i.state return this } } export abstract class BasicOrder extends Order { stage: number err?: string constructor(details:TxnDetails) { super(details) this.stage = 0 } progress():[number,number]{ return [this.stage, 1] } status():string { return this.state } error():string { return this.err ? this.err : "" } parse(i:any):BasicOrder { this.stage = i.stage this.err = i.err super.parse(i) return this } } /// start user defined export const OrderTypes = ["InvalidOrder","BankItem","InternalXfer", "PrivateMarket","MarketMove", "MarketMoveToChar"] export type OrderType = typeof OrderTypes[number] export class InvalidOrder extends Order{ order_type = "InvalidOrder" msg:string constructor(msg: string){ super(undefined) this.msg = msg this.mark("ERROR") } status():string { return "ERROR" } progress():[number, number] { return [0,0] } error(): string { return this.msg } async tick(r:RefStore, api:LTOApi):Promise { return } parse(i:any):InvalidOrder { super.parse(i) this.msg = i.msg return this } } export interface BasicResponse { status: number data: any msg?: string } export interface InternalXferRequest { item_uid:string qty:string account:string new_char:string } export interface InternalXferResponse extends BasicResponse {} export class InternalXfer extends BasicOrder{ order_type = "InternalXfer" originalRequest:InternalXferRequest originalResponse?:InternalXferResponse constructor(details:TxnDetails) { super(details) this.originalRequest = { item_uid: details.item_uid, qty: details.count.toString(), new_char: details.target, account: details.origin, } } async tick(r:RefStore, api:LTOApi):Promise { if(this.state !== "PENDING") { return } this.mark("WORKING") return api.BankAction("internal-xfer-item",this.originalRequest) .then((x:InternalXferResponse)=>{ if(x.status == 200){ this.originalResponse = x this.stage = 1 this.mark("SUCCESS") const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]! origin_item.item_count = origin_item.item_count - this.details?.count! }else{ throw x.msg } }) .catch((e)=>{ debug("InternalXfer",e) this.stage = 1 this.err = e this.mark("ERROR") }) } parse(i:any):InternalXfer { super.parse(i) this.originalRequest = i.originalRequest this.originalResponse = i.originalResponse return this } } export interface BankItemRequest { item_uid:string qty:string account:string } export interface BankItemResponse extends BasicResponse {} export class BankItem extends BasicOrder{ order_type = "BankItem"; originalRequest:BankItemRequest originalResponse?:BankItemResponse constructor(details:TxnDetails) { super(details) this.originalRequest = { item_uid: details.item_uid, qty: details.count.toString(), account: details.target, } } async tick(r:RefStore, api:LTOApi):Promise { if(this.state !== "PENDING" ){ return } this.mark("WORKING") return api.BankAction("bank-item",this.originalRequest) .then((x)=>{ debug("BankItem",x) if(x.status == 200){ this.stage = 1 this.originalResponse = x this.mark("SUCCESS") const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]! origin_item.item_count = origin_item.item_count - this.details?.count! }else { throw x.msg ? x.msg : "unknown error" } }) .catch((e)=>{ this.stage = 1 this.err = e this.mark("ERROR") }) } parse(i:any):BankItem { super.parse(i) this.originalRequest = i.originalRequest this.originalResponse = i.originalResponse return this } } export interface PrivateMarketRequest { item_uid:string qty:string account:string currency:string price:number private:number } export interface PrivateMarketResponse extends BasicResponse {} export class PrivateMarket extends BasicOrder{ order_type = "PrivateMarket"; originalRequest:PrivateMarketRequest originalResponse?:PrivateMarketResponse listingId?: string listingHash?: string constructor(details:TxnDetails) { super(details) this.originalRequest = { item_uid: details.item_uid, qty: details.count.toString(), account: details.origin_account, private: 1, currency: "0", price: 1, } } async tick(r:RefStore, api:LTOApi):Promise { if(this.state !== "PENDING" ){ return } this.mark("WORKING") return api.BankAction("sell-item",this.originalRequest) .then((x)=>{ debug("PrivateMarket",x) if(x.status == 200){ this.stage = 1 this.originalResponse = x this.mark("SUCCESS") this.listingId = x.data["listing_id"] this.listingHash = x.data["hash"] try{ const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]! origin_item.item_count = origin_item.item_count - this.details?.count! }catch(e){ } }else { throw x.msg ? x.msg : "unknown error" } }) .catch((e)=>{ this.stage = 1 this.err = e this.mark("ERROR") }) } parse(i:any):PrivateMarket { super.parse(i) this.originalRequest = i.originalRequest this.originalResponse = i.originalResponse this.listingId = i.listingId this.listingHash = i.listingHash return this } } export interface MarketMoveRequest { listing_id?: string qty:string account:string character: string } export interface MarketMoveResponse extends BasicResponse { item_uid: string } export class MarketMove extends PrivateMarket { order_type = "MarketMove"; moveRequest:MarketMoveRequest moveResponse?:MarketMoveResponse moveStage:number moveState: TxnState newUid: string constructor(details:TxnDetails) { super(details) this.moveStage = 0 this.moveState = "PENDING" this.newUid = "" this.moveRequest = { qty: details.count.toString(), account: details.target_account, character: (details.target_path.includes("/")) ? details.target : "0" , listing_id: "", // not initially populated } } async tick(r:RefStore, api:LTOApi):Promise { try { await super.tick(r, api) }catch(e){ return } switch(super.status()) { case "SUCCESS": break; case "ERROR": this.moveState = "ERROR" return default: return } if(this.moveState !== "PENDING" ){ return } this.moveRequest.listing_id = `${this.listingId}-${this.listingHash}` this.moveState = "WORKING" return api.BankAction("buy-from-order",this.moveRequest) .then((x)=>{ debug("MarketMove",x) this.moveResponse = x if(x.status == 200){ this.moveStage = 1 this.moveState = "SUCCESS" this.newUid = x.item_uid }else { throw x ? x : "unknown error" } }) .catch((e)=>{ this.moveStage = 1 this.err = e this.moveState = "ERROR" }) } progress():[number,number]{ return [this.stage + this.moveStage, 2] } status():string { return this.moveState } parse(i:any):MarketMove { super.parse(i) this.moveRequest = i.moveRequest this.moveResponse = i.moveResponse this.moveState = i.moveState this.moveStage = i.moveStage return this } } export class MarketMoveToChar extends MarketMove { order_type = "MarketMoveToChar"; charRequest:InternalXferRequest charResponse?:InternalXferResponse charStage:number charState: TxnState constructor(details:TxnDetails) { super(details) this.charStage = 0 this.charState = "PENDING" this.charRequest = { item_uid: "", qty: details.count.toString(), new_char: details.target, account: details.target_account, } } async tick(r:RefStore, api:LTOApi):Promise { try { await super.tick(r, api) }catch(e){ return } switch(super.status()) { case "SUCCESS": break; case "ERROR": this.charState = "ERROR" return default: return } if(this.charState !== "PENDING" ){ return } this.charState = "WORKING" this.charRequest.item_uid = this.newUid return api.BankAction("internal-xfer-item",this.charRequest) .then((x)=>{ debug("MarketMoveToChar",x) this.charResponse = x if(x.status == 200){ this.charStage = 1 this.charState = "SUCCESS" }else { throw x ? x : "unknown error" } }) .catch((e)=>{ this.charStage = 1 this.err = e this.charState = "ERROR" }) } progress():[number,number]{ return [this.stage +this.moveStage+ this.charStage, 3] } status():string { return this.charState } parse(i:any):MarketMoveToChar { super.parse(i) this.charRequest = i.charRequest this.charResponse = i.charResponse this.charState = i.charState this.charStage = i.charStage return this } }