@eclipse-scout/core
Version:
Eclipse Scout runtime
119 lines (103 loc) • 4.79 kB
text/typescript
/*
* Copyright (c) 2010, 2025 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
import {AjaxError, Call, CallModel, InitModelOf, SomeRequired, URL, UrlAjaxSettings} from '../index';
import $ from 'jquery';
export class AjaxCall extends Call implements AjaxCallModel {
declare model: AjaxCallModel;
declare pendingCall: JQuery.jqXHR;
declare initModel: SomeRequired<this['model'], 'ajaxOptions'>;
ajaxOptions: UrlAjaxSettings;
constructor() {
super();
this.type = 'ajax';
this.ajaxOptions = null;
}
override init(model: InitModelOf<this>) {
if (!model) {
throw new Error('Missing argument "model"');
}
if (!model.ajaxOptions) {
throw new Error('Missing model property "ajaxOptions"');
}
if (!model.name) {
model.name = model.ajaxOptions.url;
}
super.init(model);
}
// ==================================================================================
protected override _callImpl(): JQuery.jqXHR {
// Mark retries by adding a URL parameter
if (this.callCounter !== 1) {
this.ajaxOptions.url = new URL(this.ajaxOptions.url).setParameter('retry', (this.callCounter - 1) + '').toString({
alwaysLast: ['retry']
});
}
$.log.isTraceEnabled() && $.log.trace(this.logPrefix + (this.callCounter === 1 ? '--- ' : '') + this.ajaxOptions.method + ' "' + this.ajaxOptions.url + '"' + (this.callCounter === 1 ? ' ---' : ''));
return $.ajax(this.ajaxOptions);
}
protected override _setResultFail(jqXHR: JQuery.jqXHR, textStatus: JQuery.Ajax.ErrorTextStatus, errorThrown: string) {
// Store result as single object to make rethrowing the error easier for callers of AjaxCall
this._setResult(new AjaxError({
jqXHR: jqXHR,
textStatus: textStatus,
errorThrown: errorThrown,
requestOptions: this.ajaxOptions
}));
}
protected override _onCallDone(data: any, textStatus: JQuery.Ajax.SuccessTextStatus, jqXHR: JQuery.jqXHR) {
$.log.isTraceEnabled() && $.log.trace(this.logPrefix + 'AJAX success');
super._onCallDone(data, textStatus, jqXHR);
}
protected override _onCallFail(jqXHR: JQuery.jqXHR, textStatus: JQuery.Ajax.ErrorTextStatus, errorThrown: string) {
$.log.isTraceEnabled() && $.log.trace(this.logPrefix + 'AJAX fail: type=' + textStatus + ', httpStatus=' + jqXHR.status + (errorThrown ? ' "' + errorThrown + '"' : ''));
super._onCallFail(jqXHR, textStatus, errorThrown);
}
protected override _nextRetryImpl(jqXHR: JQuery.jqXHR, textStatus: JQuery.Ajax.ErrorTextStatus, errorThrown: string): number | boolean {
let offlineError = AjaxCall.isOfflineError(jqXHR, textStatus, errorThrown);
if (!offlineError) {
$.log.isTraceEnabled() && $.log.trace(this.logPrefix + 'Unexpected HTTP error');
return false;
}
return super._nextRetryImpl();
}
/* --- STATIC HELPERS ------------------------------------------------------------- */
static isOfflineError(jqXHR: JQuery.jqXHR, textStatus: JQuery.Ajax.ErrorTextStatus, errorThrown: string): boolean {
return (
// Status code = 0 -> no connection
!jqXHR.status ||
// Workaround for IE 9: Apparently, Windows network error codes (http://msdn.microsoft.com/en-us/library/aa383770%28VS.85%29.aspx)
// are passed to JS as HTTP 'status' in some cases (e.g. when server goes offline).
jqXHR.status >= 12000 ||
// Status code 502 = Bad Gateway
// Status code 503 = Service Unavailable
// Status code 504 = Gateway Timeout
// Those codes usually happen when some network component between browser and UI server (e.g. a load balancer)
// has a short outage, most likely only temporarily. Therefore, we treat them like a lost connection.
// Otherwise, the polling loop would break, eventually causing the HTTP session to be invalidated on the
// server due to inactivity. Going offline starts the reconnector which regularly emits ping requests.
// This allows us to reconnect to the server as soon as the connection is fixed, hopefully saving the
// HTTP session from inactivation.
jqXHR.status === 502 ||
jqXHR.status === 503 ||
jqXHR.status === 504
);
}
protected override _abortImpl() {
if (this.pendingCall && typeof this.pendingCall.abort === 'function') {
this.pendingCall.abort();
}
}
}
export interface AjaxCallModel extends CallModel {
/**
* Options for the jquery ajax call. At least the {@link JQuery.UrlAjaxSettings.url} is required.
*/
ajaxOptions?: UrlAjaxSettings;
}