UNPKG

@contiva/sap-integration-suite-client

Version:
153 lines (152 loc) 5.35 kB
"use strict"; /** * Cache key generation utilities * * @module cache-key-generator */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.generateCacheKey = generateCacheKey; exports.parseQueryParams = parseQueryParams; const crypto = __importStar(require("crypto")); /** * Recursively sorts object keys for deterministic JSON stringification. * This ensures the same object with different key ordering produces identical output. * * @param obj - The object to sort * @returns A new object with sorted keys (deep) * * @example * sortObjectKeys({ b: 2, a: 1 }) // { a: 1, b: 2 } * sortObjectKeys({ b: { d: 4, c: 3 }, a: 1 }) // { a: 1, b: { c: 3, d: 4 } } */ function sortObjectKeys(obj) { if (obj === null || obj === undefined) { return obj; } if (Array.isArray(obj)) { return obj.map(sortObjectKeys); } if (typeof obj === 'object') { const sorted = {}; const keys = Object.keys(obj).sort(); for (const key of keys) { sorted[key] = sortObjectKeys(obj[key]); } return sorted; } return obj; } /** * Generates a deterministic JSON string from an object. * Keys are sorted alphabetically at all nesting levels. * * FIX for P0-3 Bug: Different query parameter ordering now produces identical cache keys. * * @param obj - The object to stringify * @returns A deterministic JSON string * * @example * stableStringify({ b: 2, a: 1 }) // '{"a":1,"b":2}' * stableStringify({ a: 1, b: 2 }) // '{"a":1,"b":2}' (same!) */ function stableStringify(obj) { return JSON.stringify(sortObjectKeys(obj)); } /** * Generates a unique cache key for a request * * @param hostname - The SAP hostname * @param method - HTTP method (GET, POST, etc.) * @param url - The request URL * @param queryParams - Optional query parameters or body data * @returns A unique cache key * * @example * generateCacheKey('tenant.sap.com', 'GET', '/IntegrationPackages', { top: 10 }) * // Returns: 'sap:tenant.sap.com:GET:/IntegrationPackages:a1b2c3d4' */ function generateCacheKey(hostname, method, url, queryParams) { // Validate required parameters if (!hostname || !method || !url) { throw new Error('hostname, method, and url are required for cache key generation'); } // Normalize URL: Remove protocol and hostname if present, then remove /api/v1 prefix let normalizedUrl = url; if (normalizedUrl.startsWith('http://') || normalizedUrl.startsWith('https://')) { try { const urlObj = new URL(normalizedUrl); normalizedUrl = urlObj.pathname + urlObj.search; } catch (_a) { // Falls URL-Parsing fehlschlägt, verwende URL wie sie ist } } // Entferne /api/v1 Prefix normalizedUrl = normalizedUrl.replace(/^\/api\/v1/, ''); // Generate a hash for query parameters if present // FIX P0-3: Use stableStringify for deterministic cache keys regardless of parameter order let paramsHash = ''; if (queryParams && typeof queryParams === 'object' && Object.keys(queryParams).length > 0) { const paramsString = stableStringify(queryParams); // Use SHA-256 instead of deprecated MD5 paramsHash = crypto.createHash('sha256').update(paramsString).digest('hex').substring(0, 8); } // Construct the cache key const parts = ['sap', hostname, method, normalizedUrl]; if (paramsHash) { parts.push(paramsHash); } return parts.join(':'); } /** * Parses a URL to extract query parameters * * @param url - The URL to parse * @returns An object containing query parameters */ function parseQueryParams(url) { try { const urlObj = new URL(url, 'http://dummy.com'); // Use dummy base for relative URLs const params = {}; urlObj.searchParams.forEach((value, key) => { params[key] = value; }); return params; } catch (_a) { return {}; } }