varminer-app-header 2.1.5 → 2.1.6

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/index.js CHANGED
@@ -374,45 +374,167 @@ const getProfilePictureUrl = (baseUrl = "http://objectstore.impact0mics.local:90
374
374
  }
375
375
  };
376
376
  /**
377
- * Fetch profile picture from API with headers and return as blob URL
378
- * This function fetches the image with required headers (X-Message-Id, X-Correlation-Id)
379
- * and converts it to a blob URL that can be used in img src
377
+ * Generate AWS S3 presigned URL for accessing S3 object
378
+ * @param s3Url - Full S3 URL (e.g., https://bucket.s3.region.amazonaws.com/key)
379
+ * @param accessKeyId - AWS Access Key ID
380
+ * @param secretAccessKey - AWS Secret Access Key
381
+ * @param region - AWS Region (default: ap-south-2)
382
+ * @param expiresIn - Expiration time in seconds (default: 3600 = 1 hour)
383
+ * @returns Presigned URL string
384
+ */
385
+ const generateS3PresignedUrl = async (s3Url, accessKeyId, secretAccessKey, region = "ap-south-2", expiresIn = 3600) => {
386
+ try {
387
+ // Parse S3 URL to extract bucket, region, and key
388
+ // Format: https://bucket.s3.region.amazonaws.com/key or https://bucket.s3-region.amazonaws.com/key
389
+ const url = new URL(s3Url);
390
+ const hostnameParts = url.hostname.split('.');
391
+ let bucket = hostnameParts[0];
392
+ let extractedRegion = region;
393
+ // Try to extract region from hostname (format: bucket.s3.region.amazonaws.com)
394
+ if (hostnameParts.length >= 3 && hostnameParts[1] === 's3') {
395
+ extractedRegion = hostnameParts[2] || region;
396
+ }
397
+ else if (hostnameParts.length >= 2 && hostnameParts[1].startsWith('s3-')) {
398
+ // Format: bucket.s3-region.amazonaws.com
399
+ extractedRegion = hostnameParts[1].substring(3) || region;
400
+ }
401
+ const key = url.pathname.substring(1); // Remove leading slash
402
+ // AWS credentials
403
+ const awsAccessKeyId = accessKeyId;
404
+ const awsSecretAccessKey = secretAccessKey;
405
+ const awsRegion = extractedRegion;
406
+ // Current timestamp
407
+ const now = new Date();
408
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
409
+ const amzDate = dateStamp + 'T' + now.toISOString().slice(11, 19).replace(/[:-]/g, '') + 'Z';
410
+ // Step 1: Create canonical request
411
+ const canonicalUri = '/' + encodeURIComponent(key).replace(/%2F/g, '/');
412
+ const canonicalQuerystring = `X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${encodeURIComponent(awsAccessKeyId + '/' + dateStamp + '/' + awsRegion + '/s3/aws4_request')}&X-Amz-Date=${amzDate}&X-Amz-Expires=${expiresIn}&X-Amz-SignedHeaders=host`;
413
+ const canonicalHeaders = `host:${bucket}.s3.${awsRegion}.amazonaws.com\n`;
414
+ const signedHeaders = 'host';
415
+ const payloadHash = 'UNSIGNED-PAYLOAD';
416
+ const canonicalRequest = `GET\n${canonicalUri}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
417
+ // Step 2: Create string to sign
418
+ const algorithm = 'AWS4-HMAC-SHA256';
419
+ const credentialScope = `${dateStamp}/${awsRegion}/s3/aws4_request`;
420
+ const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${await sha256(canonicalRequest)}`;
421
+ // Step 3: Calculate signature
422
+ const kDate = await hmacSha256('AWS4' + awsSecretAccessKey, dateStamp);
423
+ const kRegion = await hmacSha256(kDate, awsRegion);
424
+ const kService = await hmacSha256(kRegion, 's3');
425
+ const kSigning = await hmacSha256(kService, 'aws4_request');
426
+ const signature = await hmacSha256(kSigning, stringToSign);
427
+ // Convert signature to hex
428
+ const signatureHex = arrayBufferToHex(signature);
429
+ // Step 4: Construct presigned URL
430
+ const presignedUrl = `https://${bucket}.s3.${awsRegion}.amazonaws.com${canonicalUri}?${canonicalQuerystring}&X-Amz-Signature=${signatureHex}`;
431
+ return presignedUrl;
432
+ }
433
+ catch (err) {
434
+ console.error("Error generating S3 presigned URL:", err);
435
+ throw err;
436
+ }
437
+ };
438
+ // Helper function for SHA-256 hashing
439
+ const sha256 = async (message) => {
440
+ const msgBuffer = new TextEncoder().encode(message);
441
+ const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
442
+ return Array.from(new Uint8Array(hashBuffer))
443
+ .map(b => b.toString(16).padStart(2, '0'))
444
+ .join('');
445
+ };
446
+ // Helper function for HMAC-SHA256
447
+ const hmacSha256 = async (key, message) => {
448
+ const encoder = new TextEncoder();
449
+ let keyBuffer;
450
+ if (typeof key === 'string') {
451
+ keyBuffer = encoder.encode(key);
452
+ }
453
+ else {
454
+ keyBuffer = new Uint8Array(key);
455
+ }
456
+ const messageBuffer = encoder.encode(message);
457
+ const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
458
+ return await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);
459
+ };
460
+ // Helper function to convert ArrayBuffer to hex string
461
+ const arrayBufferToHex = (buffer) => {
462
+ return Array.from(new Uint8Array(buffer))
463
+ .map(b => b.toString(16).padStart(2, '0'))
464
+ .join('');
465
+ };
466
+ /**
467
+ * Fetch profile picture from API with headers, get S3 URL, generate presigned URL, and return as blob URL
468
+ * This function:
469
+ * 1. Fetches the profile picture path from the API
470
+ * 2. Extracts the S3 filePath from the JSON response
471
+ * 3. Generates a presigned URL for the S3 object
472
+ * 4. Fetches the image using the presigned URL
473
+ * 5. Converts it to a blob URL that can be used in img src
380
474
  * @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
381
475
  * @param messageId - Optional message ID for X-Message-Id header (default: generated UUID)
382
476
  * @param correlationId - Optional correlation ID for X-Correlation-Id header (default: generated UUID)
477
+ * @param awsConfig - AWS configuration (accessKeyId, secretAccessKey, region, bucket)
383
478
  * @returns Promise that resolves to blob URL string or null if fetch fails
384
479
  */
385
- const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012", messageId, correlationId) => {
480
+ const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012", messageId, correlationId, awsConfig) => {
386
481
  try {
387
482
  const profilePictureUrl = getProfilePictureUrl(baseUrl);
388
483
  if (!profilePictureUrl) {
389
484
  return null;
390
485
  }
486
+ // AWS credentials (default values provided by user)
487
+ const defaultAwsConfig = {
488
+ accessKeyId: "AKIAVRUVTJGLBCYZEI5L",
489
+ secretAccessKey: "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",
490
+ region: "ap-south-2",
491
+ bucket: "development-varminer-test"
492
+ };
493
+ const finalAwsConfig = awsConfig || defaultAwsConfig;
391
494
  // Generate message ID and correlation ID if not provided
392
495
  const msgId = messageId || `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
393
496
  const corrId = correlationId || `corr-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
394
- // Fetch the image with required headers
395
- const response = await fetch(profilePictureUrl, {
497
+ // Step 1: Fetch the profile picture path from API (returns JSON with filePath)
498
+ const apiResponse = await fetch(profilePictureUrl, {
396
499
  method: 'GET',
397
500
  headers: {
398
501
  'X-Message-Id': msgId,
399
502
  'X-Correlation-Id': corrId,
400
503
  },
401
504
  });
402
- // Check if the response is successful
403
- if (!response.ok) {
404
- console.warn(`Failed to fetch profile picture: ${response.status} ${response.statusText}`);
505
+ // Check if the API response is successful
506
+ if (!apiResponse.ok) {
507
+ console.warn(`Failed to fetch profile picture path: ${apiResponse.status} ${apiResponse.statusText}`);
508
+ return null;
509
+ }
510
+ // Parse JSON response
511
+ const apiData = await apiResponse.json();
512
+ // Extract filePath from response
513
+ const s3Url = apiData?.filePath;
514
+ if (!s3Url || typeof s3Url !== 'string') {
515
+ console.warn('Profile picture API response does not contain valid filePath');
516
+ return null;
517
+ }
518
+ // Step 2: Generate presigned URL for S3 object
519
+ const presignedUrl = await generateS3PresignedUrl(s3Url, finalAwsConfig.accessKeyId, finalAwsConfig.secretAccessKey, finalAwsConfig.region);
520
+ // Step 3: Fetch the image using presigned URL
521
+ const imageResponse = await fetch(presignedUrl, {
522
+ method: 'GET',
523
+ });
524
+ // Check if the image response is successful
525
+ if (!imageResponse.ok) {
526
+ console.warn(`Failed to fetch profile picture image: ${imageResponse.status} ${imageResponse.statusText}`);
405
527
  return null;
406
528
  }
407
529
  // Check if the response is an image
408
- const contentType = response.headers.get('content-type');
530
+ const contentType = imageResponse.headers.get('content-type');
409
531
  if (!contentType || !contentType.startsWith('image/')) {
410
532
  console.warn(`Profile picture response is not an image: ${contentType}`);
411
533
  return null;
412
534
  }
413
- // Convert response to blob
414
- const blob = await response.blob();
415
- // Create blob URL
535
+ // Step 4: Convert response to blob
536
+ const blob = await imageResponse.blob();
537
+ // Step 5: Create blob URL
416
538
  const blobUrl = URL.createObjectURL(blob);
417
539
  return blobUrl;
418
540
  }