ggez-banking-sdk 0.4.32 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api/api.js CHANGED
@@ -39,7 +39,15 @@ class API {
39
39
  this.user = new UserProxy(proxyData);
40
40
  }
41
41
  async logout() {
42
- await this.cookiesHelper.clearCredentialCookies();
42
+ try {
43
+ const deviceId = await this.cookiesHelper.getDeviceId();
44
+ if (deviceId) {
45
+ await this.user.logoutDevice({ id: deviceId });
46
+ }
47
+ }
48
+ finally {
49
+ await this.cookiesHelper.clearCredentialCookies();
50
+ }
43
51
  }
44
52
  }
45
53
  export { API };
@@ -1,4 +1,5 @@
1
1
  import type { ClientContextProvider, InitialContext } from "../../types/api/context/clientContext";
2
+ import { GeoHelper } from "../../helper/geoHelper";
2
3
  declare class DefaultClientContextProvider implements ClientContextProvider {
3
4
  private baseUrl;
4
5
  private nodeUrl;
@@ -7,7 +8,9 @@ declare class DefaultClientContextProvider implements ClientContextProvider {
7
8
  private installationId;
8
9
  private token;
9
10
  private userId;
11
+ private readonly geoHelper;
10
12
  constructor(init: InitialContext);
13
+ getGeoHelper(): GeoHelper;
11
14
  getBaseUrl(): string;
12
15
  getNodeUrl(): string;
13
16
  getProgramId(): number;
@@ -1,3 +1,4 @@
1
+ import { GeoHelper } from "../../helper/geoHelper";
1
2
  class DefaultClientContextProvider {
2
3
  baseUrl;
3
4
  nodeUrl;
@@ -6,6 +7,7 @@ class DefaultClientContextProvider {
6
7
  installationId;
7
8
  token;
8
9
  userId;
10
+ geoHelper;
9
11
  constructor(init) {
10
12
  this.baseUrl = init.baseUrl;
11
13
  this.nodeUrl = init.nodeUrl;
@@ -14,6 +16,10 @@ class DefaultClientContextProvider {
14
16
  this.installationId = init.installationId ?? "";
15
17
  this.token = init.token ?? "";
16
18
  this.userId = init.userId ?? 0;
19
+ this.geoHelper = new GeoHelper(init.baseUrl);
20
+ }
21
+ getGeoHelper() {
22
+ return this.geoHelper;
17
23
  }
18
24
  getBaseUrl() {
19
25
  return this.baseUrl;
@@ -17,7 +17,7 @@ class BaseService {
17
17
  }
18
18
  // #region "Interceptors Handlers"
19
19
  async onRequest(req) {
20
- await AxiosHelper.injectBaseHeaders(req, this.context, this.cookiesHelper, this.axiosInstance);
20
+ await AxiosHelper.injectBaseHeaders(req, this.context, this.cookiesHelper);
21
21
  AxiosHelper.injectBaseBodyProperties(req);
22
22
  return req;
23
23
  }
@@ -1,4 +1,5 @@
1
- import { AxiosInstance, InternalAxiosRequestConfig } from "axios";
1
+ import { InternalAxiosRequestConfig } from "axios";
2
+ import type { GeoHelper } from "../geoHelper";
2
3
  import type { Bearer, GetLimitedAuthHeaders, GetLimitedToken, InjectBaseHeaders, InjectLimitedToken, TAddAxiosConfigHeader, TGetAuthAxiosConfig, TGetAxiosConfig, TInjectRequest } from "../../types/helper/api/axiosHelper";
3
4
  import type { BaseResult } from "../../types/banking/common/baseresult";
4
5
  declare class AxiosHelper {
@@ -11,6 +12,6 @@ declare class AxiosHelper {
11
12
  static getLimitedToken: GetLimitedToken;
12
13
  static getLimitedAuthHeaders: GetLimitedAuthHeaders;
13
14
  static injectLimitedToken: InjectLimitedToken;
14
- static injectGeoCoordinates: (req: InternalAxiosRequestConfig, axiosInstance: AxiosInstance) => Promise<InternalAxiosRequestConfig>;
15
+ static injectGeoCoordinates: (req: InternalAxiosRequestConfig, geoHelper: GeoHelper) => Promise<InternalAxiosRequestConfig>;
15
16
  }
16
17
  export { AxiosHelper };
@@ -1,7 +1,6 @@
1
1
  import { generateSourceID } from "../../utils/data/generation";
2
2
  import { isEntityEndpoint } from "../../utils/api/endpointUtils";
3
3
  import { DateTimeHelper } from "../dateTimeHelper";
4
- import { GeoHelper } from "../geoHelper";
5
4
  import qs from "qs";
6
5
  import { HeaderKeys } from "../../constant/constant";
7
6
  class AxiosHelper {
@@ -43,7 +42,7 @@ class AxiosHelper {
43
42
  config.headers = {};
44
43
  config.headers[key] = value;
45
44
  };
46
- static injectBaseHeaders = async (req, context, cookiesHelper, axiosInstance) => {
45
+ static injectBaseHeaders = async (req, context, cookiesHelper) => {
47
46
  const token = await cookiesHelper.getAccessToken();
48
47
  const userId = await cookiesHelper.getUserId();
49
48
  const lang = context.getLang();
@@ -59,7 +58,7 @@ class AxiosHelper {
59
58
  if (!req.baseURL)
60
59
  req.baseURL = context.getBaseUrl();
61
60
  req.headers.set(HeaderKeys.TimeZone, DateTimeHelper.getClientTimeZone());
62
- await AxiosHelper.injectGeoCoordinates(req, axiosInstance);
61
+ await AxiosHelper.injectGeoCoordinates(req, context.getGeoHelper());
63
62
  return req;
64
63
  };
65
64
  static injectBaseBodyProperties = (req) => {
@@ -103,10 +102,9 @@ class AxiosHelper {
103
102
  }
104
103
  return req;
105
104
  };
106
- static injectGeoCoordinates = async (req, axiosInstance) => {
105
+ static injectGeoCoordinates = async (req, geoHelper) => {
107
106
  if (!isEntityEndpoint(req.url))
108
107
  return req;
109
- const geoHelper = new GeoHelper(axiosInstance);
110
108
  const geoCoordinates = await geoHelper.getGeoCoordinates();
111
109
  if (req.data && typeof req.data === "object") {
112
110
  const data = req.data;
@@ -1,15 +1,19 @@
1
- import type { AxiosInstance } from "axios";
2
1
  import type { GeoCoordinates } from "../types";
3
2
  declare class GeoHelper {
4
- private static readonly CACHE_KEY;
3
+ private static readonly CACHE_KEY_PREFIX;
5
4
  private static readonly DEFAULT_TTL_MS;
6
5
  private static cache;
6
+ private static pending;
7
+ private readonly baseUrl;
7
8
  private readonly axiosInstance;
8
- constructor(axiosInstance: AxiosInstance);
9
- private static getCachedGeoCoordinates;
10
- private static setCachedGeoCoordinates;
9
+ constructor(baseUrl: string);
11
10
  static clearCache(): void;
11
+ private static storageKey;
12
+ private static readCache;
13
+ private static writeCache;
12
14
  private fetchIPAddressAndLocation;
15
+ private fetchSharedLocation;
16
+ private loadGeoData;
13
17
  private static toGeoCoordinates;
14
18
  private static fallbackGeoCoordinates;
15
19
  getGeoCoordinates(ttlMs?: number, forceRefresh?: boolean): Promise<GeoCoordinates>;
@@ -1,71 +1,134 @@
1
+ import axios from "axios";
1
2
  import { Endpoints } from "../constant/constant";
2
3
  class GeoHelper {
3
- static CACHE_KEY = "geo_coordinates_cache";
4
+ // #region Constants & State
5
+ static CACHE_KEY_PREFIX = "geo_coordinates_cache";
4
6
  static DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
5
- static cache = null;
7
+ static cache = new Map();
8
+ static pending = new Map();
9
+ baseUrl;
6
10
  axiosInstance;
7
- constructor(axiosInstance) {
8
- this.axiosInstance = axiosInstance;
11
+ // #endregion
12
+ // #region Constructor
13
+ constructor(baseUrl) {
14
+ this.baseUrl = baseUrl;
15
+ this.axiosInstance = axios.create({ baseURL: baseUrl });
9
16
  }
10
- static getCachedGeoCoordinates(ttlMs = GeoHelper.DEFAULT_TTL_MS) {
11
- if (GeoHelper.cache) {
12
- const age = Date.now() - GeoHelper.cache.timestamp;
13
- if (age < ttlMs)
14
- return GeoHelper.cache.geo_coordinates;
15
- GeoHelper.cache = null;
16
- }
17
+ // #endregion
18
+ // #region Cache
19
+ static clearCache() {
20
+ GeoHelper.cache.clear();
21
+ GeoHelper.pending.clear();
17
22
  try {
18
- const cachedDataStr = localStorage.getItem(GeoHelper.CACHE_KEY);
19
- if (cachedDataStr) {
20
- const cachedData = JSON.parse(cachedDataStr);
21
- const age = Date.now() - cachedData.timestamp;
22
- if (age < ttlMs) {
23
- GeoHelper.cache = cachedData;
24
- return cachedData.geo_coordinates;
23
+ const keysToRemove = [];
24
+ for (let i = 0; i < localStorage.length; i++) {
25
+ const key = localStorage.key(i);
26
+ if (key?.startsWith(GeoHelper.CACHE_KEY_PREFIX)) {
27
+ keysToRemove.push(key);
25
28
  }
26
- localStorage.removeItem(GeoHelper.CACHE_KEY);
27
29
  }
30
+ keysToRemove.forEach((key) => localStorage.removeItem(key));
28
31
  }
29
- catch (error) {
30
- console.warn("Failed to read geo coordinates from cache:", error);
32
+ catch {
33
+ // Ignore errors when clearing cache
31
34
  }
32
- return null;
33
35
  }
34
- static setCachedGeoCoordinates(geo_coordinates, ip_address) {
35
- const cachedData = {
36
- geo_coordinates,
37
- ip_address,
38
- timestamp: Date.now(),
39
- };
40
- GeoHelper.cache = cachedData;
36
+ static storageKey(baseUrl) {
37
+ return `${GeoHelper.CACHE_KEY_PREFIX}:${baseUrl}`;
38
+ }
39
+ static readCache(baseUrl, ttlMs) {
40
+ const memEntry = GeoHelper.cache.get(baseUrl);
41
+ if (memEntry) {
42
+ if (Date.now() - memEntry.timestamp < ttlMs)
43
+ return memEntry;
44
+ GeoHelper.cache.delete(baseUrl);
45
+ }
41
46
  try {
42
- localStorage.setItem(GeoHelper.CACHE_KEY, JSON.stringify(cachedData));
47
+ const cachedDataStr = localStorage.getItem(GeoHelper.storageKey(baseUrl));
48
+ if (!cachedDataStr)
49
+ return null;
50
+ const cachedData = JSON.parse(cachedDataStr);
51
+ if (Date.now() - cachedData.timestamp < ttlMs) {
52
+ GeoHelper.cache.set(baseUrl, cachedData);
53
+ return cachedData;
54
+ }
55
+ localStorage.removeItem(GeoHelper.storageKey(baseUrl));
43
56
  }
44
57
  catch (error) {
45
- console.warn("Failed to store geo coordinates in cache:", error);
58
+ console.warn("Failed to read geo coordinates from cache:", error);
46
59
  }
60
+ return null;
47
61
  }
48
- static clearCache() {
49
- GeoHelper.cache = null;
62
+ static writeCache(baseUrl, data) {
63
+ GeoHelper.cache.set(baseUrl, data);
50
64
  try {
51
- localStorage.removeItem(GeoHelper.CACHE_KEY);
65
+ localStorage.setItem(GeoHelper.storageKey(baseUrl), JSON.stringify(data));
52
66
  }
53
- catch {
54
- // Ignore errors when clearing cache
67
+ catch (error) {
68
+ console.warn("Failed to store geo coordinates in cache:", error);
55
69
  }
56
70
  }
71
+ // #endregion
72
+ // #region Helpers
57
73
  async fetchIPAddressAndLocation() {
58
74
  const response = await this.axiosInstance.get(Endpoints.IPAddress);
59
75
  const ipAddressAndLocation = response.data.value;
60
76
  if (!ipAddressAndLocation)
61
77
  return null;
62
- return JSON.parse(ipAddressAndLocation);
78
+ try {
79
+ return JSON.parse(ipAddressAndLocation);
80
+ }
81
+ catch (error) {
82
+ console.warn("Failed to parse IP address and location:", error);
83
+ return null;
84
+ }
85
+ }
86
+ // Dedupes concurrent fetches per baseURL so N callers on cold cache
87
+ // result in 1 network call instead of N.
88
+ fetchSharedLocation() {
89
+ const existing = GeoHelper.pending.get(this.baseUrl);
90
+ if (existing)
91
+ return existing;
92
+ const inflight = this.fetchIPAddressAndLocation();
93
+ GeoHelper.pending.set(this.baseUrl, inflight);
94
+ const cleanup = () => {
95
+ if (GeoHelper.pending.get(this.baseUrl) === inflight) {
96
+ GeoHelper.pending.delete(this.baseUrl);
97
+ }
98
+ };
99
+ inflight.then(cleanup, cleanup);
100
+ return inflight;
101
+ }
102
+ async loadGeoData(ttlMs, forceRefresh) {
103
+ if (!forceRefresh && ttlMs > 0) {
104
+ const cached = GeoHelper.readCache(this.baseUrl, ttlMs);
105
+ if (cached)
106
+ return cached;
107
+ }
108
+ const location = await this.fetchSharedLocation();
109
+ // Failed fetch returns fallback but does NOT cache it — a transient
110
+ // network blip should not poison the cache for 24h.
111
+ if (!location) {
112
+ return {
113
+ geo_coordinates: GeoHelper.fallbackGeoCoordinates(),
114
+ ip_address: "",
115
+ timestamp: Date.now(),
116
+ };
117
+ }
118
+ const data = {
119
+ geo_coordinates: GeoHelper.toGeoCoordinates(location),
120
+ ip_address: location.ip_address || "",
121
+ timestamp: Date.now(),
122
+ };
123
+ if (ttlMs > 0)
124
+ GeoHelper.writeCache(this.baseUrl, data);
125
+ return data;
63
126
  }
64
127
  static toGeoCoordinates(location) {
65
128
  const { latitude, longitude, city, country } = location;
66
129
  return {
67
- latitude: latitude || 0,
68
- longitude: longitude || 0,
130
+ latitude: latitude ?? 0,
131
+ longitude: longitude ?? 0,
69
132
  position_description: `${city || "N/A"}, ${country || "N/A"}`,
70
133
  };
71
134
  }
@@ -76,63 +139,22 @@ class GeoHelper {
76
139
  position_description: "N/A, N/A",
77
140
  };
78
141
  }
142
+ // #endregion
143
+ // #region Public API
79
144
  async getGeoCoordinates(ttlMs = GeoHelper.DEFAULT_TTL_MS, forceRefresh = false) {
80
- if (!forceRefresh && ttlMs > 0) {
81
- const cached = GeoHelper.getCachedGeoCoordinates(ttlMs);
82
- if (cached)
83
- return cached;
84
- }
85
- const location = await this.fetchIPAddressAndLocation();
86
- const geo_coordinates = location
87
- ? GeoHelper.toGeoCoordinates(location)
88
- : GeoHelper.fallbackGeoCoordinates();
89
- if (ttlMs > 0)
90
- GeoHelper.setCachedGeoCoordinates(geo_coordinates);
91
- return geo_coordinates;
145
+ const data = await this.loadGeoData(ttlMs, forceRefresh);
146
+ return data.geo_coordinates;
92
147
  }
93
148
  async getIPAddress(ttlMs = GeoHelper.DEFAULT_TTL_MS, forceRefresh = false) {
94
- if (!forceRefresh && ttlMs > 0 && GeoHelper.cache?.ip_address) {
95
- const cached = GeoHelper.getCachedGeoCoordinates(ttlMs);
96
- if (cached && GeoHelper.cache?.ip_address) {
97
- return GeoHelper.cache.ip_address;
98
- }
99
- }
100
- const location = await this.fetchIPAddressAndLocation();
101
- if (!location)
102
- return "";
103
- const { ip_address } = location;
104
- if (ttlMs > 0 && ip_address) {
105
- GeoHelper.setCachedGeoCoordinates(GeoHelper.toGeoCoordinates(location), ip_address);
106
- }
107
- return ip_address || "";
149
+ const data = await this.loadGeoData(ttlMs, forceRefresh);
150
+ return data.ip_address ?? "";
108
151
  }
109
152
  async getGeoCoordinatesAndIPAddress(ttlMs = GeoHelper.DEFAULT_TTL_MS, forceRefresh = false) {
110
- if (!forceRefresh && ttlMs > 0) {
111
- const cached = GeoHelper.getCachedGeoCoordinates(ttlMs);
112
- if (cached && GeoHelper.cache?.ip_address) {
113
- return {
114
- geo_coordinates: cached,
115
- ip_address: GeoHelper.cache.ip_address,
116
- };
117
- }
118
- }
119
- const location = await this.fetchIPAddressAndLocation();
120
- if (location) {
121
- const geo_coordinates = GeoHelper.toGeoCoordinates(location);
122
- const ip_address = location.ip_address || "";
123
- if (ttlMs > 0) {
124
- GeoHelper.setCachedGeoCoordinates(geo_coordinates, ip_address);
125
- }
126
- return { geo_coordinates, ip_address };
127
- }
128
- const fallback = {
129
- geo_coordinates: GeoHelper.fallbackGeoCoordinates(),
130
- ip_address: "",
153
+ const data = await this.loadGeoData(ttlMs, forceRefresh);
154
+ return {
155
+ geo_coordinates: data.geo_coordinates,
156
+ ip_address: data.ip_address ?? "",
131
157
  };
132
- if (ttlMs > 0) {
133
- GeoHelper.setCachedGeoCoordinates(fallback.geo_coordinates, fallback.ip_address);
134
- }
135
- return fallback;
136
158
  }
137
159
  }
138
160
  export { GeoHelper };
@@ -499,16 +499,16 @@ class CookiesHelper {
499
499
  }
500
500
  async clearCredentialCookies() {
501
501
  try {
502
+ this.cachedUSR = null;
503
+ this.cachedDEK = null;
504
+ this.cachedAccessToken = null;
505
+ this.cachedJWTToken = null;
502
506
  await Promise.all([
503
507
  this.remove("USR"),
504
508
  this.remove("DEK"),
505
509
  this.remove("access_token"),
506
510
  this.remove("jwt_token"),
507
511
  ]);
508
- this.cachedUSR = null;
509
- this.cachedDEK = null;
510
- this.cachedAccessToken = null;
511
- this.cachedJWTToken = null;
512
512
  }
513
513
  catch (error) {
514
514
  this.errorHandler(error);
@@ -1,3 +1,4 @@
1
+ import type { GeoHelper } from "../../../helper/geoHelper";
1
2
  type InitialContext = {
2
3
  baseUrl: string;
3
4
  nodeUrl: string;
@@ -22,5 +23,6 @@ interface ClientContextProvider {
22
23
  getProgramId(): number;
23
24
  getLang(): string;
24
25
  setLang(lang: string): void;
26
+ getGeoHelper(): GeoHelper;
25
27
  }
26
28
  export type { InitialContext, ClientContextProvider };
@@ -1,4 +1,4 @@
1
- import { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig } from "axios";
1
+ import { AxiosRequestConfig, InternalAxiosRequestConfig } from "axios";
2
2
  import { ClientContextProvider } from "../..";
3
3
  import { CookiesHelper } from "../../..";
4
4
  import type { AuthService } from "../../../api/service/authService";
@@ -6,12 +6,11 @@ type TGetAxiosConfig = (token: string, baseURL: string, lang?: string, installat
6
6
  type TGetAuthAxiosConfig = (baseURL: string, lang: string, installationId: string) => AxiosRequestConfig;
7
7
  type TAddAxiosConfigHeader = (config: AxiosRequestConfig, key: string, value: string) => void;
8
8
  type TInjectRequest<D> = (req: InternalAxiosRequestConfig<D>) => InternalAxiosRequestConfig<D>;
9
- type InjectRequiredHeaders<D> = (req: InternalAxiosRequestConfig, userId: number) => InternalAxiosRequestConfig<D>;
10
- type InjectBaseHeaders<D> = (req: InternalAxiosRequestConfig, context: ClientContextProvider, cookiesHelper: CookiesHelper, axiosInstance: AxiosInstance) => Promise<InternalAxiosRequestConfig<D>>;
9
+ type InjectBaseHeaders<D> = (req: InternalAxiosRequestConfig, context: ClientContextProvider, cookiesHelper: CookiesHelper) => Promise<InternalAxiosRequestConfig<D>>;
11
10
  type InjectLimitedToken = (req: InternalAxiosRequestConfig, authService: AuthService, cookiesHelper: CookiesHelper) => Promise<InternalAxiosRequestConfig>;
12
11
  type GetLimitedToken = (authService: AuthService, cookiesHelper: CookiesHelper) => Promise<string | null>;
13
12
  type GetLimitedAuthHeaders = (authService: AuthService, cookiesHelper: CookiesHelper) => Promise<{
14
13
  Authorization: string;
15
14
  } | undefined>;
16
15
  type Bearer = (token: string) => string;
17
- export { TGetAxiosConfig, TGetAuthAxiosConfig, TAddAxiosConfigHeader, TInjectRequest, InjectBaseHeaders, InjectRequiredHeaders, InjectLimitedToken, GetLimitedToken, GetLimitedAuthHeaders, Bearer, };
16
+ export { TGetAxiosConfig, TGetAuthAxiosConfig, TAddAxiosConfigHeader, TInjectRequest, InjectBaseHeaders, InjectLimitedToken, GetLimitedToken, GetLimitedAuthHeaders, Bearer, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ggez-banking-sdk",
3
- "version": "0.4.32",
3
+ "version": "0.5.2",
4
4
  "description": "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.",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",