UNPKG

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.

664 lines (663 loc) 22.7 kB
import { v4 } from "uuid"; import DocumentCookie from "js-cookie"; import { CipherHelper } from "../cipherHelper"; class CookiesHelper { // #region "Properties" key; pendingIID = null; eventHandlers = new Map(); domain = undefined; programId = "0"; Cookies = null; useCookieStore = false; cipherHelper; errorHandler; cachedIID = null; cachedUSR = null; cachedDEK = null; cachedAccessToken = null; cachedJWTToken = null; // #endregion // #region "Constructor" constructor(programId, domain, errorHandler) { this.key = v4(); this.programId = programId; this.errorHandler = errorHandler; this.domain = domain; this.cipherHelper = new CipherHelper(this.errorHandler); // Check if cookieStore API is available (requires HTTPS or localhost) this.useCookieStore = this.isCookieStoreAvailable(); if (this.useCookieStore && window.cookieStore) { this.Cookies = window.cookieStore; } } // #endregion // #region "Utils" /** * Check if CookieStore API is available * CookieStore requires secure context (HTTPS or localhost) */ isCookieStoreAvailable() { return (typeof window !== "undefined" && "cookieStore" in window && window.cookieStore !== null && window.cookieStore !== undefined); } isSecureContext() { if (typeof window === "undefined") return false; return window.isSecureContext; } buildCookieName(key) { return `${key}-${this.programId}`; } clearCacheForKey(cookieName) { switch (cookieName) { case `IID-${this.programId}`: this.cachedIID = null; this.cachedUSR = null; this.cachedDEK = null; this.cachedAccessToken = null; break; case `USR-${this.programId}`: this.cachedUSR = null; this.cachedDEK = null; break; case `DEK-${this.programId}`: this.cachedDEK = null; break; case `access_token-${this.programId}`: this.cachedAccessToken = null; break; case `jwt_token-${this.programId}`: this.cachedJWTToken = null; break; } } // #endregion // #region "Fallback Methods" setCookieFallback(name, value, expires, domain) { const isSecure = this.isSecureContext(); DocumentCookie.set(name, value, { expires: expires ? new Date(expires) : undefined, path: "/", domain: domain, secure: isSecure, sameSite: isSecure ? "none" : "lax", }); } getCookieFallback(name) { if (typeof document === "undefined") return ""; return DocumentCookie.get(name) || ""; } deleteCookieFallback(name, domain) { DocumentCookie.remove(name, { path: "/", domain: domain, }); } // #endregion // #region "IID" async getIID() { if (this.cachedIID) return this.cachedIID; if (this.pendingIID) return this.pendingIID; this.pendingIID = this._fetchOrCreateIID().finally(() => { this.pendingIID = null; }); return this.pendingIID; } async _fetchOrCreateIID() { try { const installationId = await this.get("IID"); if (!installationId) { return await this.setIID(); } const { key, iv } = this.cipherHelper.generateByProgramID(this.programId); const { IID } = this.cipherHelper.decryptAsJson(installationId, key, iv); if (!IID) { await this.remove("IID"); return await this.setIID(); } this.cachedIID = IID; return IID; } catch (error) { this.errorHandler(error); return ""; } } async setIID() { try { const IID = v4(); const { key, iv } = this.cipherHelper.generateByProgramID(this.programId); const encryptedIID = this.cipherHelper.encryptAsJson(IID, "IID", key, iv); await this.set("IID", encryptedIID); this.cachedIID = IID; return IID; } catch (error) { this.errorHandler(error); return ""; } } async validateIID(installationId) { try { const { key, iv } = this.cipherHelper.generateByProgramID(this.programId); const { IID } = this.cipherHelper.decryptAsJson(installationId, key, iv); return !!IID; } catch (error) { this.errorHandler(error); return false; } } // #endregion // #region "DEK" async getDEK() { try { if (this.cachedDEK) return this.cachedDEK; const [IID, DEK, USR] = await Promise.all([ this.getIID(), this.get("DEK"), this.getUSR(), ]); if (!DEK || !USR || !IID) { return ""; } const { key, iv } = this.cipherHelper.generateByUSRAndIID(USR, IID); const { DEK: encryptionKey } = this.cipherHelper.decryptAsJson(DEK, key, iv); if (encryptionKey.length == 16) { this.cachedDEK = encryptionKey; return encryptionKey; } return ""; } catch (error) { this.errorHandler(error); return ""; } } async setDEK(deviceEncryptionKey, USR) { try { const IID = await this.getIID(); if (!USR || !IID) return; const { user_id } = USR; const { key, iv } = this.cipherHelper.generateByUSRAndIID(USR, IID); if (deviceEncryptionKey) { const cipherKeys = this.cipherHelper.generateByUserID(user_id); const encryptionKey = this.cipherHelper.decrypt(deviceEncryptionKey, cipherKeys.key, cipherKeys.iv); const DEK = this.cipherHelper.encryptAsJson(encryptionKey, "DEK", key, iv); await this.set("DEK", DEK); if (encryptionKey.length === 16) { this.cachedDEK = encryptionKey; } } } catch (error) { this.errorHandler(error); } } async validateDEK(DEK) { try { const IID = this.cachedIID; const USR = this.cachedUSR; if (!DEK || !USR || !IID) return false; const { key, iv } = this.cipherHelper.generateByUSRAndIID(USR, IID); const { DEK: decryptedDEK } = this.cipherHelper.decryptAsJson(DEK, key, iv); return !!decryptedDEK; } catch (error) { this.errorHandler(error); return false; } } // #endregion // #region "User ID / Device ID" async getUserId() { try { const USR = await this.getUSR(); return Number(USR?.user_id || 0); } catch (error) { this.errorHandler(error); return 0; } } async getDeviceId() { try { const USR = await this.getUSR(); return Number(USR?.device_id || 0); } catch (error) { this.errorHandler(error); return 0; } } // #endregion // #region "USR" async getUSR() { try { if (this.cachedUSR) return this.cachedUSR; const [IID, USR] = await Promise.all([this.getIID(), this.get("USR")]); if (!IID || !USR) return null; const { key, iv } = this.cipherHelper.generateByInstallationID(IID); const { USR: decryptedUSR } = this.cipherHelper.decryptAsJson(USR, key, iv); if (!decryptedUSR) return null; const parsed = JSON.parse(decryptedUSR); this.cachedUSR = parsed; return parsed; } catch (error) { this.errorHandler(error); return null; } } async setUSR(deviceId, userId) { try { const IID = await this.getIID(); if (!IID) return; const { key, iv } = this.cipherHelper.generateByInstallationID(IID); const usr = { device_id: deviceId, user_id: userId }; const USR = JSON.stringify(usr); const encryptedUSR = this.cipherHelper.encryptAsJson(USR, "USR", key, iv); await this.set("USR", encryptedUSR); this.cachedUSR = usr; } catch (error) { this.errorHandler(error); } } async validateUSR(USR) { try { const IID = this.cachedIID; if (!IID) return false; const { key, iv } = this.cipherHelper.generateByInstallationID(IID); const { USR: decryptedUSR } = this.cipherHelper.decryptAsJson(USR, key, iv); return !!decryptedUSR; } catch (error) { this.errorHandler(error); return false; } } // #endregion // #region "Device Security Code" async getDeviceSecurityCode() { try { const [IID, USR, DEK] = await Promise.all([ this.getIID(), this.getUSR(), this.getDEK(), ]); if (!DEK || !USR || !IID) { return ""; } const { key, iv } = this.cipherHelper.generateByUserID(USR.user_id.toString()); const DUID = IID + "." + DEK; // const encryptedJSON = this.cipherHelper.EncryptAsJson( // DUID, // "device_security_code", // key, // iv // ); // const { device_security_code } = JSON.parse(encryptedJSON); const device_security_code = this.cipherHelper.encrypt(DUID, key, iv); return device_security_code; } catch (error) { this.errorHandler(error); return ""; } } // #endregion // #region "Access Token" async getAccessToken() { try { if (this.cachedAccessToken) return this.cachedAccessToken; const [IID, accessToken] = await Promise.all([ this.getIID(), this.get("access_token"), ]); if (!accessToken || !IID) return ""; const { key, iv } = this.cipherHelper.generateByInstallationID(IID); const { access_token } = this.cipherHelper.decryptAsJson(accessToken, key, iv); this.cachedAccessToken = access_token; return access_token; } catch (error) { this.errorHandler(error); return ""; } } async setAccessToken(accessToken, expires) { try { const IID = await this.getIID(); if (!IID) return; const { key, iv } = this.cipherHelper.generateByInstallationID(IID); const encryptedAccessToken = this.cipherHelper.encryptAsJson(accessToken, "access_token", key, iv); await this.set("access_token", encryptedAccessToken, expires); this.cachedAccessToken = accessToken; } catch (error) { this.errorHandler(error); } } async validateAccessToken(accessToken) { try { const IID = this.cachedIID; if (!IID) return false; const { key, iv } = this.cipherHelper.generateByInstallationID(IID); const { access_token } = this.cipherHelper.decryptAsJson(accessToken, key, iv); return !!access_token; } catch (error) { this.errorHandler(error); return false; } } // #endregion // #region "JWT Token" async getJWTToken() { try { if (this.cachedJWTToken) return this.cachedJWTToken; const jwtToken = await this.get("jwt_token"); if (!jwtToken) return ""; const { key, iv } = this.cipherHelper.generateByProgramID(this.programId); const { jwt_token } = this.cipherHelper.decryptAsJson(jwtToken, key, iv); this.cachedJWTToken = jwt_token; return jwt_token; } catch (error) { this.errorHandler(error); return ""; } } async setJWTToken(jwtToken) { try { const { key, iv } = this.cipherHelper.generateByProgramID(this.programId); const encryptedJWTToken = this.cipherHelper.encryptAsJson(jwtToken, "jwt_token", key, iv); await this.set("jwt_token", encryptedJWTToken); this.cachedJWTToken = jwtToken; } catch (error) { this.errorHandler(error); } } async validateJWTToken(jwtToken) { try { const { key, iv } = this.cipherHelper.generateByProgramID(this.programId); const { jwt_token } = this.cipherHelper.decryptAsJson(jwtToken, key, iv); return !!jwt_token; } catch (error) { this.errorHandler(error); return false; } } // #endregion // #region "Getters & Setters" async get(key) { const cookieName = this.buildCookieName(key); try { if (this.useCookieStore && this.Cookies) { const cookie = await this.Cookies.get({ name: cookieName }); return cookie?.value || ""; } else { // Fallback to document.cookie return this.getCookieFallback(cookieName); } } catch (error) { // If cookieStore fails, fallback to document.cookie this.errorHandler(error); return this.getCookieFallback(cookieName); } } async set(key, value, expires) { const defaultExpireDate = Date.now() + 1000 * 60 * 60 * 24 * 365; const expireTime = expires ?? defaultExpireDate; const isSecure = this.isSecureContext(); const cookieName = this.buildCookieName(key); try { if (this.useCookieStore && this.Cookies) { await this.Cookies.set({ name: cookieName, value: value, domain: this.domain, expires: expireTime, path: "/", sameSite: isSecure ? "none" : "lax", secure: isSecure, }); } else { // Fallback to document.cookie this.setCookieFallback(cookieName, value, expireTime, this.domain); } } catch (error) { // If cookieStore fails, fallback to document.cookie this.errorHandler(error); this.setCookieFallback(cookieName, value, expireTime, this.domain); } } async remove(key) { const cookieName = this.buildCookieName(key); try { if (this.useCookieStore && this.Cookies) { await this.Cookies.delete({ name: cookieName, domain: this.domain, path: "/", }); } else { // Fallback to document.cookie this.deleteCookieFallback(cookieName, this.domain); } } catch (error) { // If cookieStore fails, fallback to document.cookie this.errorHandler(error); this.deleteCookieFallback(cookieName, this.domain); } } async setCredentialCookiesByTokenData(tokenData) { try { const { device_id, user_id, access_token, device_encryption_key, jwt_token, expires_in, } = tokenData; await Promise.all([ this.setAccessToken(access_token, Date.now() + expires_in * 1000), this.setJWTToken(jwt_token), this.setUSR(device_id, user_id), this.setDEK(device_encryption_key, { user_id, device_id, }), ]); } catch (error) { this.errorHandler(error); } } async clearCredentialCookies() { try { this.cachedUSR = null; this.cachedDEK = null; this.cachedAccessToken = null; this.cachedJWTToken = null; await Promise.all([ this.remove("USR"), this.remove("DEK"), this.remove("access_token"), this.remove("jwt_token"), ]); } catch (error) { this.errorHandler(error); } } // #endregion // #region "Cookie Change Event Listener" async cookieEventHandler(e, params) { if (e.changed.length > 0) { await this.onChangeHandler(e.changed, params); } if (e.deleted.length > 0) { await this.onDeleteHandler(e.deleted, params); } } async onChangeHandler(changed, params) { const eventType = "change"; for (const changedCookie of changed) { // const cookieName = this.parseCookieName(changedCookie.name); const cookieName = changedCookie.name; this.clearCacheForKey(cookieName); switch (cookieName) { case `IID-${this.programId}`: { const isValidIID = await this.validateIID(changedCookie.value); if (!isValidIID) params.IID(eventType); break; } case `USR-${this.programId}`: { const isValidUSR = await this.validateUSR(changedCookie.value); if (!isValidUSR) params.USR(eventType); break; } case `DEK-${this.programId}`: { const isValidDEK = await this.validateDEK(changedCookie.value); if (!isValidDEK) params.DEK(eventType); break; } case `access_token-${this.programId}`: { const isValidAT = await this.validateAccessToken(changedCookie.value); if (!isValidAT) params.accessToken(eventType); break; } case `jwt_token-${this.programId}`: { const isValidJWT = await this.validateJWTToken(changedCookie.value); if (!isValidJWT) params.jwtToken(eventType); break; } } } } async onDeleteHandler(deleted, params) { const eventType = "delete"; for (const deletedCookie of deleted) { const cookieName = deletedCookie.name; this.clearCacheForKey(cookieName); switch (cookieName) { case `IID-${this.programId}`: params.IID(eventType); break; case `USR-${this.programId}`: params.USR(eventType); break; case `DEK-${this.programId}`: params.DEK(eventType); break; case `access_token-${this.programId}`: params.accessToken(eventType); break; case `jwt_token-${this.programId}`: params.jwtToken(eventType); break; } } } addOnChangeEventListener(id, params) { if (this.useCookieStore && this.Cookies) { const handler = (e) => this.cookieEventHandler(e, params); this.eventHandlers.set(id, handler); this.Cookies.addEventListener("change", handler); } else { console.warn("[CookiesHelper] cookieStore is unavailable — change events will not fire. Requires a secure context (HTTPS or localhost)."); } } removeOnChangeEventListener(id) { if (this.useCookieStore && this.Cookies) { const handler = this.eventHandlers.get(id); if (handler) { this.Cookies.removeEventListener("change", handler); this.eventHandlers.delete(id); } } } // #endregion // #region "Checkers" async isAuthenticated() { try { const rawIID = await this.get("IID"); if (!rawIID) return false; const [IID, USR, DEK, accessToken, jwtToken] = await Promise.all([ this.getIID(), this.getUSR(), this.getDEK(), this.getAccessToken(), this.getJWTToken(), ]); return !!IID && !!USR && !!DEK && !!accessToken && !!jwtToken; } catch (error) { this.errorHandler(error); return false; } } // #endregion // #region "Utils" async getCredentialsCookies() { try { const [IID, USR, DEK, accessToken, jwtToken] = await Promise.all([ this.getIID(), this.getUSR(), this.getDEK(), this.getAccessToken(), this.getJWTToken(), ]); const credentials = { IID: IID || "", USR: USR || { user_id: "0", device_id: "0" }, DEK: DEK || "", access_token: accessToken || "", jwt_token: jwtToken || "", }; return credentials; } catch (error) { this.errorHandler(error); return { IID: "", USR: { user_id: "0", device_id: "0" }, DEK: "", access_token: "", jwt_token: "", }; } } } export { CookiesHelper };