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