ideal-postcodes-core
Version:
Ideal Postcodes core frontend javascript library
774 lines (772 loc) • 32.1 kB
JavaScript
/**
* ideal-postcodes-core - Ideal Postcodes core frontend javascript library
* @version v1.0.0
* @link https://ideal-postcodes.co.uk/
* @license MIT
*/
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var IdealPostcodes;
(function (IdealPostcodes) {
IdealPostcodes.API_URL = "api.ideal-postcodes.co.uk";
IdealPostcodes.TLS = true;
IdealPostcodes.VERSION = "v1";
IdealPostcodes.DEFAULT_TIMEOUT = 10000;
/*
* STRICT_AUTHORISATION forces authorization header usage on
* autocomplete API which increases latency due to overhead
* OPTIONS request
*/
IdealPostcodes.STRICT_AUTHORISATION = false;
// Removes IdealPostcodes reference from window
IdealPostcodes.removeGlobalReference = function () {
if (root && root["IdealPostcodes"]) {
root["IdealPostcodes"] = undefined;
}
};
})(IdealPostcodes || (IdealPostcodes = {}));
/** Module exporting with thanks to github.com/lodash/lodash */
/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global === "object" && global && global.Object === Object && global;
/** Used as a reference to the global object. */
var root = freeGlobal || Function("return this")();
/** Detect free variable `exports`. */
var freeExports = typeof exports === "object" && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule = freeExports && typeof module === "object" && module && !module.nodeType && module;
// Export for AMD
if (typeof define === "function" && typeof define.amd === "object" && define.amd) {
define(function () { return IdealPostcodes; });
}
else if (freeModule) {
// Export for Node.js although this won't work without updating lib/transport
freeModule.exports = IdealPostcodes;
// Export for CommonJS support
freeExports.IdealPostcodes = IdealPostcodes;
}
// Export to the global object
root.IdealPostcodes = IdealPostcodes;
var IdealPostcodes;
(function (IdealPostcodes) {
var Utils;
(function (Utils) {
// Credit to https://github.com/component/debounce
Utils.now = function () { return Date.now(); };
Utils.debounce = function (func, delay) {
if (delay === void 0) { delay = 100; }
var timeout, args, context, timeInvoked, result;
function later() {
var timeSinceInvocation = Utils.now() - timeInvoked;
if (timeSinceInvocation > 0 && timeSinceInvocation < delay) {
timeout = setTimeout(later, delay - timeSinceInvocation);
}
else {
timeout = null;
result = func.apply(context, args);
if (!timeout)
context = args = null;
}
}
return function () {
context = this;
args = arguments;
timeInvoked = Utils.now();
if (!timeout)
timeout = setTimeout(later, delay);
return result;
};
};
Utils.extend = function (target) {
var sources = [];
for (var _i = 1; _i < arguments.length; _i++) {
sources[_i - 1] = arguments[_i];
}
var length = sources.length;
for (var i = 0; i < length; i++) {
var source = sources[i];
for (var key in source) {
if (source[key] !== undefined) {
target[key] = source[key];
}
}
}
return target;
};
})(Utils = IdealPostcodes.Utils || (IdealPostcodes.Utils = {}));
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="../index.ts" />
var IdealPostcodes;
/// <reference path="../index.ts" />
(function (IdealPostcodes) {
var cacheArguments = [
"id",
"postcode",
"query",
"limit",
"page",
"post_town",
"postcode_outward",
"filter"
];
var generateCacheId = function (qs) {
return cacheArguments.map(function (arg) { return [arg, qs[arg]]; })
.filter(function (elem) { return elem[1] !== undefined; })
.map(function (elem) { return elem.join("="); })
.join("|");
};
var Cache = (function () {
function Cache() {
this.initialiseStore();
this.active = true;
}
Cache.prototype.disable = function () {
this.active = false;
};
Cache.prototype.enable = function () {
this.active = true;
};
Cache.prototype.initialiseStore = function () {
this.store = {
postcodeStore: {},
addressStore: {},
autocompleteStore: {},
udprnStore: {},
umprnStore: {}
};
};
Cache.prototype.cacheAddressQuery = function (options, response) {
if (!this.active)
return;
var id = generateCacheId(options);
this.store.addressStore[id] = response;
};
Cache.prototype.getAddressQuery = function (options) {
if (!this.active)
return;
var id = generateCacheId(options);
return this.store.addressStore[id];
};
Cache.prototype.cachePostcodeQuery = function (options, response) {
if (!this.active)
return;
var id = generateCacheId(options);
this.store.postcodeStore[id] = response;
};
Cache.prototype.getPostcodeQuery = function (options) {
if (!this.active)
return;
var id = generateCacheId(options);
return this.store.postcodeStore[id];
};
Cache.prototype.cacheAutocompleteQuery = function (options, response) {
if (!this.active)
return;
var id = generateCacheId(options);
this.store.autocompleteStore[id] = response;
};
Cache.prototype.getAutocompleteQuery = function (options) {
if (!this.active)
return;
var id = generateCacheId(options);
return this.store.autocompleteStore[id];
};
Cache.prototype.cacheUdprnQuery = function (options, response) {
if (!this.active)
return;
var id = generateCacheId(options);
this.store.udprnStore[id] = response;
};
Cache.prototype.getUdprnQuery = function (options) {
if (!this.active)
return;
var id = generateCacheId(options);
return this.store.udprnStore[id];
};
Cache.prototype.cacheUmprnQuery = function (options, response) {
if (!this.active)
return;
var id = generateCacheId(options);
this.store.umprnStore[id] = response;
};
Cache.prototype.getUmprnQuery = function (options) {
if (!this.active)
return;
var id = generateCacheId(options);
return this.store.umprnStore[id];
};
return Cache;
}());
IdealPostcodes.Cache = Cache;
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="../index.ts" />
var IdealPostcodes;
/// <reference path="../index.ts" />
(function (IdealPostcodes) {
var Transport;
(function (Transport) {
Transport.blankRe = /^\s*$/;
Transport.AllowedAuthorizationParameters = ["api_key"];
Transport.detectTls = function (window) {
try {
return window.location.protocol !== "http:";
}
catch (e) {
return true;
}
};
Transport.legacyBrowser = function (w) {
var ieVersion = Transport.isIE(w);
var operaVersion = Transport.isOpera(w);
return !!(ieVersion && ieVersion <= 9) || !!(operaVersion && operaVersion <= 12);
};
Transport.isIE = function (w) {
var nav = w ? w.navigator : window.navigator;
try {
var myNav = nav.userAgent.toLowerCase();
return (myNav.indexOf("msie") !== -1) ? parseInt(myNav.split("msie")[1]) : false;
}
catch (e) {
return false;
}
};
Transport.isOpera = function (w) {
var opera = w ? w.opera : window["opera"];
if (!opera)
return false;
if (Object.prototype.toString.call(opera) !== "[object Opera]")
return false;
try {
var version = parseInt(opera.version(), 10);
if (isNaN(version))
return false;
return version;
}
catch (e) {
return false;
}
};
Transport.generateQueryString = function (query) {
var result = [];
for (var key in query) {
result.push(encodeURIComponent(key) + "=" + encodeURIComponent(query[key]));
}
return result.join("&");
};
Transport.constructHeaders = function (headerOptions) {
var headers = {};
headers["Authorization"] = Transport.constructAuthenticationHeader(headerOptions);
return headers;
};
Transport.deconstructAuthenticationHeader = function (authorizationHeader) {
var result = {};
if (!authorizationHeader)
return result;
authorizationHeader
.replace("IDEALPOSTCODES ", "")
.trim()
.split(" ")
.forEach(function (elem) {
var e = elem.split("=");
if (typeof e[0] === "string" && typeof e[1] === "string") {
result[e[0]] = e[1].replace(/(^"|"$)/g, "");
}
});
return result;
};
Transport.constructAuthenticationHeader = function (authOptions) {
var authorizationHeader = [];
for (var i = 0; i < Transport.AllowedAuthorizationParameters.length; i++) {
var param = Transport.AllowedAuthorizationParameters[i];
if (authOptions[param] !== undefined) {
authorizationHeader.push(param + "=\"" + authOptions[param] + "\"");
}
}
if (authorizationHeader.length === 0)
return "";
return "IDEALPOSTCODES " + authorizationHeader.join(" ");
};
Transport.constructQueryString = function (options) {
var queryString = {};
if (options.filter)
queryString["filter"] = options.filter.join(",");
if (options.licensee)
queryString["licensee"] = options.licensee;
if (options.tags)
queryString["tags"] = options.tags.join(",");
return queryString;
};
Transport.constructAutocompleteQueryString = function (options) {
var queryString = {};
queryString["query"] = options.query;
if (options.limit)
queryString["limit"] = options.limit;
if (options.postcode_outward) {
queryString["postcode_outward"] = options.postcode_outward.join(",");
}
if (options.post_town) {
queryString["post_town"] = options.post_town.join(",");
}
return queryString;
};
Transport.constructAddressQueryString = function (options) {
var queryString = {};
queryString["query"] = options.query;
queryString["page"] = options.page || 0;
queryString["limit"] = options.limit || 10;
if (options.postcode_outward) {
queryString["postcode_outward"] = options.postcode_outward.join(",");
}
if (options.post_town) {
queryString["post_town"] = options.post_town.join(",");
}
return queryString;
};
})(Transport = IdealPostcodes.Transport || (IdealPostcodes.Transport = {}));
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="../index.ts" />
var IdealPostcodes;
/// <reference path="../index.ts" />
(function (IdealPostcodes) {
var Errors;
(function (Errors) {
var IdealPostcodesError = (function (_super) {
__extends(IdealPostcodesError, _super);
function IdealPostcodesError(options) {
var _this = _super.call(this) || this;
_this.message = "Ideal Postcodes Error: " + options.message;
return _this;
}
return IdealPostcodesError;
}(Error));
Errors.IdealPostcodesError = IdealPostcodesError;
var JsonParseError = (function (_super) {
__extends(JsonParseError, _super);
function JsonParseError() {
return _super.call(this, {
message: "Unable to parse JSON response"
}) || this;
}
return JsonParseError;
}(IdealPostcodesError));
Errors.JsonParseError = JsonParseError;
})(Errors = IdealPostcodes.Errors || (IdealPostcodes.Errors = {}));
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="../index.ts" />
/// <reference path="./standard.ts" />
var IdealPostcodes;
/// <reference path="../index.ts" />
/// <reference path="./standard.ts" />
(function (IdealPostcodes) {
var Errors;
(function (Errors) {
Errors.parse = function (xhr) {
var status = xhr.status;
if (status === 200)
return;
switch (status) {
case 503:
return new RateLimitError();
}
try {
return Errors.parseErrorResponse(JSON.parse(xhr.responseText), status);
}
catch (e) {
return new Errors.JsonParseError();
}
};
Errors.parseErrorResponse = function (response, status) {
var responseCode = response.code;
var message = response.message;
if (responseCode === undefined || message === undefined)
return new GenericApiError();
return new IdealPostcodesApiError({
responseCode: responseCode,
status: status,
message: message
});
};
var IdealPostcodesApiError = (function (_super) {
__extends(IdealPostcodesApiError, _super);
function IdealPostcodesApiError(options) {
var _this = _super.call(this, options) || this;
if (options.status)
_this.status = options.status;
if (options.responseCode)
_this.responseCode = options.responseCode;
return _this;
}
return IdealPostcodesApiError;
}(Errors.IdealPostcodesError));
Errors.IdealPostcodesApiError = IdealPostcodesApiError;
var RateLimitError = (function (_super) {
__extends(RateLimitError, _super);
function RateLimitError() {
return _super.call(this, {
status: 503,
message: "Rate Limit Reached. Please wait a while before you retry your request"
}) || this;
}
return RateLimitError;
}(IdealPostcodesApiError));
Errors.RateLimitError = RateLimitError;
var RequestTimeoutError = (function (_super) {
__extends(RequestTimeoutError, _super);
function RequestTimeoutError() {
return _super.call(this, {
message: "Request timed out"
}) || this;
}
return RequestTimeoutError;
}(IdealPostcodesApiError));
Errors.RequestTimeoutError = RequestTimeoutError;
var GenericApiError = (function (_super) {
__extends(GenericApiError, _super);
function GenericApiError() {
return _super.call(this, {
message: "Unknown AJAX error occurred when accessing API"
}) || this;
}
return GenericApiError;
}(IdealPostcodesApiError));
Errors.GenericApiError = GenericApiError;
})(Errors = IdealPostcodes.Errors || (IdealPostcodes.Errors = {}));
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="./utils.ts" />
/// <reference path="../index.ts" />
/// <reference path="../error/api.ts" />
/// <reference path="../utils/utils.ts" />
var IdealPostcodes;
/// <reference path="./utils.ts" />
/// <reference path="../index.ts" />
/// <reference path="../error/api.ts" />
/// <reference path="../utils/utils.ts" />
(function (IdealPostcodes) {
var Transport;
(function (Transport) {
Transport.getXhr = function () {
try {
return new (XMLHttpRequest || ActiveXObject)("MSXML2.XMLHTTP.3.0");
}
catch (e) {
return null;
}
};
Transport.xhrRequest = function (options, callback) {
var url = options.url;
var queryString = Transport.generateQueryString(options.queryString);
if (queryString.length > 0)
url += "?" + queryString;
var xhr = Transport.getXhr();
xhr.open(options.method, url, true);
try {
for (var attr in options.headers) {
xhr.setRequestHeader(attr, options.headers[attr]);
}
}
catch (e) { }
var abortTimeout = setTimeout(function () {
xhr.onreadystatechange = function () { };
xhr.abort();
callback(new Error("Request timeout"), null, xhr);
}, options.timeout);
xhr.onreadystatechange = function () {
var result;
if (xhr.readyState === 4) {
clearTimeout(abortTimeout);
if (xhr.status !== 200) {
return callback(IdealPostcodes.Errors.parse(xhr), {}, xhr);
}
try {
result = Transport.blankRe.test(xhr.responseText) ? {} : JSON.parse(xhr.responseText);
}
catch (e) {
return callback(new Error("parsererror"), null, xhr);
}
return callback(null, result, xhr);
}
};
xhr.send(options.data);
return xhr;
};
})(Transport = IdealPostcodes.Transport || (IdealPostcodes.Transport = {}));
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="./utils.ts" />
/// <reference path="../index.ts" />
/// <reference path="../error/api.ts" />
/// <reference path="../utils/utils.ts" />
var IdealPostcodes;
/// <reference path="./utils.ts" />
/// <reference path="../index.ts" />
/// <reference path="../error/api.ts" />
/// <reference path="../utils/utils.ts" />
(function (IdealPostcodes) {
var Transport;
(function (Transport) {
var jsonpCounter = 0;
var noop = function () { };
// Include callback name, any header authorisation, other querystring options
var jsonpQueryString = function (options, callbackName) {
options.queryString["callback"] = callbackName;
var headers = options.headers;
var auth = Transport.deconstructAuthenticationHeader(headers["Authorization"]);
IdealPostcodes.Utils.extend(options.queryString, auth);
return Transport.generateQueryString(options.queryString);
};
var extractStatus = function (apiResponse) {
var code = apiResponse.code;
if (!code || typeof code !== "number")
return 500;
return parseInt(String(code).slice(0, 3));
};
Transport.jsonpRequest = function (options, callback) {
jsonpCounter += 1;
var url = options.url;
// Reject non GET requests
if (options.method && options.method.toLowerCase() !== "get") {
callback(new Error("Browser is unable to perform non-GET requests"), null, null);
return null;
}
// Generate callbackname
var callbackName = "idpc_" + IdealPostcodes.Utils.now() + "_" + jsonpCounter;
// Configure querystring
var queryString = jsonpQueryString(options, callbackName);
if (queryString.length > 0)
url += "?" + queryString;
var target = document.getElementsByTagName("script")[0] || document.head;
var timer = setTimeout(function () {
cleanup();
callback(new Error("Request timeout"), null, null);
}, options.timeout);
var cleanup = function () {
if (script.parentNode)
script.parentNode.removeChild(script);
window[callbackName] = noop;
if (timer)
clearTimeout(timer);
};
var cancel = function () {
if (window[callbackName])
cleanup();
};
window[callbackName] = function (result) {
cleanup();
var status = extractStatus(result);
var virtualXhr = {
responseText: result,
status: status
};
if (virtualXhr.status !== 200) {
return callback(IdealPostcodes.Errors.parseErrorResponse(result, status), null, virtualXhr);
}
return callback(null, result, virtualXhr);
};
var script = document.createElement("script");
script.src = url;
script.type = "text/javascript";
target.parentNode.insertBefore(script, target);
return null;
};
})(Transport = IdealPostcodes.Transport || (IdealPostcodes.Transport = {}));
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="./utils.ts" />
/// <reference path="./xhr.ts" />
/// <reference path="./jsonp.ts" />
/// <reference path="../index.ts" />
var IdealPostcodes;
/// <reference path="./utils.ts" />
/// <reference path="./xhr.ts" />
/// <reference path="./jsonp.ts" />
/// <reference path="../index.ts" />
(function (IdealPostcodes) {
var Transport;
(function (Transport) {
Transport.defaultHeaders = {
"Accept": "text/javascript, application/javascript"
};
Transport.request = function (options, callback) {
var strictOptions = {
url: options.url,
method: options.method || "GET",
headers: options.headers || {},
queryString: options.queryString || {},
timeout: options.timeout || IdealPostcodes.DEFAULT_TIMEOUT,
data: options.data || null
};
IdealPostcodes.Utils.extend(strictOptions.headers, Transport.defaultHeaders);
// If legacy (<IE9, <Opera12, fallback to jsonp)
if (Transport.legacyBrowser())
return Transport.jsonpRequest(strictOptions, callback);
// Otherwise proceed with XMLHttpRequest
return Transport.xhrRequest(strictOptions, callback);
};
})(Transport = IdealPostcodes.Transport || (IdealPostcodes.Transport = {}));
})(IdealPostcodes || (IdealPostcodes = {}));
/// <reference path="../index.ts" />
/// <reference path="../utils/utils.ts" />
/// <reference path="../utils/cache.ts" />
/// <reference path="../transport/index.ts" />
/// <reference path="../transport/utils.ts" />
var IdealPostcodes;
/// <reference path="../index.ts" />
/// <reference path="../utils/utils.ts" />
/// <reference path="../utils/cache.ts" />
/// <reference path="../transport/index.ts" />
/// <reference path="../transport/utils.ts" />
(function (IdealPostcodes) {
var extend = IdealPostcodes.Utils.extend;
var XhrUtils = IdealPostcodes.Transport;
var constructHeaders = XhrUtils.constructHeaders;
var constructQuery = XhrUtils.constructQueryString;
var constructAddressQuery = XhrUtils.constructAddressQueryString;
var constructAutocompleteQuery = XhrUtils.constructAutocompleteQueryString;
var Client = (function () {
function Client(options) {
if (options === void 0) { options = {}; }
var _this = this;
this.api_key = options.api_key;
this.tls = options.tls === undefined ? IdealPostcodes.TLS : options.tls;
this.version = options.version === undefined ? IdealPostcodes.VERSION : options.version;
this.baseUrl = options.baseUrl === undefined ? IdealPostcodes.API_URL : options.baseUrl;
this.strictAuthorisation = options.strictAuthorisation === undefined ? IdealPostcodes.STRICT_AUTHORISATION : options.strictAuthorisation;
this.cache = new IdealPostcodes.Cache();
var self = this;
this.autocompleteCallback = function () { };
// Need to consider caching as well! Can't store meta in cache store
this.debouncedAutocomplete = IdealPostcodes.Utils.debounce(function (options) {
_this.lookupAutocomplete(options, self.autocompleteCallback);
});
}
Client.prototype.apiUrl = function () {
return "http" + (this.tls ? "s" : "") + "://" + this.baseUrl + "/" + this.version;
};
Client.prototype.ping = function (callback) {
IdealPostcodes.Transport.request({
url: "http" + (this.tls ? "s" : "") + "://" + this.baseUrl
}, callback);
};
Client.prototype.lookupPostcode = function (options, callback) {
var _this = this;
options.api_key = this.api_key;
var headers = constructHeaders(options);
var queryString = constructQuery(options);
var cachedResponse = this.cache.getPostcodeQuery(options);
if (cachedResponse)
return callback(null, cachedResponse);
IdealPostcodes.Transport.request({
url: this.apiUrl() + "/postcodes/" + encodeURIComponent(options.postcode),
headers: headers,
queryString: queryString
}, function (error, data, xhr) {
if (error && error.responseCode === 4040)
return callback(null, [], xhr);
if (error)
return callback(error, null, xhr);
_this.cache.cachePostcodeQuery(options, data.result);
return callback(null, data.result, xhr);
});
};
Client.prototype.lookupAddress = function (options, callback) {
var _this = this;
options.api_key = this.api_key;
var headers = constructHeaders(options);
var queryString = constructQuery(options);
extend(queryString, constructAddressQuery(options));
var cachedResponse = this.cache.getAddressQuery(options);
if (cachedResponse)
return callback(null, cachedResponse);
IdealPostcodes.Transport.request({
url: this.apiUrl() + "/addresses",
headers: headers,
queryString: queryString
}, function (error, data, xhr) {
if (error)
return callback(error, null, xhr);
_this.cache.cacheAddressQuery(options, data.result);
return callback(null, data.result, xhr);
});
};
Client.prototype.lookupAutocomplete = function (options, callback) {
var _this = this;
options.api_key = this.api_key;
var headers = constructHeaders(options);
var queryString = constructQuery(options);
extend(queryString, constructAutocompleteQuery(options));
var cachedResponse = this.cache.getAutocompleteQuery(options);
if (cachedResponse)
return callback(null, cachedResponse, null, options);
if (!this.strictAuthorisation) {
queryString["api_key"] = this.api_key;
delete headers["Authorization"];
}
IdealPostcodes.Transport.request({
url: this.apiUrl() + "/autocomplete/addresses",
headers: headers,
queryString: queryString
}, function (error, data, xhr) {
if (error)
return callback(error, null, xhr, options);
_this.cache.cacheAutocompleteQuery(options, data.result);
return callback(null, data.result, xhr, options);
});
};
Client.prototype.lookupUdprn = function (options, callback) {
var _this = this;
options.api_key = this.api_key;
var headers = constructHeaders(options);
var queryString = constructQuery(options);
var cachedResponse = this.cache.getUdprnQuery(options);
if (cachedResponse)
return callback(null, cachedResponse);
IdealPostcodes.Transport.request({
url: this.apiUrl() + "/udprn/" + options.id,
headers: headers,
queryString: queryString
}, function (error, data, xhr) {
if (error)
return callback(error, null, xhr);
_this.cache.cacheUdprnQuery(options, data.result);
return callback(null, data.result, xhr);
});
};
Client.prototype.lookupUmprn = function (options, callback) {
var _this = this;
options.api_key = this.api_key;
var headers = constructHeaders(options);
var queryString = constructQuery(options);
var cachedResponse = this.cache.getUmprnQuery(options);
if (cachedResponse)
return callback(null, cachedResponse);
IdealPostcodes.Transport.request({
url: this.apiUrl() + "/umprn/" + options.id,
headers: headers,
queryString: queryString
}, function (error, data, xhr) {
if (error)
return callback(error, null, xhr);
_this.cache.cacheUmprnQuery(options, data.result);
return callback(null, data.result, xhr);
});
};
Client.prototype.checkKeyUsability = function (options, callback) {
IdealPostcodes.Transport.request({
url: this.apiUrl() + "/keys/" + this.api_key,
queryString: constructQuery(options)
}, function (error, data, xhr) {
if (error)
return callback(error, null, xhr);
return callback(null, data.result, xhr);
});
};
Client.prototype.autocompleteAddress = function (options) {
this.debouncedAutocomplete(options);
};
Client.prototype.registerAutocompleteCallback = function (callback) {
this.autocompleteCallback = callback;
};
return Client;
}());
IdealPostcodes.Client = Client;
})(IdealPostcodes || (IdealPostcodes = {}));