Last active
July 17, 2020 22:13
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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