ggez-banking-sdk
Version:
A Node.js package to handle GGEZ Banking API endpoints, Simplify the process of managing CRUD operations with this efficient and easy-to-use package.
161 lines (160 loc) • 5.63 kB
JavaScript
import axios from "axios";
import { Endpoints } from "../constant/constant";
class GeoHelper {
// #region Constants & State
static CACHE_KEY_PREFIX = "geo_coordinates_cache";
static DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
static cache = new Map();
static pending = new Map();
baseUrl;
axiosInstance;
// #endregion
// #region Constructor
constructor(baseUrl) {
this.baseUrl = baseUrl;
this.axiosInstance = axios.create({ baseURL: baseUrl });
}
// #endregion
// #region Cache
static clearCache() {
GeoHelper.cache.clear();
GeoHelper.pending.clear();
try {
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key?.startsWith(GeoHelper.CACHE_KEY_PREFIX)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach((key) => localStorage.removeItem(key));
}
catch {
// Ignore errors when clearing cache
}
}
static storageKey(baseUrl) {
return `${GeoHelper.CACHE_KEY_PREFIX}:${baseUrl}`;
}
static readCache(baseUrl, ttlMs) {
const memEntry = GeoHelper.cache.get(baseUrl);
if (memEntry) {
if (Date.now() - memEntry.timestamp < ttlMs)
return memEntry;
GeoHelper.cache.delete(baseUrl);
}
try {
const cachedDataStr = localStorage.getItem(GeoHelper.storageKey(baseUrl));
if (!cachedDataStr)
return null;
const cachedData = JSON.parse(cachedDataStr);
if (Date.now() - cachedData.timestamp < ttlMs) {
GeoHelper.cache.set(baseUrl, cachedData);
return cachedData;
}
localStorage.removeItem(GeoHelper.storageKey(baseUrl));
}
catch (error) {
console.warn("Failed to read geo coordinates from cache:", error);
}
return null;
}
static writeCache(baseUrl, data) {
GeoHelper.cache.set(baseUrl, data);
try {
localStorage.setItem(GeoHelper.storageKey(baseUrl), JSON.stringify(data));
}
catch (error) {
console.warn("Failed to store geo coordinates in cache:", error);
}
}
// #endregion
// #region Helpers
async fetchIPAddressAndLocation() {
const response = await this.axiosInstance.get(Endpoints.IPAddress);
const ipAddressAndLocation = response.data.value;
if (!ipAddressAndLocation)
return null;
try {
return JSON.parse(ipAddressAndLocation);
}
catch (error) {
console.warn("Failed to parse IP address and location:", error);
return null;
}
}
// Dedupes concurrent fetches per baseURL so N callers on cold cache
// result in 1 network call instead of N.
fetchSharedLocation() {
const existing = GeoHelper.pending.get(this.baseUrl);
if (existing)
return existing;
const inflight = this.fetchIPAddressAndLocation();
GeoHelper.pending.set(this.baseUrl, inflight);
const cleanup = () => {
if (GeoHelper.pending.get(this.baseUrl) === inflight) {
GeoHelper.pending.delete(this.baseUrl);
}
};
inflight.then(cleanup, cleanup);
return inflight;
}
async loadGeoData(ttlMs, forceRefresh) {
if (!forceRefresh && ttlMs > 0) {
const cached = GeoHelper.readCache(this.baseUrl, ttlMs);
if (cached)
return cached;
}
const location = await this.fetchSharedLocation();
// Failed fetch returns fallback but does NOT cache it — a transient
// network blip should not poison the cache for 24h.
if (!location) {
return {
geo_coordinates: GeoHelper.fallbackGeoCoordinates(),
ip_address: "",
timestamp: Date.now(),
};
}
const data = {
geo_coordinates: GeoHelper.toGeoCoordinates(location),
ip_address: location.ip_address || "",
timestamp: Date.now(),
};
if (ttlMs > 0)
GeoHelper.writeCache(this.baseUrl, data);
return data;
}
static toGeoCoordinates(location) {
const { latitude, longitude, city, country } = location;
return {
latitude: latitude ?? 0,
longitude: longitude ?? 0,
position_description: `${city || "N/A"}, ${country || "N/A"}`,
};
}
static fallbackGeoCoordinates() {
return {
latitude: 0,
longitude: 0,
position_description: "N/A, N/A",
};
}
// #endregion
// #region Public API
async getGeoCoordinates(ttlMs = GeoHelper.DEFAULT_TTL_MS, forceRefresh = false) {
const data = await this.loadGeoData(ttlMs, forceRefresh);
return data.geo_coordinates;
}
async getIPAddress(ttlMs = GeoHelper.DEFAULT_TTL_MS, forceRefresh = false) {
const data = await this.loadGeoData(ttlMs, forceRefresh);
return data.ip_address ?? "";
}
async getGeoCoordinatesAndIPAddress(ttlMs = GeoHelper.DEFAULT_TTL_MS, forceRefresh = false) {
const data = await this.loadGeoData(ttlMs, forceRefresh);
return {
geo_coordinates: data.geo_coordinates,
ip_address: data.ip_address ?? "",
};
}
}
export { GeoHelper };