Meteor.wrapAsync and NPM package (Steam)


#1

Hi!

I’m trying to build a simple STEAM bot for my Meteor application but I hit a roadblock as I can’t figure out how to correctly use Meteor.wrapAsync in order to correctly display the results on the front.

My code looks like this:

1. SERVER

import SteamUser from 'steam-user';
import SteamTotp from 'steam-totp';
import SteamCommunity from 'steamcommunity';
import TradeOfferManager from 'steam-tradeoffer-manager';

tradeBot1 = Meteor.settings.SteamTradeBot1;

// Vars
const client = new SteamUser();

const community = new SteamCommunity();

const manager = new TradeOfferManager({
	steam: client,
	community: community,
	language: 'en'
});

const logOnOptionsBot1 = {
	accountName: tradeBot1.username,
	password: tradeBot1.password,
	twoFactorCode: SteamTotp.generateAuthCode(tradeBot1.sharedSecret)
};

// Bot login
client.logOn(logOnOptionsBot1);

// After login set bot as online
client.on('loggedOn', Meteor.bindEnvironment(function(){
	console.log('Steam Trade Bot #1 online.');
	client.setPersona(SteamUser.Steam.EPersonaState.Online);
}));

// Set cookies
client.on('webSession', Meteor.bindEnvironment(function(sessionid, cookies){
	manager.setCookies(cookies);
	community.setCookies(cookies);
}));


Meteor.methods({
	"sendTradeOffer": function(tradeUrl, winId, uid){
		try {
			var assetId = "1111111111";
			var appid = "730";
			var itemName = "itemnamexxxxxxx";

			// Create new offer
			// https://github.com/DoctorMcKay/node-steam-tradeoffer-manager/wiki/TradeOfferManager
			const offer = manager.createOffer(tradeUrl);

			// Add item to offer
			offer.addMyItem({
				'assetid': assetId,
				'appid': appid,
				'contextid': 2,
				'amount': 1
			});

			// Set custom message
			offer.setMessage(`Congrats! You got "${itemName}"! Ref: "` + winId + `"`);

			// Send offer
			offer.send(Meteor.bindEnvironment(function(err, status){
				if (err) {
					console.log(err);
					return err;
				} else {
					console.log(`Sent offer. Status: ${status}. Waiting for auto confirmation ...`);

					// Set a 5 second delay before confirmation
					Meteor.setTimeout(function(){
						community.acceptConfirmationForObject(tradeBot1.indentitySecret, offer.id, Meteor.bindEnvironment(function(err){

							if (err) {
								console.log(err);
								return err;
							} else {
							    console.log("Offer confirmed.");
								console.log("Sent Steam item with ID: " + assetId);

								// Update data in mongo
								// .....

								// Return something to the front ???
								var outcome = {
									message: "Trade offer sent!"
								}

								return outcome;

							}

						}));
					}, 5000);
				}

			}));

		} catch(error){
			console.log(error);
			return error;
		}
	}

});

2. FRONT


'submit form#send-trade': function(event, t){
    event.preventDefault();

	var tradeUrl = $("#user-trade-url").val();
	var winId = "winidxxxxxxx";
	var uid = "uidxxxxxxx";

	Meteor.call("sendTradeOffer", tradeUrl, winId, uid, function(error, result){
		if (error){
			console.log(error);
		} else {
			console.log(result);
			
		}
	});
 }

What this should do is:

  • Bot login - working
  • Set bot status to online after login - working
  • User enters their tradeUrl on the front - working
  • Meteor method sendTradeOffer is executed - working
  • Offer is sent and confirmed - working
  • Return error or result in the front - not working

Console.logs on server works just fine but I can’t return anything to the front, it’s always undefined.

I’ve searched around quite a bit this past couple of days and from my understanding I should be using Meteor.wrapAsync to correctly return either an error or a result but I simply can’t wrap my head around it. How would Meteor.wrapAsync would apply in my case?

Any help is much appreciated.

Many thanks!


#2

I’d probably try rewriting your method to something like this:

Meteor.methods({
  sendTradeOffer(tradeUrl, winId, uid) {
    const offerSend = Meteor.wrapAsync(offer.send, offer);
    const communityAcceptConfirmationForObject = Meteor.wrapAsync(community.acceptConfirmationForObject, community);
    try {
      const assetId = '1111111111';
      const appid = '730';
      const itemName = 'itemnamexxxxxxx';

      // Create new offer
      // https://github.com/DoctorMcKay/node-steam-tradeoffer-manager/wiki/TradeOfferManager
      const offer = manager.createOffer(tradeUrl);

      // Add item to offer
      offer.addMyItem({
        assetId,
        appid,
        contextid: 2,
        amount: 1
      });

      // Set custom message
      offer.setMessage(`Congrats! You got ${itemName}! Ref: ${winId}`);

      // Send offer
      const status = offerSend(); // will throw error on failure
      console.log(`Sent offer. Status: ${status}. Waiting for auto confirmation ...`);

      // Set a 5 second delay before confirmation
      Meteor._sleepForMs(5000); // Fiber based inline sleep
      communityAcceptConfirmationForObject(tradeBot1.indentitySecret, offer.id);  // will throw error on failure

      console.log('Offer confirmed.');
      console.log('Sent Steam item with ID: ', assetId);

      // Update data in mongo
      // .....

      // Return something to the front ???
      const outcome = {
        message: 'Trade offer sent!'
      }

      return outcome;

    } catch (err) {
      throw new Meteor.Error('oops', err.message); // return error to client
    }
  },
});

Notes:

  • All callbacks removed to use Fibers. That means the code works sequentially and the return object is available exactly where it appears to be.
  • Meteor.wrapAsync used for the two calls which seem to need it.
  • Tidied up for ES6 (ESLint is a harsh mistress!)
  • I don’t know the steam API, so this is untested and may still not work.

#3

Legend, thank you! This makes much more sense now. I’ve been reading your Meteor.wrapAsync replies here on the forums and they were always spot on - thank you for the help and the quick reply.

If you don’t mind I would like to ask you another question. You’ve mentioned that these will throw and error on failure:

const status = offerSend(); // will throw error on failure
communityAcceptConfirmationForObject(tradeBot1.indentitySecret, offer.id);  // will throw error on failure

How can I access the errors and return it to front? I assume I should change this part?

// Return something to the front ???
const outcome = {
	message: 'Trade offer sent!'
}

return outcome;

Finally a couple of notes for anyone who comes across this particular example. To make it work:

  1. I moved the create offer to the top of the Meteor method:
// Create new offer
const offer = manager.createOffer(tradeUrl);
// Meter.wrapAsync
const offerSend = Meteor.wrapAsync(offer.send, offer);
const communityAcceptConfirmationForObject = Meteor.wrapAsync(community.acceptConfirmationForObject, community);

[...]

  1. When adding the item to the offer I’ve changed slightly the syntax, so it becomes:
// Add item to offer
offer.addMyItem({
	'assetid': assetId,
	'appid': appid,
	'contextid': 2,
	'amount': 1,
	'cancelTime': newDate
});

With these small adjustments this will work as it should.


#4

Just wrap with try/catch at the point they’re actually used: although you already have an all-enclosing try/catch which will pick these up anyway. Use throw new Meteor.Error() to return to the client.


#5

Oh I see, so there is no additional wrapping required as the errors will be picked up by main try / catch bit. That makes sense. I’m going to go through this whole thing again and give it another test. Many thank again for your help and for clarifying things, much appreciated!