steam-tradeoffer-manager-promise
Version:
A Fork with promise Support | A simple trade offers API for Steam
1,129 lines (983 loc) • 27.5 kB
JavaScript
"use strict";
const SteamID = require("steamid");
const EconItem = require("./EconItem.js");
const Helpers = require("../helpers.js");
const ETradeOfferState = require("../../resources/ETradeOfferState.js");
const EOfferFilter = require("../../resources/EOfferFilter.js");
const EConfirmationMethod = require("../../resources/EConfirmationMethod.js");
const ETradeStatus = require("../../resources/ETradeStatus.js");
function TradeOffer(manager, partner, token) {
if (typeof partner === "string") {
this.partner = new SteamID(partner);
} else {
this.partner = partner;
}
if (
!this.partner.isValid ||
!this.partner.isValid() ||
this.partner.type != SteamID.Type.INDIVIDUAL
) {
throw new Error("Invalid input SteamID " + this.partner);
}
Object.defineProperties(this, {
_countering: {
configurable: true,
enumerable: false,
writable: true,
value: null,
},
_tempData: {
configurable: true,
enumerable: false,
writable: true,
value: {},
},
_token: {
configurable: true,
enumerable: false,
writable: true,
value: token,
},
manager: {
configurable: false,
enumerable: false,
writable: false,
value: manager,
},
});
this.id = null;
this.message = null;
this.state = ETradeOfferState.Invalid;
this.itemsToGive = [];
this.itemsToReceive = [];
this.isOurOffer = null;
this.created = null;
this.updated = null;
this.expires = null;
this.tradeID = null;
this.fromRealTimeTrade = null;
this.confirmationMethod = null;
this.escrowEnds = null;
this.rawJson = "";
}
TradeOffer.prototype.isGlitched = function () {
if (!this.id) {
// not sent yet
return false;
}
if (this.itemsToGive.length + this.itemsToReceive.length == 0) {
return true;
}
// Is any item missing its name?
//noinspection RedundantIfStatementJS
if (
this.manager._language &&
this.itemsToGive.concat(this.itemsToReceive).some((item) => !item.name)
) {
return true;
}
return false;
};
TradeOffer.prototype.containsItem = function (item) {
return this.itemsToGive
.concat(this.itemsToReceive)
.some((offerItem) => Helpers.itemEquals(offerItem, item));
};
TradeOffer.prototype.data = function (key, value) {
var pollData = this.manager.pollData;
if (arguments.length < 1) {
// No arguments passed, so we return the whole offerData of this offer
if (!this.id) {
// Offer isn't sent yet, return object cache
return this._tempData;
}
// return offerData from pollData if it exists, else return an empty object
return (pollData.offerData && pollData.offerData[this.id]) || {};
} else if (arguments.length < 2) {
// We're only retrieving the value.
if (!this.id) {
// Offer isn't sent yet, return from object cache
return this._tempData[key];
}
return (
pollData.offerData &&
pollData.offerData[this.id] &&
pollData.offerData[this.id][key]
);
} else {
// If this is a special data key, perform necessary checks
switch (key) {
case "cancelTime":
if (!this.isOurOffer) {
throw new Error(
`Cannot set cancelTime for offer #${this.id} as we did not send it.`
);
}
if (
this.id &&
this.state != ETradeOfferState.Active &&
this.state != ETradeOfferState.CreatedNeedsConfirmation
) {
throw new Error(
`Cannot set cancelTime for offer #${
this.id
} as it is not active (${
ETradeOfferState[this.state]
}).`
);
}
break;
}
if (!this.id) {
// Offer isn't sent yet. Set in local object cache.
this._tempData[key] = value;
return;
}
// Make sure pollData has offerData set.
pollData.offerData = pollData.offerData || {};
pollData.offerData[this.id] = pollData.offerData[this.id] || {};
pollData.offerData[this.id][key] = value;
// Emit the pollData event
this.manager.emit("pollData", pollData);
}
};
/**
* Get the tradable contents of your trade partner's inventory for a specific context.
* @deprecated Use getPartnerInventoryContents instead
* @param {int} appid
* @param {int} contextid
* @param {function} callback
*/
TradeOffer.prototype.loadPartnerInventory = function (
appid,
contextid,
callback
) {
let items = [];
let req = (start) => {
let qs = {
sessionid: this.manager._community.getSessionID(),
partner: this.partner.toString(),
appid,
contextid,
};
if (start) {
qs.start = start;
}
let refererUrl =
"https://steamcommunity.com/tradeoffer/new/?partner=" +
this.partner.accountid;
if (this._token) {
refererUrl += "&token=" + this._token;
}
this.manager._community.httpRequest(
{
method: "GET",
uri: "https://steamcommunity.com/tradeoffer/new/partnerinventory/",
qs,
headers: {
Referer: refererUrl,
},
json: true,
},
(err, response, body) => {
if (err) {
callback(err);
return;
}
if (response.statusCode != 200) {
callback(new Error("HTTP error " + response.statusCode));
return;
}
if (!body || !body.success) {
callback(new Error("Malformed response"));
return;
}
for (let i in body.rgInventory || {}) {
let item = body.rgInventory[i];
let descKey = [item.classid, item.instanceid || 0].join(
"_"
);
let description = (body.rgDescriptions || {})[descKey];
for (let j in description) {
item[j] = description[j];
}
item.appid = appid;
item.contextid = contextid;
items.push(new EconItem(item));
}
if (body.more) {
req(body.more_start);
} else {
callback(null, items);
}
}
);
};
req();
};
/**
* Get the tradable contents of your trade partner's inventory for a specific context.
* @param {int} appid
* @param {int} contextid
* @param {function} callback
*/
TradeOffer.prototype.getPartnerInventoryContents = function (
appid,
contextid,
callback
) {
this.manager.getUserInventoryContents(
this.partner,
appid,
contextid,
true,
callback
);
};
TradeOffer.prototype.addMyItem = function (item) {
return addItem(item, this, this.itemsToGive);
};
TradeOffer.prototype.addMyItems = function (items) {
var added = 0;
var self = this;
items.forEach(function (item) {
if (self.addMyItem(item)) {
added++;
}
});
return added;
};
TradeOffer.prototype.removeMyItem = function (item) {
if (this.id) {
throw new Error("Cannot remove items from an already-sent offer");
}
for (let i = 0; i < this.itemsToGive.length; i++) {
if (Helpers.itemEquals(this.itemsToGive[i], item)) {
this.itemsToGive.splice(i, 1);
return true;
}
}
return false;
};
TradeOffer.prototype.removeMyItems = function (items) {
var removed = 0;
items.forEach((item) => {
if (this.removeMyItem(item)) {
removed++;
}
});
return removed;
};
TradeOffer.prototype.addTheirItem = function (item) {
return addItem(item, this, this.itemsToReceive);
};
TradeOffer.prototype.addTheirItems = function (items) {
var added = 0;
items.forEach((item) => {
if (this.addTheirItem(item)) {
added++;
}
});
return added;
};
TradeOffer.prototype.removeTheirItem = function (item) {
if (this.id) {
throw new Error("Cannot remove items from an already-sent offer");
}
for (let i = 0; i < this.itemsToReceive.length; i++) {
if (Helpers.itemEquals(this.itemsToReceive[i], item)) {
this.itemsToReceive.splice(i, 1);
return true;
}
}
return false;
};
TradeOffer.prototype.removeTheirItems = function (items) {
var removed = 0;
items.forEach((item) => {
if (this.removeTheirItem(item)) {
removed++;
}
});
return removed;
};
function addItem(details, offer, list) {
if (offer.id) {
throw new Error("Cannot add items to an already-sent offer");
}
if (
typeof details.appid === "undefined" ||
typeof details.contextid === "undefined" ||
(typeof details.assetid === "undefined" &&
typeof details.id === "undefined")
) {
throw new Error("Missing appid, contextid, or assetid parameter");
}
var item = {
id: (details.id || details.assetid).toString(), // always needs to be a string
assetid: (details.assetid || details.id).toString(), // always needs to be a string
appid: parseInt(details.appid, 10), // always needs to be an int
contextid: details.contextid.toString(), // always needs to be a string
amount: parseInt(details.amount || 1, 10), // always needs to be an int
};
if (list.some((tradeItem) => Helpers.itemEquals(tradeItem, item))) {
// Already in trade
return false;
}
list.push(item);
return true;
}
TradeOffer.prototype.send = async function () {
if (this.id)
return [new Error("This offer has already been sent"), undefined];
if (this.itemsToGive.length + this.itemsToReceive.length == 0)
return [new Error("Cannot send an empty trade offer"), , undefined];
function itemMapper(item) {
return {
appid: item.appid,
contextid: item.contextid,
amount: item.amount || 1,
assetid: item.assetid,
};
}
var offerdata = {
newversion: true,
version: this.itemsToGive.length + this.itemsToReceive.length + 1,
me: {
assets: this.itemsToGive.map(itemMapper),
currency: [], // TODO
ready: false,
},
them: {
assets: this.itemsToReceive.map(itemMapper),
currency: [],
ready: false,
},
};
var params = {};
if (this._token) {
params.trade_offer_access_token = this._token;
}
this.manager._pendingOfferSendResponses++;
const [err, response, body] = await promisifyFunction(
this.manager._community.httpRequestPost.bind(this.manager._community),
"https://steamcommunity.com/tradeoffer/new/send",
{
headers: {
referer:
`https://steamcommunity.com/tradeoffer/${
this.id || "new"
}/?partner=${this.partner.accountid}` +
(this._token ? "&token=" + this._token : ""),
},
json: true,
form: {
sessionid: this.manager._community.getSessionID(),
serverid: 1,
partner: this.partner.toString(),
tradeoffermessage: this.message || "",
json_tradeoffer: JSON.stringify(offerdata),
captcha: "",
trade_offer_create_params: JSON.stringify(params),
tradeofferid_countered: this._countering,
},
checkJsonError: false,
checkHttpError: false, // we'll check it ourself. Some trade offer errors return HTTP 500
},
"tradeoffermanager"
);
this.manager._pendingOfferSendResponses--;
if (err) return [err, undefined];
if (response.statusCode != 200) {
if (response.statusCode == 401) {
this.manager._notifySessionExpired(new Error("HTTP error 401"));
return [new Error("HTTP error 401: Not Logged In"), undefined];
}
return [new Error("HTTP error " + response.statusCode), undefined];
}
if (!body)
return [new Error("Malformed JSON response from Steam"), undefined];
if (body && body.strError) return [new Error(body.strError), undefined];
if (body && body.tradeofferid) {
this.id = body.tradeofferid;
this.state = ETradeOfferState.Active;
this.created = new Date();
this.updated = new Date();
this.expires = new Date(Date.now() + 1209600000);
// Set any temporary local data into persistent poll data
for (var i in this._tempData) {
if (this._tempData.hasOwnProperty(i)) {
this.manager.pollData.offerData =
this.manager.pollData.offerData || {};
this.manager.pollData.offerData[this.id] =
this.manager.pollData.offerData[this.id] || {};
this.manager.pollData.offerData[this.id][i] = this._tempData[i];
}
}
delete this._tempData;
}
this.confirmationMethod = EConfirmationMethod.None;
if (body && body.needs_email_confirmation) {
this.state = ETradeOfferState.CreatedNeedsConfirmation;
this.confirmationMethod = EConfirmationMethod.Email;
}
if (body && body.needs_mobile_confirmation) {
this.state = ETradeOfferState.CreatedNeedsConfirmation;
this.confirmationMethod = EConfirmationMethod.MobileApp;
}
this.manager.pollData.sent = this.manager.pollData.sent || {};
this.manager.pollData.sent[this.id] = this.state;
this.manager.emit("pollData", this.manager.pollData);
if (body && this.state == ETradeOfferState.CreatedNeedsConfirmation) {
return [undefined, "pending"];
} else if (body && body.tradeofferid) {
return [undefined, "sent"];
} else {
return [new Error("Unknown response"), undefined];
}
};
TradeOffer.prototype.cancel = TradeOffer.prototype.decline = function (
callback
) {
if (!this.id) {
Helpers.makeAnError(
new Error("Cannot cancel or decline an unsent offer"),
callback
);
return;
}
if (
this.state != ETradeOfferState.Active &&
this.state != ETradeOfferState.CreatedNeedsConfirmation
) {
Helpers.makeAnError(
new Error(
`Offer #${this.id} is not active, so it may not be cancelled or declined`
),
callback
);
return;
}
this.manager._community.httpRequestPost(
`https://steamcommunity.com/tradeoffer/${this.id}/${
this.isOurOffer ? "cancel" : "decline"
}`,
{
headers: {
referer:
`https://steamcommunity.com/tradeoffer/${this.id}/?partner=${this.partner.accountid}` +
(this._token ? "&token=" + this._token : ""),
},
json: true,
form: {
sessionid: this.manager._community.getSessionID(),
},
checkJsonError: false,
checkHttpError: false, // we'll check it ourself. Some trade offer errors return HTTP 500
},
(err, response, body) => {
if (err) {
Helpers.makeAnError(err, callback);
return;
}
if (response.statusCode != 200) {
if (response.statusCode == 401) {
this.manager._community._notifySessionExpired(
new Error("HTTP error 401")
);
Helpers.makeAnError(new Error("Not Logged In"), callback);
return;
}
Helpers.makeAnError(
new Error("HTTP error " + response.statusCode),
callback,
body
);
return;
}
if (!body) {
Helpers.makeAnError(
new Error("Malformed JSON response"),
callback
);
return;
}
if (body && body.strError) {
Helpers.makeAnError(null, callback, body);
return;
}
if (body && body.tradeofferid !== this.id) {
Helpers.makeAnError("Wrong response", callback);
}
this.state = this.isOurOffer
? ETradeOfferState.Canceled
: ETradeOfferState.Declined;
this.updated = new Date();
if (callback) {
callback(null);
}
this.manager.doPoll();
},
"tradeoffermanager"
);
};
TradeOffer.prototype.accept = async function (skipStateUpdate) {
if (typeof skipStateUpdate === "undefined") {
skipStateUpdate = false;
}
if (!this.id) {
return [new Error("Cannot accept an unsent offer"), undefined];
}
if (this.state != ETradeOfferState.Active) {
return [
new Error(`Offer is not active, so it may not be accepted`),
undefined,
];
}
if (this.isOurOffer) {
return [new Error(`Cannot accept our own offer`), undefined];
}
const [err, response, body] = await promisifyFunction(
this.manager._community.httpRequestPost.bind(this.manager._community),
`https://steamcommunity.com/tradeoffer/${this.id}/accept`,
{
headers: {
Referer: `https://steamcommunity.com/tradeoffer/${this.id}/`,
},
json: true,
form: {
sessionid: this.manager._community.getSessionID(),
serverid: 1,
tradeofferid: this.id,
partner: this.partner.toString(),
captcha: "",
},
checkJsonError: false,
checkHttpError: false, // we'll check it ourself. Some trade offer errors return HTTP 500
},
"tradeoffermanager"
);
if (err || response.statusCode != 200) {
if (response && response.statusCode == 403) {
this.manager._community._notifySessionExpired(
new Error("HTTP error 403")
);
return [new Error("Not Logged In"), undefined];
} else {
return [
err || new Error("HTTP error " + response.statusCode),
undefined,
];
}
}
if (!body) {
return [new Error("Malformed JSON response"), undefined];
}
if (body && body.strError) {
return [new Error(body.strError), undefined];
}
this.manager.doPoll();
if (skipStateUpdate) {
if (body.tradeid) {
this.tradeID = body.tradeid;
}
if (body.needs_mobile_confirmation || body.needs_email_confirmation) {
return [undefined, "pending"];
} else {
return [undefined, "accepted"];
}
}
this.update((err) => {
if (err) {
return [
new Error("Cannot load new trade data: " + err.message),
undefined,
];
}
if (
this.confirmationMethod !== null &&
this.confirmationMethod != EConfirmationMethod.None
) {
return [undefined, "pending"];
} else if (this.state == ETradeOfferState.InEscrow) {
return [undefined, "escrow"];
} else if (this.state == ETradeOfferState.Accepted) {
return [undefined, "accepted"];
} else {
return [new Error("Unknown state " + this.state), undefined];
}
});
};
function promisifyFunction(...args) {
const functionName = args.shift();
const source = args.pop();
return new Promise((resolve) => {
functionName(
...args,
(err, ...results) => {
if (err instanceof Error) resolve([err, ...results]);
// Wired Fix, cause one Function is giving me the Results in the error Obj.
if (err) resolve([err, ...results]);
resolve([undefined, ...results]);
},
source
);
});
}
TradeOffer.prototype.update = function (callback) {
this.manager.getOffer(this.id, (err, offer) => {
if (err) {
callback(err);
return;
}
// Clone only the properties that might be out of date from the new TradeOffer onto this one, unless this one is
// glitched. Sometimes Steam is bad and some properties are missing/malformed.
var properties = [
"id",
"state",
"expires",
"created",
"updated",
"escrowEnds",
"confirmationMethod",
"tradeID",
];
for (var i in offer) {
if (
offer.hasOwnProperty(i) &&
typeof offer[i] !== "function" &&
(properties.indexOf(i) != -1 || this.isGlitched())
) {
this[i] = offer[i];
}
}
callback(null);
});
};
TradeOffer.prototype.getReceivedItems = function (getActions, callback) {
if (typeof getActions === "function") {
callback = getActions;
getActions = false;
}
if (!this.id) {
Helpers.makeAnError(
new Error("Cannot request received items on an unsent offer"),
callback
);
return;
}
if (this.state != ETradeOfferState.Accepted) {
Helpers.makeAnError(
new Error(
`Offer #${this.id} is not accepted, cannot request received items`
),
callback
);
return;
}
if (!this.tradeID) {
Helpers.makeAnError(
new Error(
`Offer #${this.id} is accepted, but does not have a trade ID`
),
callback
);
return;
}
// Borrowed from node-steam-trade (https://github.com/seishun/node-steam-trade/blob/master/index.js#L86-L119)
this.manager._community.httpRequestGet(
`https://steamcommunity.com/trade/${this.tradeID}/receipt/`,
(err, response, body) => {
if (err || response.statusCode != 200) {
Helpers.makeAnError(
err || new Error("HTTP error " + response.statusCode),
callback
);
return;
}
var match = body.match(/<div id="error_msg">\s*([^<]+)\s*<\/div>/); // I believe this is now redundant thanks to httpRequestGet
if (match) {
Helpers.makeAnError(new Error(match[1].trim()), callback);
return;
}
var script = body.match(/(var oItem;[\s\S]*)<\/script>/);
if (!script) {
if (body.length < 100 && body.match(/\{"success": ?false}/)) {
Helpers.makeAnError(new Error("Not Logged In"), callback);
return;
}
// no session? or something
Helpers.makeAnError(new Error("Malformed response"), callback);
return;
}
var items = [];
require("vm").runInNewContext(script[1], {
UserYou: null,
BuildHover: function (str, item) {
items.push(item);
},
$: function () {
return { show: function () {} };
},
});
if (items.length == 0 && this.itemsToReceive.length > 0) {
Helpers.makeAnError(
new Error("Data temporarily unavailable; try again later"),
callback
);
return;
}
if (!getActions) {
callback(null, Helpers.processItems(items));
} else {
this.manager._addDescriptions(items, (err, describedItems) => {
if (err) {
callback(null, Helpers.processItems(items)); // welp, we have to just accept what we have
} else {
callback(null, describedItems);
}
});
}
},
"tradeoffermanager"
);
};
TradeOffer.prototype.getExchangeDetails = function (
getDetailsIfFailed,
callback
) {
if (typeof getDetailsIfFailed === "function") {
callback = getDetailsIfFailed;
getDetailsIfFailed = false;
}
if (!this.id) {
Helpers.makeAnError(
new Error("Cannot get trade details for an unsent trade offer"),
callback
);
return;
}
if (!this.tradeID) {
Helpers.makeAnError(
new Error("No trade ID; unable to get trade details"),
callback
);
return;
}
var offer = this;
offer.manager._apiCall(
"GET",
"GetTradeStatus",
1,
{ tradeid: offer.tradeID },
(err, result) => {
if (err) {
Helpers.makeAnError(err, callback);
return;
}
if (!result.response || !result.response.trades) {
Helpers.makeAnError(new Error("Malformed response"), callback);
return;
}
var trade = result.response.trades[0];
if (!trade || trade.tradeid != offer.tradeID) {
Helpers.makeAnError(
new Error(
"Trade not found in GetTradeStatus response; try again later"
),
callback
);
return;
}
if (
!getDetailsIfFailed &&
[
ETradeStatus.Complete,
ETradeStatus.InEscrow,
ETradeStatus.EscrowRollback,
].indexOf(trade.status) == -1
) {
Helpers.makeAnError(
new Error(
"Trade status is " +
(ETradeStatus[trade.status] || trade.status)
),
callback
);
return;
}
if (!offer.manager._language) {
// No need for descriptions
callback(
null,
trade.status,
new Date(trade.time_init * 1000),
trade.assets_received || [],
trade.assets_given || []
);
} else {
offer.manager._requestDescriptions(
(trade.assets_received || []).concat(
trade.assets_given || []
),
(err) => {
if (err) {
callback(err);
return;
}
var received = offer.manager._mapItemsToDescriptions(
null,
null,
trade.assets_received || []
);
var given = offer.manager._mapItemsToDescriptions(
null,
null,
trade.assets_given || []
);
callback(
null,
trade.status,
new Date(trade.time_init * 1000),
received,
given
);
}
);
}
}
);
};
TradeOffer.prototype.getUserDetails = function (callback) {
if (this.id && this.isOurOffer) {
Helpers.makeAnError(
new Error("Cannot get user details for an offer that we sent."),
callback
);
return;
}
if (this.id && this.state != ETradeOfferState.Active) {
Helpers.makeAnError(
new Error(
"Cannot get user details for an offer that is sent and not Active."
),
callback
);
return;
}
var url;
if (this.id) {
url = `https://steamcommunity.com/tradeoffer/${this.id}/`;
} else {
url = `https://steamcommunity.com/tradeoffer/new/?partner=${this.partner.accountid}`;
if (this._token) {
url += "&token=" + this._token;
}
}
this.manager._community.httpRequestGet(url, (err, response, body) => {
if (err || response.statusCode != 200) {
Helpers.makeAnError(
err || new Error("HTTP error " + response.statusCode),
callback
);
return;
}
var script = body.match(
/\n\W*<script type="text\/javascript">\W*\r?\n?(\W*var g_rgAppContextData[\s\S]*)<\/script>/
);
if (!script) {
Helpers.makeAnError(new Error("Malformed response"), callback);
return;
}
script = script[1];
var pos = script.indexOf("</script>");
if (pos != -1) {
script = script.substring(0, pos);
}
// Run this script in a VM
var vmContext = require("vm").createContext({
UserYou: {
SetProfileURL: function () {},
SetSteamId: function () {},
},
UserThem: {
SetProfileURL: function () {},
SetSteamId: function () {},
},
$J: function () {},
});
require("vm").runInContext(script, vmContext);
var me = {
personaName: vmContext.g_strYourPersonaName,
contexts: vmContext.g_rgAppContextData,
};
var them = {
personaName: vmContext.g_strTradePartnerPersonaName,
contexts: vmContext.g_rgPartnerAppContextData,
probation: vmContext.g_bTradePartnerProbation,
};
// Escrow
var myEscrow = body.match(/var g_daysMyEscrow = (\d+);/);
var theirEscrow = body.match(/var g_daysTheirEscrow = (\d+);/);
if (myEscrow && theirEscrow) {
me.escrowDays = parseInt(myEscrow[1], 10);
them.escrowDays = parseInt(theirEscrow[1], 10);
}
// Avatars
var myAvatar = body.match(
new RegExp(
'<img src="([^"]+)"( alt="[^"]*")? data-miniprofile="' +
this.manager.steamID.accountid +
'">'
)
);
var theirAvatar = body.match(
new RegExp(
'<img src="([^"]+)"( alt="[^"]*")? data-miniprofile="' +
this.partner.accountid +
'">'
)
);
if (myAvatar) {
me.avatarIcon = myAvatar[1];
me.avatarMedium = myAvatar[1].replace(".jpg", "_medium.jpg");
me.avatarFull = myAvatar[1].replace(".jpg", "_full.jpg");
}
if (theirAvatar) {
them.avatarIcon = theirAvatar[1];
them.avatarMedium = theirAvatar[1].replace(".jpg", "_medium.jpg");
them.avatarFull = theirAvatar[1].replace(".jpg", "_full.jpg");
}
callback(null, me, them);
});
};
TradeOffer.prototype.counter = function () {
if (this.state != ETradeOfferState.Active) {
throw new Error("Cannot counter a non-active offer.");
}
var offer = this.duplicate();
offer._countering = this.id;
return offer;
};
TradeOffer.prototype.duplicate = function () {
var offer = new TradeOffer(this.manager, this.partner, this._token);
offer.itemsToGive = this.itemsToGive.slice();
offer.itemsToReceive = this.itemsToReceive.slice();
offer.isOurOffer = true;
offer.fromRealTimeTrade = false;
return offer;
};
TradeOffer.prototype.setMessage = function (message) {
if (this.id) {
throw new Error("Cannot set message in an already-sent offer");
}
this.message = message.toString().substring(0, 128);
};
TradeOffer.prototype.setToken = function (token) {
if (this.id) {
throw new Error("Cannot set token in an already-sent offer");
}
this._token = token;
};
module.exports = TradeOffer;