I am creating a simple tictactoe terminal game in JS. I use the variable named player1Input to get user prompt. If the prompt is not equal to "X", I call the function again to make sure the user input the right input. If I enter the wrong input multiple times, the function (player1Game) ends up being called multiple times instead of once. How do I get it to just be called once. I put a snippet of my code at the bottom.I commented the part of the code that makes the function run twice
function player1Game () {
let player1Input = prompt(`${player1Name.charAt(0).toUpperCase() + player1Name.slice(1) } please enter \"x\": `);
//Create an error that responds if player 1 does not type x
if (player1Input !== "x") {
console.log("Please make sure you type in x")
player1Game();
//the function runs multiple times here instead of once.
// How do I get it to run only once.
}
You still aren't showing the whole context here, but perhaps you just need to return after you call it again so that the rest of the function doesn't execute when the function doesn't meet the input requirements:
function player1Game () {
let player1Input = prompt(`${player1Name.charAt(0).toUpperCase() + player1Name.slice(1) } please enter \"x\": `);
//Create an error that responds if player 1 does not type x
if (player1Input !== "x") {
console.log("Please make sure you type in x")
player1Game();
// return so it doesn't execute any more of the function
return;
}
// code here will only get called if the `player1Input`
// meets the above critera
// Rest of your code here...
}
Or, you could use an if/else:
function player1Game () {
let player1Input = prompt(`${player1Name.charAt(0).toUpperCase() + player1Name.slice(1) } please enter \"x\": `);
//Create an error that responds if player 1 does not type x
if (player1Input !== "x") {
console.log("Please make sure you type in x")
player1Game();
} else {
// code here will only get called if the `player1Input`
// meets the above critera
// Rest of your code here...
}
}
FYI, there's nothing special here. This is just normal function control flow in Javascript. If you want no more of the function to execution then insert a return statement. Or protect branches of code with an if/else so a condition will control what code executes.
I'm using Firebase Functions with the "Spark Plan" (free). This is part of my function:
return query.once("value").then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var childData = childSnapshot.val();
if (childData.displayName === ally) {
existAlly = true;
console.log('uid: '+uid)
var ref = admin.database().ref('users/'+uid).transaction(function (current_value) {
console.log('current_value: '+uid)
current_value.mainAlly = ally;
current_value.coins = (current_value.coins || 0) + 10
return current_value;
}).then(() => {
console.log('New Ally added');
return true;
});
...
Here the logs, you can see "'current_value: null'
But, ss you can see in the next picture, the "ref" is correct:
So, is it a billing issue? The "admin.database()" stops working after a while? Or is it something else?
Thanks!
EDIT: I just did another test, and now the error is in "coins", with the same code:
Everything is working as expected. When working with transactions, you can expect that your handler function will get called the first time with null (which you will have to check for), then again with the actual contents of the database. You should review the documentation, and pay special attention to the note that says:
Transaction Function is Called Multiple Times
Your transaction handler is called multiple times and must be able to
handle null data. Even if there is existing data in your database it
may not be locally cached when the transaction function is run.
I’m brand new to programming and I’m currently working on a MySQL database. I’m using Visual Studio Code for all of my JavaScript, HTML, and CSS files.
I have a JavaScript server file that is giving me issues. Our professor gave us his code for the server JavaScript file (which is posted below), his client JavaScript file (which is named contacts.js), and his HTML file.
He told us to open the server JavaScript file, open a terminal and type: node contacts.js. However, doing this gives me error messages that say that the document is not defined.
Occasionally, I'll even get "module not found" errors.
We just did a similar project last week and the terminal worked just fine with a similar node.js command, but I’m running into issues now and don’t know what to do. Hours on Google haven’t helped at all and my professor can’t be contacted for the entire week.
I’m not sure how to get beyond this “document not defined” error. Any help would be appreciated.
Below is the server JavaScript file:
// The following statements are for database connection and queries
var mysql = require('mysql'); // use the msql libraries. Must use 'npm install msyql --save' before using
var bodyParser = require('body-parser'); // use the body-parser library for JSON use. Must 'npm install body-parser --save'
// Set up the SQL connection to the MYSQL database. This will all need to match what you set up in your DB
var connection = mysql.createConnection({
host:'localhost',
user: 'mike',
password: '********',
database: 'contacts'
});
// do the actual connecting by calling the connect method and log the result
connection.connect();
console.log("After connection to DB established in server, setting up web server");
//The following are for web server setup - we are using the express library that makes this all pretty easy
const express = require('express'); // use express library. must use 'npm install express --save'
const cors = require('cors'); // use cors library. must use 'npm install cors --save'
const app = express(); // get the express application object
const path = require('path'); // use the path library for managing paths. must use 'npm install path --save'
const port = 3000; // constant for the port we're using.
// set up the use of JSON url-encoding. Allows us to put all the arguments in the url
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
// We want to also serve static pages. This command sets that up. In my case, I created a subdirectory called 'public'
// and put the main html page (contacts.html), the javascript client file (contacts.js) and the CSS file (contacts.css)
// in that directory, and therefore I can get everything I need by just goint to (localhost:3000) and it all just works
app.use(express.static('public'));
// This is our main save handler (express calls these 'middleware'). The request coming from the client is a post
// and all the parameters/values are in the query object in the request object (req).
// All the field names here must match the names in the form (name='blah') which we use when we craft the request
// in the javascript saveContact().
// NOTE: we have to use the cors() method to make this all work. Look up cors (Cross-Origin Resource Sharing) to learn about it
app.post('/save', cors(), function (req, res) {
console.log("trying to save contact (post)"); // Log what we're doing
console.log(req); // log the actual request
var curId = req.query.Id; // Get the Id from the query object
var firstName = req.query.fname; // get the fname from the query object
var lastName = req.query.lname; // get the lname from the query object
var age = req.query.age; // etc. etc. etc.
var phone = req.query.phone;
var email = req.query.email;
// We can use the same handler for both cases of saving information:
// 1)we INSERT the new contact in the DB - the Id is 0 in this case
// 2)we UPDATE an existing contact in the DB - the Id is the correct Id for the contact we're updating
// Here we're crafting the appropriate SQL statements using the values above - either INSERT or UPDATE
if (curId > 0) {
var sql = `UPDATE contacts SET fname = '${firstName}', lname = '${lastName}', age = ${age}, phone = '${phone}', email = '${email}' WHERE Id = ${curId}`;
} else {
var sql = `INSERT INTO contacts (fname, lname, age, phone, email) VALUES ('${firstName}', '${lastName}', '${age}', '${phone}', '${email}')`;
}
// Here we're creating the query and the callback function for when we get a response from the DB asynchronously
// This same method executes the SQL call to the database connection we established earlier (above)
connection.query(sql, function (err, result) {
console.log("Trying to save contact into DB"); // log what we're doing
if (err) throw err; // If we get an error, send the error along
console.log(result.affectedRows + " record(s) saved");
res.status(201).send(result); // set the status code (201 = successful add) and send it
console.log(`result of post is: ${result}`); // log the result
console.log(result);
});
});
// This handler is for deleting a user given a valid Id.
// NOTE: we have to use the cors() method to make this all work. Look up cors (Cross-Origin Resource Sharing) to learn about it
app.post('/delete', cors(), function (req, res) {
console.log("trying to delete contact (post)"); // log what we're doing
console.log(req); // log the actual request we received
var curId = req.query.Id; // get the Id from the query object
console.log(curId); // log the Id
// As long as we have a valid Id (in variable curId), we craft the sql statement and execute the query
// all the SQL commands are asynchronous so we provide a callback function
if (curId > 0) {
var sql = `DELETE FROM contacts WHERE Id = ${curId}`; // This is the right SQL statement
connection.query(sql, function (err, result) {
console.log("Trying to delete contact from DB"); // log what we're trying to do
if (err) throw err; // if we get an error, pass it along to the client
console.log(result.affectedRows + " record(s) deleted");
res.status(200).send(result); // otherwise set the status to success (200) and send the result to the client
console.log(`result of post is:`); // log the result
console.log(result);
});
}
});
// THis is our static GET Handler if you just open a browser and type in 'http://localhost:3000'. the '/' means root
// and so this our default page (often called 'index.html' but in this case it's our 'contacts.html')
// Simply send the contacts.html page by getting the default path (wherever we have this javascript file)
app.get('/', function (req, res) {
console.log(req.params);
res.sendFile(path.join(__dirname + '\\contacts.html'));
});
// This is our handler for getting the full list of contacts
// NOTE: we have to use the cors() method to make this all work. Look up cors (Cross-Origin Resource Sharing) to learn about it
app.get('/list', cors(), function (req, res) {
console.log(`inside list GET function, req object is ${req}`);
console.log(req);
// Craft the simple select statement that just gets everything in the contacts table
var sql = `SELECT * FROM contacts`;
//Create the query and execute it, sending the appropriate result back to the client
connection.query(sql, function (err, result) {
console.log(`Trying to get list from DB - result is ${result}`);
console.log(`Inside get list - result first row is ${result[0]}`);
if (err) throw err; // if we get an error, pass it along to the client
res.send(result); // simply send the result of the query to the client.
console.log(`result of GET to list is: ${result}`);
});
});
// This is what actually starts the express server, listening on the port constant we defined at the beginning
// of the file (in this case I'm using 3000) and logging what we're doing.
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
Below is the client JavaScript file:
// This JavaScript file is in support of the contacts application.
// Users can see and manage all their contacts, where a contact is [Id, firstname, lastname, age, phone, email]
// There are functions to manage the http interactions with the server backend
// and to manage the screen/user experience
// This is a globally available array of contacts we get back from the server. Set it initially to an empty array
document.contactList = [];
// This is the function queries the server (using an HTTP GET) to get the list of contacts
// We save the contacts to a globable variable in the document (contactList) and we fill
// both the table at the bottom of the document and a drop-down list used for management
// both of those functionality are function calls ('fillContactTable()' and 'fillContactSelect()')
function getContacts() {
console.log(`Getting contact list from server`); // log what we're doing
var xhttp = new XMLHttpRequest(); // create a variable for HTTP protocol
xhttp.onreadystatechange = function() { // callback function for when a response occurs
console.log(this.responseText); // log the response
// readyState is the XMLHttpRequest state that means we're done. Status is what is returned from the server
// a status code anywhere in the 200's is success. SO if we're done and get a success return code, then we're good!
if (this.readyState == 4 && (this.status >= 200 && this.status < 300)) {
console.log(`Got the contact list successfully`); // Log that we're good
document.contactList = this.responseText; // The response is the actual list of contacts.Set to globabl var in document
fillContactTable(); // Fill the table
fillContactSelect(); // Fill the drop down list (select element)
} else {
console.log(`failed to get contact list`); // Log failure if that's what we got
}
}
// We've set the callback function that handles the result. This is the actual setting up the http request (the open method)
// and the actual sending of the http request (send method)
xhttp.open("GET", "http://localhost:3000/list", true);
xhttp.send();
}
// Given an Id of a contact, get the rest of the contact information and return it
// The pLocal parater is in case we want to get the contact information from the server instead of getting it from
// the global variable (document.contactList). Default is to be local.
function getContactById(pId, pLocal = true) {
console.log(`Getting contact by ID = ${pId}`); // log what we're doing
if (pLocal) { // If we're local, get the data from document.contactList
var contactsJSON = JSON.parse(document.contactList); // parse the contactList into JSON format - easier to deal with
// Loop through all the contacts in the JSON formated list of contacts to look for the one we want (by Id)
for (loopIndex = 0; loopIndex < contactsJSON.length; loopIndex++) {
if (contactsJSON[loopIndex].Id == pId) { // if Id's match, we're good but log what we found
console.log(`Found contact in getContactById. Index = ${loopIndex}`);
console.log(contactsJSON[loopIndex]);
return contactsJSON[loopIndex]; // Return the found contact
}
}
console.log(`Did not find the contact in getContactById`); // log the fact that we didn't find it and return null
return null;
// For now, if we're not local just return null. Will add the code to get the data from the server later
} else {
return null;
}
}
// Simple function that just clears the form that we use for showing, creating new, and updating contacts
function clearEditForm() {
console.log("clearing the contact form"); // Log what we're doing
// Set all the values to empty (or 0 for the Id - that has to be a number)
document.getElementById('contact_id').value = 0;
document.getElementById('contact_fname').value = "";
document.getElementById('contact_lname').value = "";
document.getElementById('contact_age').value = "";
document.getElementById('contact_phone').value = "";
document.getElementById('contact_email').value = "";
// Now control the user experience. Hide the ID fields and change the name of the button to "Insert"
document.getElementById('contact_id').hidden = true;
document.getElementById('contact_id_label').hidden = true;
document.getElementById('save_button').innerHTML = "Insert Contact";
document.getElementById('save_button').name = "Insert Contact";
}
// Main function that saves the contact form. We have two cases to deal with:
// 1) We're inserting a new contact. In that case, the Id (curId below) will be 0
// 2) We're updating an exesting contact. In that case, the Id will NOT be 0
// If the ID is not a number >= 0, we have a problem so we don't do anything
function saveContact() {
console.log("Attempting to save contact"); // Log what we're doing
// Get all the values from the elements in the form by name.
var curId = document.getElementById('contact_id').value;
var curFName = document.getElementById('contact_fname').value;
var curLName = document.getElementById('contact_lname').value;
var curAge = document.getElementById('contact_age').value;
var curPhone = document.getElementById('contact_phone').value;
var curEmail = document.getElementById('contact_email').value;
console.log(`Trying to save contact in saveContact. Id = ${curId}`);
// As long as we have a valid Id (number at least 0) we'll make the http request (a POST)
if (curId >= 0) {
var xhttp = new XMLHttpRequest(); // Create a new HTTP object and put it in xHTTP variable
// As in all of our interactions implementing http, we supply a callback function for when we actually get a response
// Remember, all http request/responses should be asynchronous, and so we have to use callbacks
xhttp.onreadystatechange = function() {
console.log(this.responseText); // log what's happening
// If readyState shows we're done (value == 4) and status code is in the 200's we got a success response
if (this.readyState == 4 && (this.status >= 200 && this.status < 300)) {
console.log(`saved the contact successfully`); // Log our success
getContacts(); // Re-get our contact list since it has changed
} else {
console.log(`failed to save contact `); // Log our failure response
console.log(this.status); // log the actual status code
console.log(this.responseText); // log the actual response text
}
}
// Here we're crafting the http POST request with all the parameters urlencoded. Look up url encoding to understand it
// As usual, the open method is used to set up the call, and the send method actually sends the request
xhttp.open("POST", `http://localhost:3000/save?Id=${curId}&fname=${curFName}&lname=${curLName}&age=${curAge}&phone=${curPhone}&email=${curEmail}`, true);
xhttp.send();
}
}
// Function to delete a contact by creating the right server http request (a POST)
// We'll pass the Id of the contact we want to delete in the url (url-encoded)
// We'll get the name of the contact to be deleted and prompt the user to verify that they want to really delete the contact
// look up the window method 'confirm' to understand how that works
function deleteContact() {
console.log("Attempting to delete contact"); // Log what we're doing
var contactList = document.getElementById('contacts_list'); // get the drop-down select element in the form
var curId = contactList.value; // get the value of the form, which will be an Id of the contact to be deleted
var curIndex = contactList.selectedIndex; // We need the index of the option chosen to get the name for prompting the user
var curName = contactList.options[curIndex].text; // get the name from the option list based on the index
console.log(`Trying to verify delete. curid = ${curId}, curIndex = ${curIndex}, and curName = ${curName}`);
// Prompt the user to confirm using the window.confirm method. If they say ok, confirm returns true
// if they say cancel, confirm returns false. We're checking for the false, thus the not (!) at the beginning of the condition
if (!confirm(`Are you sure you want to delete contact: ${curName}?`)) {
return; // If we're here they said cancel, so just return out of here
}
console.log(`Trying to delete contact in fillEditForm. Id = ${curId}`);
if (curId.length > 0) { // Make sure we have a good Id
var xhttp = new XMLHttpRequest(); // create the http object
// Here's our callback for the asynchronous return. As long as we get a good status code, we update the form appropriately
xhttp.onreadystatechange = function() {
console.log(this.responseText); // log the actual response
// readyState 4 means we're done, and status in the 200's means success, so re-get the contact list from the server
if (this.readyState == 4 && (this.status >= 200 && this.status < 300)) {
console.log(`deleted the contact successfully`);
clearEditForm(); // clear the form since we deleted the contact
getContacts(); // get the contacts from the server
} else {
console.log(`failed to delete contact list`);
}
}
// Create the actual request and send it.
xhttp.open("POST", `http://localhost:3000/delete?Id=${curId}`, true);
xhttp.send();
}
}
// Simple function to clear the table element. We delete all the rows backwards. Make the function generic by allowing
// a parameter (pTable) which is the name of the table to be reset if there is more than one on the form
function tableDeleteRows(pTable = "") {
var curTable;
if (pTable.length == 0) {
curTable = document.getElementById('contacts_table');
} else {
curTable = document.getElementById(pTable);
}
// We start at the end of the rows (rows[length-1]), deleting backwards until we delete all of them
for (i = curTable.rows.length - 1; i >= 0; i--) {
curTable.deleteRow(i);
}
}
// Simple function to clear a drop-down select element. We delete all the rows backwards. Make the function generic by allowing
// a parameter (pSelect) which is the name of the select element to be reset if there is more than one on the form
function selectDeleteOptions(pSelect = "") {
var curSelect;
if (pSelect.length == 0) {
curSelect = document.getElementById('contacts_list');
} else {
curSelect = document.getElementById(pSelect);
}
// Go backward from the end of the list of options in the select, removing them until we remove all of them
for (i = curSelect.length - 1; i >= 0; i--) {
curSelect.remove(i);
}
}
// Assuming we have a contact chosen in the drop-down select element, fill the edit form with all the values for that contact
function fillEditForm() {
console.log("filling the contact form"); // Log what we're doing
var contactList = document.getElementById('contacts_list'); // get the drop-down list
var curId = contactList.value; // the selected element Id is the value of the list
console.log(`Trying to find contact in fillEditForm. Id = ${curId}`);
var curContact = getContactById(curId); // Get the whole contact by calling the function
console.log(curContact); // log the contact we're using to fill the form
// As long as we have a good contact, we fill the form
if (curContact != null) {
document.getElementById('contact_id').value = curContact['Id'];
document.getElementById('contact_fname').value = curContact['fname'];
document.getElementById('contact_lname').value = curContact['lname'];
document.getElementById('contact_age').value = curContact['age'];
document.getElementById('contact_phone').value = curContact['phone'];
document.getElementById('contact_email').value = curContact['email'];
}
// after we fill the form, we set elements appropriate to things like update and delete instead of add new
document.getElementById('contact_id').hidden = false;
document.getElementById('contact_id').disabled = true;
document.getElementById('contact_id_label').hidden = false;
document.getElementById('save_button').innerHTML = "Update Contact";
document.getElementById('save_button').name = "Update Contact";
}
// This function fills the table at the bottom of the document with all the contacts and all the information
function fillContactTable() {
console.log("Filling the contacts table in the form"); // Log what we're doing
tableDeleteRows("contacts_table"); // Reset the table
// if we don't have anything in the global contact list - forget it and return
if (document.contactList.length == 0) {
console.log("the contact list/array is empty!");
return;
}
// We have contacts in the global array, so first parse the array into JSON and process it
var contactsJSON = JSON.parse(document.contactList);
var properties = ['Id', 'fname', 'lname', 'age', 'phone', 'email']; // we need the property names
var tr, curRow; // variables for table properties
var contactTable = document.getElementById("contacts_table"); // get the table element
// cycle through the rows in the contacts array
for (var rowIndex = 0; rowIndex < contactsJSON.length; rowIndex++) {
console.log(`Creating table rows, rowindex is ${rowIndex}`);
tr = document.createElement('tr'); //create a new table row element
curRow = contactsJSON[rowIndex]; // get the current row from the array
console.log(curRow); // log the data in the current row
// Cycle through the columns - defined in the property array above and add column elements to the row in the table
for (var i = 0; i < properties.length; i++) {
console.log(`Creating table columns for row ${i}, property is ${properties[i]} value is ${curRow[properties[i]]}`);
var td = document.createElement('td'); // create a data element for the column
td.appendChild(document.createTextNode(curRow[properties[i]])); //Append the property data to the new data element
tr.appendChild(td); // append the new data element to the row element
}
contactTable.appendChild(tr); // append the row element to the table element
}
console.log("Finished procesing contacts list");
}
// Fill the drop-down select. First reset the select (removing all options), then recreate it
function fillContactSelect() {
console.log("Filling the contacts drop down select in the form"); // log what we're doing
selectDeleteOptions("contacts_list"); // Reset the select element clearing all options
// if we don't have anything in the global contact list - forget it and return
if (document.contactList.length == 0) {
console.log("the contact list/array is empty!");
return;
}
// We have contacts in the global array, so first parse the array into JSON and process it
var contactsJSON = JSON.parse(document.contactList);
var properties = ['Id', 'fname', 'lname']; // only need Id, fname, lname for drop down
var option, curRow; // variables for table properties
var contact = document.getElementById("contacts_list"); // get the select drop down element
// cycle through the rows in the contacts array
for (var rowIndex = 0; rowIndex < contactsJSON.length; rowIndex++) {
console.log(`Creating select items, rowindex is ${rowIndex}`);
option = document.createElement('option'); //create a select option element
curRow = contactsJSON[rowIndex]; // get the current row from the array
console.log(curRow); // log the data in the current row
// the value of this option will be the id, since that's what we'll use to get a contact. The text is fname + lname
option.value = curRow["Id"];
option.appendChild(document.createTextNode(`${curRow['fname']} ${curRow['lname']}`));
contact.appendChild(option); // append the option to the select element
}
console.log("Finished procesing contacts list");
}
// When the windo first loads, get the list of contacts which will also fill the table and drop down list
window.onload = function() {
getContacts();
};
You are running the wrong JS file. You want to do:
node contactserver.js
At the moment you are running contacts.js, which is client-side code.
To expand on this, you're seeing that error because document is a global variable available in browsers, but not in Node.js. Frontend code designed to run in a browser often relies in browser APIs that simply don't exist on a server, so attempting to run a client-side only file in a server environment will throw errors when it can't find global objects that only exist in a browser.
(Props to #Jon Church for the explanation from the comments below)
Turns out that contacts.js was the wrong file. Typing into the terminal: node contactsserver.js was the solution.
I'm using this Gumroad-API npm package in order to fetch data from an external service (Gumroad). Unfortunately, it seems to use a .then() construct which can get a little unwieldy as you will find out below:
This is my meteor method:
Meteor.methods({
fetchGumroadData: () => {
const Gumroad = Meteor.npmRequire('gumroad-api');
let gumroad = new Gumroad({ token: Meteor.settings.gumroadAccessKey });
let before = "2099-12-04";
let after = "2014-12-04";
let page = 1;
let sales = [];
// Recursively defined to continue fetching the next page if it exists
let doThisAfterResponse = (response) => {
sales.push(response.sales);
if (response.next_page_url) {
page = page + 1;
gumroad.listSales(after, before, page).then(doThisAfterResponse);
} else {
let finalArray = R.unnest(sales);
console.log('result array length: ' + finalArray.length);
Meteor.call('insertSales', finalArray);
console.log('FINISHED');
}
}
gumroad.listSales(after, before, page).then(doThisAfterResponse); // run
}
});
Since the NPM package exposes the Gumorad API using something like this:
gumroad.listSales(after, before, page).then(callback)
I decided to do it recursively in order to grab all pages of data.
Let me try to re-cap what is happening here:
The journey starts on the last line of the code shown above.
The initial page is fetched, and doThisAfterResponse() is run for the first time.
We first dump the returned data into our sales array, and then we check if the response has given us a link to the next page (as an indication as to whether or not we're on the final page).
If so, we increment our page count and we make the API call again with the same function to handle the response again.
If not, this means we're at our final page. Now it's time to format the data using R.unnest and finally insert the finalArray of data into our database.
But a funny thing happens here. The entire execution halts at the Meteor.call() and I don't even get an error output to the server logs.
I even tried switching out the Meteor.call() for a simple: Sales.insert({text: 'testing'}) but the exact same behaviour is observed.
What I really need to do is to fetch the information and then store it into the database on the server. How can I make that happen?
EDIT: Please also see this other (much more simplified) SO question I made:
Calling a Meteor Method inside a Promise Callback [Halting w/o Error]
I ended up ditching the NPM package and writing my own API call. I could never figure out how to make my call inside the .then(). Here's the code:
fetchGumroadData: () => {
let sales = [];
const fetchData = (page = 1) => {
let options = {
data: {
access_token: Meteor.settings.gumroadAccessKey,
before: '2099-12-04',
after: '2014-12-04',
page: page,
}
};
HTTP.call('GET', 'https://api.gumroad.com/v2/sales', options, (err,res) => {
if (err) { // API call failed
console.log(err);
throw err;
} else { // API call successful
sales.push(...res.data.sales);
res.data.next_page_url ? fetchData(page + 1) : Meteor.call('addSalesFromAPI', sales);
}
});
};
fetchData(); // run the function to fetch data recursively
}
Something about my use of chrome.hid.send seems to be leaving the bus in a bad state. I consistently can NOT get my second usage of the API call to work. Sometimes, it will also fail on the first usage. WITH THE EXACT SAME CODE, I can come back and try a while later (maybe 10min) and the first send will work.
The device I'm working with does not return a response to all messages sent to it. The test message for example, is just a dummy message that is ignored by the device. I've tested this both on a mac and a PC. My call stack depth is 2 at this point in my application (literally first one is kicked off by a button click and then a setTimeout calls the same method 5s later).
I've testing sending buffers of length 64Bytes as well as 58Bytes. The properties from the HidDeviceInfo object read "maxInputReportSize":64,"maxOutputReportSize":64
Params on first usage:
Params on second usage:
I really can't identify how I'm using the API incorrectly. When messages do succeed, I can see them on the device side.
// Transmits the given data
//
// #param[in] outData, The data to send as an ArrayBuffer
// #param[in] onTxCompleted, The method called on completion of the outgoing transfer. The return
// code is passed as a string.
// #param[in] onRxCompleted, The method called on completion of the incoming transfer. The return
// code is passed as a string along with the response as an ArrayBuffer.
send: function(outData, onTxCompleted, onRxCompleted) {
if (-1 === connection_) {
console.log("Attempted to send data with no device connected.");
return;
}
if (0 == outData.byteLength) {
console.log("Attempted to send nothing.");
return;
}
if (COMMS.receiving) {
console.log("Waiting for a response to a previous message. Aborting.");
return;
}
if (COMMS.transmitting) {
console.log("Waiting for a previous message to finish sending. Aborting.");
return;
}
COMMS.transmitting = true;
var dummyUint8Array = new Uint8Array(outData);
chrome.hid.send(connection_, REPORT_ID, outData, function() {
COMMS.transmitting = false;
if (onTxCompleted) {
onTxCompleted(chrome.runtime.lastError ? chrome.runtime.lastError.message : '');
}
if (chrome.runtime.lastError) {
console.log('Error in COMMS.send: ' + chrome.runtime.lastError.message);
return;
}
// Register a response handler if one is expected
if (onRxCompleted) {
COMMS.receiving = true;
chrome.hid.receive(connection_, function(reportId, inData) {
COMMS.receiving = false;
onRxCompleted(chrome.runtime.lastError ? chrome.runtime.lastError.message : '', inData);
});
}
});
}
// Example usage
var testMessage = new Uint8Array(58);
var testTransmission = function() {
message[0] = 123;
COMMS.send(message.buffer, null, null);
setTimeout(testTransmission, 5000);
};
testTranmission();
The issue is that Windows requires buffers to be the full report size expected by the device. I have filed a bug against Chromium to track adding a workaround or at least a better error message to pinpoint the problem.
In general you can get more detailed error messages from the chrome.hid API by enabling verbose logging with the --enable-logging --v=1 command line options. Full documentation of Chrome logging is here.