Skip to content
This repository has been archived by the owner on Dec 9, 2023. It is now read-only.

Commit

Permalink
support aborting
Browse files Browse the repository at this point in the history
  • Loading branch information
LukePulverenti committed Mar 4, 2020
1 parent 5e752fd commit ae2fbb9
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 193 deletions.
184 changes: 64 additions & 120 deletions apiclient.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,7 @@ function paramsToString(params) {
return values.join('&');
}

function fetchWithTimeout(url, options, timeoutMs) {

return new Promise((resolve, reject) => {

const timeout = setTimeout(reject, timeoutMs);

options = options || {};
options.credentials = 'same-origin';

fetch(url, options).then(response => {
clearTimeout(timeout);
resolve(response);
}, error => {
clearTimeout(timeout);
reject(error);
});
});
}

function getFetchPromise(request) {
function getFetchPromise(request, signal) {

const headers = request.headers || {};

Expand All @@ -48,11 +29,30 @@ function getFetchPromise(request) {
}

const fetchRequest = {
headers,
headers: headers,
method: request.type,
credentials: 'same-origin'
};

if (request.timeout) {

const abortController = new AbortController();

const boundAbort = abortController.abort.bind(abortController);

if (signal) {
signal.addEventListener('abort', boundAbort);
}

setTimeout(boundAbort, request.timeout);

signal = abortController.signal;
}

if (signal) {
fetchRequest.signal = signal;
}

let contentType = request.contentType;

if (request.data) {
Expand All @@ -71,11 +71,7 @@ function getFetchPromise(request) {
headers['Content-Type'] = contentType;
}

if (!request.timeout) {
return fetch(request.url, fetchRequest);
}

return fetchWithTimeout(request.url, fetchRequest, request.timeout);
return fetch(request.url, fetchRequest);
}

function clearCurrentUserCacheIfNeeded(apiClient) {
Expand Down Expand Up @@ -330,14 +326,14 @@ class ApiClient {
return url;
}

fetchWithFailover(request, enableReconnection) {
fetchWithFailover(request, enableReconnection, signal) {

console.log(`Requesting ${request.url}`);

request.timeout = 30000;
const instance = this;

return getFetchPromise(request).then(response => {
return getFetchPromise(request, signal).then(response => {

instance.connected = true;

Expand All @@ -356,21 +352,25 @@ class ApiClient {

}, error => {

if (error) {
console.log("Request failed to " + request.url + ' ' + (error.status || '') + ' ' + error.toString());
} else {
if (!error) {
console.log("Request timed out to " + request.url);
}
else if (error.name === 'AbortError') {
console.log("AbortError: " + request.url);
}
else {
console.log("Request failed to " + request.url + ' ' + (error.status || '') + ' ' + error.toString());
}

// http://api.jquery.com/jQuery.ajax/
if ((!error || !error.status) && enableReconnection) {
console.log("Attempting reconnection");

const previousServerAddress = instance.serverAddress();

return tryReconnect(instance).then((newServerAddress) => {
return tryReconnect(instance, null, signal).then(function (newServerAddress) {

console.log("Reconnect succeeded");
console.log("Reconnect succeeded to " + newServerAddress);
instance.connected = true;

if (instance.enableWebSocketAutoConnect) {
Expand All @@ -379,7 +379,9 @@ class ApiClient {

request.url = request.url.replace(previousServerAddress, newServerAddress);

return instance.fetchWithFailover(request, false);
console.log("Retrying request with new url: " + request.url);

return instance.fetchWithFailover(request, false, signal);
});

} else {
Expand All @@ -394,24 +396,19 @@ class ApiClient {
/**
* Wraps around jQuery ajax methods to add additional info to the request.
*/
fetch(request, includeAuthorization, includeAccessToken) {
fetch(request, includeAccessToken, signal) {

if (!request) {
throw new Error("Request cannot be null");
}

request.headers = request.headers || {};

if (includeAuthorization !== false) {

this.setRequestHeaders(request.headers, includeAccessToken);
}
this.setRequestHeaders(request.headers, includeAccessToken);

if (this.enableAutomaticNetworking === false || request.type !== "GET") {
console.log(`Requesting url without automatic networking: ${request.url}`);

const instance = this;
return getFetchPromise(request).then(response => {
return getFetchPromise(request, signal).then(function (response) {

if (response.status < 400) {

Expand All @@ -429,7 +426,7 @@ class ApiClient {
});
}

return this.fetchWithFailover(request, true);
return this.fetchWithFailover(request, true, signal);
}

setAuthenticationInfo(accessKey, userId) {
Expand Down Expand Up @@ -494,13 +491,13 @@ class ApiClient {
/**
* Wraps around jQuery ajax methods to add additional info to the request.
*/
ajax(request, includeAuthorization, includeAccessToken) {
ajax(request, includeAccessToken) {

if (!request) {
throw new Error("Request cannot be null");
}

return this.fetch(request, includeAuthorization, includeAccessToken);
return this.fetch(request, includeAccessToken);
}

/**
Expand Down Expand Up @@ -737,7 +734,7 @@ class ApiClient {
});
}

getJSON(url, includeAuthorization) {
getJSON(url, signal) {

return this.fetch({

Expand All @@ -748,18 +745,18 @@ class ApiClient {
accept: 'application/json'
}

}, includeAuthorization);
}, signal);
}

getText(url, includeAuthorization) {
getText(url, signal) {

return this.fetch({

url,
type: 'GET',
dataType: 'text'

}, includeAuthorization);
}, signal);
}

updateServerInfo(server, serverUrl) {
Expand Down Expand Up @@ -1015,11 +1012,11 @@ class ApiClient {
});
}

getLiveTvRecordings(options) {
getLiveTvRecordings(options, signal) {

const url = this.getUrl("LiveTv/Recordings", options || {});

return this.getJSON(url);
return this.getJSON(url, signal);
}

getLiveTvRecordingSeries(options) {
Expand All @@ -1029,24 +1026,6 @@ class ApiClient {
return this.getJSON(url);
}

getLiveTvRecordingGroups(options) {

const url = this.getUrl("LiveTv/Recordings/Groups", options || {});

return this.getJSON(url);
}

getLiveTvRecordingGroup(id) {

if (!id) {
throw new Error("null id");
}

const url = this.getUrl(`LiveTv/Recordings/Groups/${id}`);

return this.getJSON(url);
}

getLiveTvRecording(id, userId) {

if (!id) {
Expand Down Expand Up @@ -2480,11 +2459,11 @@ class ApiClient {
/**
* Gets all users from the server
*/
getUsers(options) {
getUsers(options, signal) {

const url = this.getUrl("users", options || {});

return this.getJSON(url).then(setUsersProperties);
return this.getJSON(url, signal).then(setUsersProperties);
}

/**
Expand Down Expand Up @@ -2998,7 +2977,7 @@ class ApiClient {
* recursive - Whether or not the query should be recursive
* searchTerm - search term to use as a filter
*/
getItems(userId, options) {
getItems(userId, options, signal) {

let url;

Expand All @@ -3009,7 +2988,7 @@ class ApiClient {
url = this.getUrl("Items", options);
}

return this.getJSON(url);
return this.getJSON(url, signal);
}

getResumableItems(userId, options) {
Expand Down Expand Up @@ -3877,39 +3856,6 @@ class ApiClient {
});
}

createPackageReview(review) {

const url = this.getUrl(`Packages/Reviews/${review.id}`, review);

return this.ajax({
type: "POST",
url,
});
}

getPackageReviews(packageId, minRating, maxRating, limit) {

if (!packageId) {
throw new Error("null packageId");
}

const options = {};

if (minRating) {
options.MinRating = minRating;
}
if (maxRating) {
options.MaxRating = maxRating;
}
if (limit) {
options.Limit = limit;
}

const url = this.getUrl(`Packages/${packageId}/Reviews`, options);

return this.getJSON(url);
}

getSavedEndpointInfo() {

return this._endPointInfo;
Expand Down Expand Up @@ -3984,22 +3930,20 @@ function setSavedEndpointInfo(instance, info) {
instance._endPointInfo = info;
}

function tryReconnectToUrl(instance, url, delay) {

const timeout = 15000;
function tryReconnectToUrl(instance, url, delay, signal) {

console.log('tryReconnectToUrl: ' + url);

return setTimeoutPromise(delay).then(() => {
return fetchWithTimeout(instance.getUrl('system/info/public', null, url), {

method: 'GET',
accept: 'application/json'
return getFetchPromise({

// Commenting this out since the fetch api doesn't have a timeout option yet
//timeout: timeout
url: instance.getUrl('system/info/public', null, url),
type: 'GET',
dataType: 'json',
timeout: 15000

}, timeout).then(() => {
}, signal).then(() => {

return url;
});
Expand All @@ -4026,7 +3970,7 @@ function setTimeoutPromise(timeout) {
});
}

function tryReconnectInternal(instance) {
function tryReconnectInternal(instance, signal) {

const addresses = [];
const addressesStrings = [];
Expand Down Expand Up @@ -4055,7 +3999,7 @@ function tryReconnectInternal(instance) {

for (let i = 0, length = addresses.length; i < length; i++) {

promises.push(tryReconnectToUrl(instance, addresses[i].url, addresses[i].timeout));
promises.push(tryReconnectToUrl(instance, addresses[i].url, addresses[i].timeout, signal));
}

return onAnyResolveOrAllFail(promises).then((url) => {
Expand Down Expand Up @@ -4086,11 +4030,11 @@ function onAnyResolveOrAllFail(promises) {
});
}

function tryReconnect(instance, retryCount) {
function tryReconnect(instance, retryCount, signal) {

retryCount = retryCount || 0;

const promise = tryReconnectInternal(instance);
const promise = tryReconnectInternal(instance, signal);

if (retryCount >= 2) {
return promise;
Expand All @@ -4101,7 +4045,7 @@ function tryReconnect(instance, retryCount) {
console.log('error in tryReconnectInternal: ' + (err || ''));

return setTimeoutPromise(500).then(() => {
return tryReconnect(instance, retryCount + 1);
return tryReconnect(instance, retryCount + 1, signal);
});
});
}
Expand Down
Loading

0 comments on commit ae2fbb9

Please sign in to comment.