varminer-app-header 2.1.4 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"AppHeader.d.ts","sourceRoot":"","sources":["../src/AppHeader.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAe,MAAM,SAAS,CAAC;AAKtD,OAAO,sBAAsB,CAAC;AAQ9B,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAuuBvC,CAAC;AAEF,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"AppHeader.d.ts","sourceRoot":"","sources":["../src/AppHeader.tsx"],"names":[],"mappings":"AA4BA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAe,MAAM,SAAS,CAAC;AAKtD,OAAO,sBAAsB,CAAC;AAQ9B,QAAA,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CAwwBvC,CAAC;AAEF,eAAe,SAAS,CAAC"}
package/dist/index.esm.js CHANGED
@@ -320,6 +320,209 @@ const getAllDataFromStorage = () => {
320
320
  };
321
321
  }
322
322
  };
323
+ /**
324
+ * Get profile picture URL from object store API using tenant_id and user_id from JWT token
325
+ * @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
326
+ * @returns Profile picture URL or null if tenant_id or user_id is not available
327
+ */
328
+ const getProfilePictureUrl = (baseUrl = "http://objectstore.impact0mics.local:9012") => {
329
+ try {
330
+ const allData = getAllDataFromStorage();
331
+ // Get tenant_id and user_id from decoded token
332
+ let tenantId = null;
333
+ let userId = null;
334
+ if (allData.decodedToken) {
335
+ tenantId = allData.decodedToken.tenant_id || allData.decodedToken.tenant || null;
336
+ userId = allData.decodedToken.user_id || null;
337
+ }
338
+ // If not found in decoded token, try auth data
339
+ if ((!tenantId || !userId) && allData.auth) {
340
+ tenantId = tenantId || allData.auth.tenant_id || allData.auth.tenant || null;
341
+ userId = userId || allData.auth.user_id || null;
342
+ }
343
+ // Construct URL if we have both tenant_id and user_id
344
+ if (tenantId && userId) {
345
+ // Remove trailing slash from baseUrl if present
346
+ const cleanBaseUrl = baseUrl.replace(/\/$/, '');
347
+ return `${cleanBaseUrl}/v1/objectStore/profilePicture/path/${tenantId}/${userId}`;
348
+ }
349
+ return null;
350
+ }
351
+ catch (err) {
352
+ console.error("Error getting profile picture URL:", err);
353
+ return null;
354
+ }
355
+ };
356
+ /**
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
454
+ * @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
455
+ * @param messageId - Optional message ID for X-Message-Id header (default: generated UUID)
456
+ * @param correlationId - Optional correlation ID for X-Correlation-Id header (default: generated UUID)
457
+ * @param awsConfig - AWS configuration (accessKeyId, secretAccessKey, region, bucket)
458
+ * @returns Promise that resolves to blob URL string or null if fetch fails
459
+ */
460
+ const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012", messageId, correlationId, awsConfig) => {
461
+ try {
462
+ const profilePictureUrl = getProfilePictureUrl(baseUrl);
463
+ if (!profilePictureUrl) {
464
+ return null;
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;
474
+ // Generate message ID and correlation ID if not provided
475
+ const msgId = messageId || `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
476
+ const corrId = correlationId || `corr-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
477
+ // Step 1: Fetch the profile picture path from API (returns JSON with filePath)
478
+ const apiResponse = await fetch(profilePictureUrl, {
479
+ method: 'GET',
480
+ headers: {
481
+ 'X-Message-Id': msgId,
482
+ 'X-Correlation-Id': corrId,
483
+ },
484
+ });
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}`);
507
+ return null;
508
+ }
509
+ // Check if the response is an image
510
+ const contentType = imageResponse.headers.get('content-type');
511
+ if (!contentType || !contentType.startsWith('image/')) {
512
+ console.warn(`Profile picture response is not an image: ${contentType}`);
513
+ return null;
514
+ }
515
+ // Step 4: Convert response to blob
516
+ const blob = await imageResponse.blob();
517
+ // Step 5: Create blob URL
518
+ const blobUrl = URL.createObjectURL(blob);
519
+ return blobUrl;
520
+ }
521
+ catch (err) {
522
+ console.error("Error fetching profile picture:", err);
523
+ return null;
524
+ }
525
+ };
323
526
 
324
527
  const translations = {
325
528
  en: {
@@ -527,6 +730,34 @@ const AppHeader = ({ language: languageProp }) => {
527
730
  const [messageCount, setMessageCount] = React__default.useState(() => {
528
731
  return getMessageCountFromStorage() ?? undefined;
529
732
  });
733
+ // State for profile picture blob URL
734
+ const [profilePictureBlobUrl, setProfilePictureBlobUrl] = React__default.useState(null);
735
+ // Fetch profile picture from API when component mounts or user data changes
736
+ React__default.useEffect(() => {
737
+ const fetchProfilePicture = async () => {
738
+ // Try to fetch profile picture from API
739
+ const blobUrl = await fetchProfilePictureAsBlobUrl();
740
+ if (blobUrl) {
741
+ // Clean up previous blob URL if it exists
742
+ setProfilePictureBlobUrl((prevUrl) => {
743
+ if (prevUrl) {
744
+ URL.revokeObjectURL(prevUrl);
745
+ }
746
+ return blobUrl;
747
+ });
748
+ }
749
+ };
750
+ fetchProfilePicture();
751
+ // Cleanup function to revoke blob URL when component unmounts or effect re-runs
752
+ return () => {
753
+ setProfilePictureBlobUrl((prevUrl) => {
754
+ if (prevUrl) {
755
+ URL.revokeObjectURL(prevUrl);
756
+ }
757
+ return null;
758
+ });
759
+ };
760
+ }, []); // Only run once on mount - fetch when component loads
530
761
  React__default.useEffect(() => {
531
762
  const allData = getAllDataFromStorage();
532
763
  // Try to get user data from various sources
@@ -589,11 +820,12 @@ const AppHeader = ({ language: languageProp }) => {
589
820
  userInitials = userInitials || profileData.initials || undefined;
590
821
  }
591
822
  }
823
+ // Use fetched blob URL if available, otherwise fall back to other sources
592
824
  setUser({
593
825
  name: userName || "",
594
826
  email: userEmail || "",
595
827
  role: userRole || "",
596
- avatar: userAvatar,
828
+ avatar: profilePictureBlobUrl || userAvatar,
597
829
  initials: userInitials,
598
830
  });
599
831
  // Update online status
@@ -618,7 +850,7 @@ const AppHeader = ({ language: languageProp }) => {
618
850
  setCurrentLanguage(languageProp);
619
851
  setI18nLocaleToStorage(languageProp);
620
852
  }
621
- }, [languageProp]);
853
+ }, [languageProp, profilePictureBlobUrl]); // Also update when profile picture is fetched
622
854
  const finalRoutes = DEFAULT_ROUTES;
623
855
  // Get online status from localStorage dynamically
624
856
  const [isOnlineStatus, setIsOnlineStatus] = React__default.useState(() => {