Skip to content

Instantly share code, notes, and snippets.

@prantlf
Last active July 17, 2020 22:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save prantlf/316c8dc3b80c3b2a0aad3be2d0d01a86 to your computer and use it in GitHub Desktop.
Save prantlf/316c8dc3b80c3b2a0aad3be2d0d01a86 to your computer and use it in GitHub Desktop.
Intercepts AJAX calls issued against a base URL and redirect them to other service
// jQuery.ajaxIntercept 0.2.0
// https://gist.github.com/prantlf/316c8dc3b80c3b2a0aad3be2d0d01a86
//
// Copyright (c) 2017 Ferdinand Prantl
// Licensed under the MIT license.
//
// Allows intercepting AJAX calls issued against a base URL
// and either redirect them to some other service, or compose
// the response by a custom asynchronous callback
(function ($) {
'use strict';
$.ajaxIntercept = function (baseUrl, interceptors) {
registry.addInterceptor(baseUrl, interceptors);
};
$.ajaxInterceptSettings = {logResponses: false};
function Registry() {
this._interceptors = [];
}
Object.assign(Registry.prototype, {
constructor: Registry,
addInterceptor: function (baseUrl, rules) {
var interceptor = new Interceptor(rules);
interceptor.baseUrl = parseUrl(baseUrl).href;
this._interceptors.push(interceptor);
},
findInterceptor: function (url) {
if (!url || typeof url === 'string') {
url = parseUrl(url);
}
url = url.origin + url.pathname;
return this._interceptors.find(function (interceptor) {
return url.startsWith(interceptor.baseUrl);
});
}
});
var registry = new Registry();
function Interceptor(rules) {
this._rules = [];
if (rules) {
rules.forEach(this.addRule.bind(this));
}
}
Object.assign(Interceptor.prototype, {
constructor: Interceptor,
addRule: function (rule) {
var parsedPath = pathToExpression(rule.path);
rule.expr = new RegExp('^' + parsedPath.expr.join('/') + '$');
rule.params = parsedPath.params;
rule.method = (rule.method || 'GET').toUpperCase();
this._rules.push(rule);
},
createHandler: function (options, uri) {
var result = findAndExecuteRule.call(this, uri, options),
rule = result.rule;
if (!rule) {
throw new Error('AJAX request not intercepted: ' +
result.method + ' ' + result.url.href + '.');
}
applyExecutedRule(result, options);
return new Handler(rule, options);
}
});
function Handler(rule, options) {
this._rule = rule;
this._options = options;
}
Object.assign(Handler.prototype, {
constructor: Handler,
send: function (headers, complete) {
var rule = this._rule,
original = this._options,
execute = rule.execute,
options = rule.options;
original.headers = headersToLowerCase(
Object.assign({}, original.headers, headers));
if (execute) {
this._jqxhr = execute(original, callback);
} else {
var ajax = rule.ajax || $.ajax;
options = options ? options(original) : original;
Object.assign(options, {
beforeSend: function (jqxhr, options) {
jqxhr.options = original;
jqxhr.proxyOptions = options;
},
success: function (response, statusText, jqxhr) {
var success = rule.success;
if (success) {
if (success.length > 3) {
success(response, statusText, jqxhr, function (result) {
callback(result, statusText, jqxhr);
});
} else {
var result = success(response, statusText, jqxhr);
callback(result, statusText, jqxhr);
}
}
},
error: function (jqxhr, statusText, errorText) {
var error = rule.error;
if (error) {
var result = error(jqxhr, statusText, errorText);
callback(result, statusText, jqxhr);
}
},
complete: function (jqxhr, statusText) {
var complete = rule.complete;
if (complete) {
var result = complete(jqxhr, statusText);
callback(result, statusText, jqxhr);
}
}
});
this._jqxhr = ajax(options);
}
function callback(result, statusText, jqxhr) {
var status = result.status || jqxhr && jqxhr.status || 200,
finalStatusText = result.statusText || statusText || 'OK',
responses = result.responses || {},
headers = result.headers || jqxhr && jqxhr.getAllResponseHeaders() || {};
logResponse(original, status, finalStatusText, responses, headers);
complete(status, finalStatusText, responses, stringifyHeaders(headers));
}
},
abort: function (statusText) {
var jqxhr = this._jqxhr;
if (jqxhr && jqxhr.abort) {
jqxhr.abort(statusText);
}
}
});
function parseUrl(url) {
if (!url) {
url = location.href;
} else if (url.charAt(0) === '/') {
if (url.charAt(1) === '/') {
url = location.protocol + url;
} else {
url = location.origin + url;
}
} else if (url.indexOf('//') < 0) {
url = location.origin + location.pathname + url;
}
return new URL(url);
}
function pathToExpression(path) {
return path.split('/')
.reduce(function (path, part) {
if (part.charAt(0) === ':') {
path.expr.push('([^/\\?]+)');
path.params.push(part.substr(1));
} else {
part = part.replace(/\./g, '\\.')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)')
.replace(/\*/g, '\\*')
.replace(/\+/g, '\\+');
path.expr.push(part);
}
return path;
}, {
expr: [],
params: []
});
}
function findAndExecuteRule(uri, options) {
var method = (options.method || options.type || 'GET').toUpperCase(),
url = uri || parseUrl(options.url),
path = (url.origin + url.pathname).substr(this.baseUrl.length),
params,
rule = this._rules.find(function (rule) {
if (rule.method === method) {
params = rule.expr.exec(path);
return params;
}
});
return {
rule: rule,
method: method,
url: url,
params: params
};
}
function applyExecutedRule(result, options) {
var rule = result.rule,
url = result.url,
params = result.params;
options.method = result.method;
options.url = url.href;
options.params = rule.params.reduce(function (result, param, index) {
result[param] = params[index + 1];
return result;
}, {});
options.query = $.parseParams(url.search);
}
function headersToLowerCase(headers) {
return Object.keys(headers || {})
.reduce(function (result, key) {
result[key.toLowerCase()] = headers[key];
return result;
}, {});
}
function stringifyHeaders(headers) {
if (typeof headers === 'string') {
return headers;
}
return Object.keys(headers || {})
.reduce(function (result, name) {
return result + name + ': ' + headers[name] + '\n';
}, '');
}
function logResponse(options, status, statusText, responses, headers) {
if ($.ajaxInterceptSettings.logResponses) {
if (typeof headers === 'string') {
headers = headers.split('\n')
.reduce(function (result, header) {
if (header) {
header = header.split(/:(.+)/, 2);
result[header[0]] = header[1].trim();
}
return result;
}, {});
} else if (!headers) {
headers = {};
}
var response = responses[Object.keys(responses)[0]];
if (typeof response === 'string' && response.length > 256) {
response = response.substr(0, 256) + '...';
}
if (!headers['content-type']) {
headers['content-type'] = typeof response === 'string' ?
'text/plain' : 'application/json';
}
if (options.data) {
console.log('INTERCEPT', options.method, options.url, '\n',
'request headers:', options.headers, '\n',
options.data, '\n',
'status:', status, statusText, '\n',
'response headers:', headers, '\n', response);
} else {
console.log('INTERCEPT', options.method, options.url, '\n',
'request headers:', options.headers, '\n',
'status:', status, statusText, '\n',
'response headers:', headers, '\n', response);
}
}
}
$.ajaxPrefilter(function (options, originalOptions, jqxhr) {
if (options.intercept !== false) {
var url = parseUrl(options.url),
interceptor = registry.findInterceptor(url);
if (interceptor) {
options.originalDataType = options.dataType;
options.originalDataTypes = options.dataTypes;
options.dataType = 'intercept';
options.dataTypes = ['intercept'];
options.interceptor = interceptor;
options.parsedUrl = url;
}
}
});
$.ajaxTransport('intercept', function (options) {
var interceptor = options.interceptor;
if (interceptor) {
options.dataType = options.originalDataType;
options.dataTypes = options.originalDataTypes;
var handler = interceptor.createHandler(options, options.parsedUrl);
return {
send: function (headers, complete) {
handler.send(headers, complete);
},
abort: function (statusText) {
handler.abort(statusText);
}
};
}
});
}(jQuery));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment