Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function callbackError(message, error){
console.log(error.data);
}
```
For more information on the error JSON structure returned from MyGeotab API, see [here](https://geotab.github.io/sdk/software/guides/concepts/#results-and-errors).
For more information on the error JSON structure returned from MyGeotab API, see [here](https://developers.geotab.com/myGeotab/guides/concepts#result-and-errors).

Note: `error` parameter in a `.catch` statement always returns an instance of `Error`.

Expand Down
2 changes: 1 addition & 1 deletion dist/api.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/api.min.js.map

Large diffs are not rendered by default.

33 changes: 22 additions & 11 deletions lib/ApiHelper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const LocalStorageCredentialStore = require('./LocalStorageCredentialStore.js').default;
const AxiosCall = require('./AxiosCall.js').default;
const HttpCall = require('./HttpCall.js').default;

/**
* Helper method for GeotabApi.
Expand Down Expand Up @@ -121,12 +121,12 @@ class ApiHelper {
/**
* When the user doesn't have fullResposne enabled, we put some promises in front of the call
* to return only data.result to the user
* @param {Promise} call Axios call
* @param {Promise} call Http call
*/
parseAxiosResponse(call) {
parseHttpResponse(call) {
return call
.then(response => {
// Error handling has already taken errors out in either errorHandle() or AxiosCall.send()
// Error handling has already taken errors out in either errorHandle() or HttpCall.send()
if (response.data.result) {
return response.data.result;
}
Expand All @@ -136,14 +136,15 @@ class ApiHelper {

/**
* Error handling - only called if fullresponse isn't enabled, and if the user hasn't provided a callback
* @param {Promise} call Axios call
* @param {Promise} call Http call
* @param {function} callbackError Optional callback for unsuccessful calls
*/
errorHandle(call, callbackError) {
// User hasn't asked for the full axios object, so we should error check
// User hasn't asked for the full http object, so we should error check
return call
.then(response => {
if (response.status !== 200) {
// TODO - Make DRY
if (response.status && response.status !== 200) {
let error = new Error(`Response ${response.status} - ${response.statusText}`);

if (callbackError) {
Expand All @@ -152,6 +153,16 @@ class ApiHelper {
throw error;
}
}
// node http/https returns a different response schema
if (response.statusCode && response.statusCode !== 200) {
let error = new Error(`Response ${response.statusCode} - ${response.statusMessage}`);

if (callbackError) {
callbackError(error.toString(), error);
} else {
throw error;
}
}
return response;
})
.then(response => {
Expand All @@ -169,7 +180,7 @@ class ApiHelper {
}

/**
* Creates a new AxiosCall and returns it to the user. If the call fails,
* Creates a new HttpCall and returns it to the user. If the call fails,
* and the call isn't an authentication, the call will attempt to
* refresh the credentials
*
Expand All @@ -180,8 +191,8 @@ class ApiHelper {
* @param {function} authenticate authentication function. Context binded to GeotabApi
* @param {boolean} rememberMe should store authentication results
*/
async sendAxiosCall(method, params, callbackSuccess, callbackError, authenticate, rememberMe) {
let call = new AxiosCall(method, this.server, params, callbackSuccess).send(this.options.timeout);
async sendHttpCall(method, params, callbackSuccess, callbackError, authenticate, rememberMe) {
let call = new HttpCall(method, this.server, params, callbackSuccess).send(this.options.timeout);
// We don't want to continue retrying if we fail authentication
if (method !== 'Authenticate') {
call = call.then(async response => {
Expand All @@ -196,7 +207,7 @@ class ApiHelper {
.then(auth => {
params.credentials = auth;
});
return new AxiosCall(method, this.server, params, callbackSuccess).send(this.options.timeout);
return new HttpCall(method, this.server, params, callbackSuccess).send(this.options.timeout);
}
}
return response;
Expand Down
65 changes: 0 additions & 65 deletions lib/AxiosCall.js

This file was deleted.

34 changes: 16 additions & 18 deletions lib/GeotabApi.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const { AxiosError } = require('axios');

const ApiHelper = require('./ApiHelper.js').default;

/**
Expand Down Expand Up @@ -27,12 +25,12 @@ class GeotabApi {
}

/**
* Authenticates the user against the server. Gets the sessionId and other relevant session information
*
* @param {function} callbackSuccess optional callback for successful authentications
* @param {function} callbackError optional callback for unsuccessful authentications
* @returns {promise} Call promise - result will be either response.data.error or response.data.result
*/
* Authenticates the user against the server. Gets the sessionId and other relevant session information
*
* @param {function} callbackSuccess optional callback for successful authentications
* @param {function} callbackError optional callback for unsuccessful authentications
* @returns {promise} Call promise - result will be either response.data.error or response.data.result
*/
async authenticate(callbackSuccess, callbackError) {
if (callbackSuccess && !callbackError) {
throw new Error(`If callbackSuccess is supplied so must callbackError`);
Expand All @@ -52,14 +50,14 @@ class GeotabApi {
}

/**
* Constructs an API call to MyGeotab
*
* @param {string} method method name for the API call
* @param {Object} params method's parameters
* @param {function} callbackSuccess Optional callback for successful calls
* @param {function} callbackError Optional callback for unsuccessful calls
* @returns {promise} an axios promise which will resolve to either data.error or data.result
*/
* Constructs an API call to MyGeotab
*
* @param {string} method method name for the API call
* @param {Object} params method's parameters
* @param {function} callbackSuccess Optional callback for successful calls
* @param {function} callbackError Optional callback for unsuccessful calls
* @returns {promise} an axios promise which will resolve to either data.error or data.result
*/
async call(method, params, callbackSuccess, callbackError) {
if (callbackSuccess && !callbackError) {
throw new Error(`If callbackSuccess is supplied so must callbackError`);
Expand Down Expand Up @@ -110,11 +108,11 @@ class GeotabApi {
});
}
// Creating the actual call
let call = this._helper.sendAxiosCall(method, params, callbackSuccess, callbackError, this.authenticate, this._helper.rememberMe);
let call = this._helper.sendHttpCall(method, params, callbackSuccess, callbackError, this.authenticate, this._helper.rememberMe);
// Seeing if the user wants the axios response object, or default error handling
if (!this._helper.fullResponse) {
call = this._helper.errorHandle(call, callbackError);
call = this._helper.parseAxiosResponse(call);
call = this._helper.parseHttpResponse(call);
}
// Returning call to user
return call;
Expand Down
117 changes: 117 additions & 0 deletions lib/HttpCall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const http = typeof window === 'undefined' ? require('http') : null;
const https = typeof window === 'undefined' ? require('https') : null;

class HttpCall {

constructor(method, server, params, callbackSuccess) {
this.config = {
headers: {
'Content-Type': 'application/json'
}
}
this.callbackSuccess = callbackSuccess;
this.server = server;
this.body = {
method: method || '',
params: params
};
}

getCallUrl() {
let server = this.server || 'my.geotab.com';
let thisServer = server.replace(/\S*:\/\//, '').replace(/\/$/, '');
return 'https://' + thisServer + '/apiv1/';
}

encode(data) {
let stringBody = JSON.stringify({
method: data.method || '',
params: data.params
})
return stringBody;
}

/**
* Sends axios request
* @param {int} timeout amount in milliseconds for timeout.
* options.timeout * 1000 is a good start
* Defaults to no timeout (0)
* @returns {Promise} Axios promise
*/
async send(timeout) {
const url = this.getCallUrl();
const body = this.encode(this.body);
const headers = this.config.headers;

if (typeof window !== 'undefined' && window.fetch) {
// Web environment (using fetch)
let fetchTimeout;
const fetchPromise = fetch(url, {
method: 'POST',
headers: headers,
body: body
});

const timeoutPromise = new Promise((_, reject) => {
fetchTimeout = setTimeout(() => reject(new Error('Request timed out')), timeout * 1000);
});

return Promise.race([fetchPromise, timeoutPromise])
.then(async response => {
clearTimeout(fetchTimeout);
const data = await response.json();
response.data = data;
if (this.callbackSuccess && !data.error) {
this.callbackSuccess(data.result);
}
console.log("Inside send (web) - ", response);
return response;
})
.catch(error => {
return Promise.reject(error);
});

} else {
// Node.js environment (using http/https)
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
path: urlObj.pathname,
method: 'POST',
headers: headers,
timeout: timeout * 1000
};

return new Promise((resolve, reject) => {
const req = (urlObj.protocol === 'https:' ? https : http).request(options, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const parsedData = JSON.parse(data);
res.data = parsedData; // Attach parsed response body to the response object.
if (this.callbackSuccess && !parsedData.error) {
this.callbackSuccess(parsedData.result);
}
console.log("Inside send (node) - ", res);
resolve(res);
} catch (error) {
reject(error);
}
});
});

req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timed out'));
});

req.write(body);
req.end();
});
}
}
}

exports.default = HttpCall;
Loading