varminer-app-header 2.1.5 → 2.1.7
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/AppHeader.d.ts.map +1 -1
- package/dist/index.d.ts +85 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +180 -16
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +189 -15
- package/dist/index.js.map +1 -1
- package/dist/utils/localStorage.d.ts +53 -4
- package/dist/utils/localStorage.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -55,7 +55,42 @@ const DrawerProvider = ({ children }) => {
|
|
|
55
55
|
};
|
|
56
56
|
const useDrawer = () => React__namespace.useContext(DrawerContext);
|
|
57
57
|
|
|
58
|
+
// --- IAM app user details (shared from IAM after verify-OTP, same origin) ---
|
|
59
|
+
/** localStorage key used by IAM app for user details (GET /v1/userManagement/userDetails response). */
|
|
60
|
+
const USER_DETAILS_STORAGE_KEY = "linn-i-am-userDetails";
|
|
61
|
+
/**
|
|
62
|
+
* Read user details from IAM app localStorage (set after verify-OTP on same origin).
|
|
63
|
+
* Does not write or overwrite this key; IAM app is the source of truth.
|
|
64
|
+
*/
|
|
65
|
+
function getStoredUserDetails() {
|
|
66
|
+
try {
|
|
67
|
+
const raw = localStorage.getItem(USER_DETAILS_STORAGE_KEY);
|
|
68
|
+
if (!raw)
|
|
69
|
+
return null;
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
if (parsed.status !== "SUCCESS" || !parsed.data?.usersDetails?.length) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return parsed.data.usersDetails[0];
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
58
80
|
const getUserDataFromStorage = () => {
|
|
81
|
+
// Prefer IAM user details when available (same origin, after verify-OTP)
|
|
82
|
+
const iamUser = getStoredUserDetails();
|
|
83
|
+
if (iamUser) {
|
|
84
|
+
const name = [iamUser.firstName, iamUser.lastName].filter(Boolean).join(" ").trim();
|
|
85
|
+
const role = iamUser.roles?.length ? iamUser.roles[0] : iamUser.workInfo?.jobTitle ?? "";
|
|
86
|
+
return {
|
|
87
|
+
name: name || "",
|
|
88
|
+
email: iamUser.email || "",
|
|
89
|
+
role: role || "",
|
|
90
|
+
avatar: undefined,
|
|
91
|
+
initials: name ? name.split(/\s+/).map((s) => s[0]).join("").slice(0, 2).toUpperCase() : undefined,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
59
94
|
const userDbString = localStorage.getItem("persist:userdb");
|
|
60
95
|
if (userDbString) {
|
|
61
96
|
try {
|
|
@@ -374,45 +409,167 @@ const getProfilePictureUrl = (baseUrl = "http://objectstore.impact0mics.local:90
|
|
|
374
409
|
}
|
|
375
410
|
};
|
|
376
411
|
/**
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
412
|
+
* Generate AWS S3 presigned URL for accessing S3 object
|
|
413
|
+
* @param s3Url - Full S3 URL (e.g., https://bucket.s3.region.amazonaws.com/key)
|
|
414
|
+
* @param accessKeyId - AWS Access Key ID
|
|
415
|
+
* @param secretAccessKey - AWS Secret Access Key
|
|
416
|
+
* @param region - AWS Region (default: ap-south-2)
|
|
417
|
+
* @param expiresIn - Expiration time in seconds (default: 3600 = 1 hour)
|
|
418
|
+
* @returns Presigned URL string
|
|
419
|
+
*/
|
|
420
|
+
const generateS3PresignedUrl = async (s3Url, accessKeyId, secretAccessKey, region = "ap-south-2", expiresIn = 3600) => {
|
|
421
|
+
try {
|
|
422
|
+
// Parse S3 URL to extract bucket, region, and key
|
|
423
|
+
// Format: https://bucket.s3.region.amazonaws.com/key or https://bucket.s3-region.amazonaws.com/key
|
|
424
|
+
const url = new URL(s3Url);
|
|
425
|
+
const hostnameParts = url.hostname.split('.');
|
|
426
|
+
let bucket = hostnameParts[0];
|
|
427
|
+
let extractedRegion = region;
|
|
428
|
+
// Try to extract region from hostname (format: bucket.s3.region.amazonaws.com)
|
|
429
|
+
if (hostnameParts.length >= 3 && hostnameParts[1] === 's3') {
|
|
430
|
+
extractedRegion = hostnameParts[2] || region;
|
|
431
|
+
}
|
|
432
|
+
else if (hostnameParts.length >= 2 && hostnameParts[1].startsWith('s3-')) {
|
|
433
|
+
// Format: bucket.s3-region.amazonaws.com
|
|
434
|
+
extractedRegion = hostnameParts[1].substring(3) || region;
|
|
435
|
+
}
|
|
436
|
+
const key = url.pathname.substring(1); // Remove leading slash
|
|
437
|
+
// AWS credentials
|
|
438
|
+
const awsAccessKeyId = accessKeyId;
|
|
439
|
+
const awsSecretAccessKey = secretAccessKey;
|
|
440
|
+
const awsRegion = extractedRegion;
|
|
441
|
+
// Current timestamp
|
|
442
|
+
const now = new Date();
|
|
443
|
+
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
444
|
+
const amzDate = dateStamp + 'T' + now.toISOString().slice(11, 19).replace(/[:-]/g, '') + 'Z';
|
|
445
|
+
// Step 1: Create canonical request
|
|
446
|
+
const canonicalUri = '/' + encodeURIComponent(key).replace(/%2F/g, '/');
|
|
447
|
+
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`;
|
|
448
|
+
const canonicalHeaders = `host:${bucket}.s3.${awsRegion}.amazonaws.com\n`;
|
|
449
|
+
const signedHeaders = 'host';
|
|
450
|
+
const payloadHash = 'UNSIGNED-PAYLOAD';
|
|
451
|
+
const canonicalRequest = `GET\n${canonicalUri}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
|
|
452
|
+
// Step 2: Create string to sign
|
|
453
|
+
const algorithm = 'AWS4-HMAC-SHA256';
|
|
454
|
+
const credentialScope = `${dateStamp}/${awsRegion}/s3/aws4_request`;
|
|
455
|
+
const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${await sha256(canonicalRequest)}`;
|
|
456
|
+
// Step 3: Calculate signature
|
|
457
|
+
const kDate = await hmacSha256('AWS4' + awsSecretAccessKey, dateStamp);
|
|
458
|
+
const kRegion = await hmacSha256(kDate, awsRegion);
|
|
459
|
+
const kService = await hmacSha256(kRegion, 's3');
|
|
460
|
+
const kSigning = await hmacSha256(kService, 'aws4_request');
|
|
461
|
+
const signature = await hmacSha256(kSigning, stringToSign);
|
|
462
|
+
// Convert signature to hex
|
|
463
|
+
const signatureHex = arrayBufferToHex(signature);
|
|
464
|
+
// Step 4: Construct presigned URL
|
|
465
|
+
const presignedUrl = `https://${bucket}.s3.${awsRegion}.amazonaws.com${canonicalUri}?${canonicalQuerystring}&X-Amz-Signature=${signatureHex}`;
|
|
466
|
+
return presignedUrl;
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
console.error("Error generating S3 presigned URL:", err);
|
|
470
|
+
throw err;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
// Helper function for SHA-256 hashing
|
|
474
|
+
const sha256 = async (message) => {
|
|
475
|
+
const msgBuffer = new TextEncoder().encode(message);
|
|
476
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
|
477
|
+
return Array.from(new Uint8Array(hashBuffer))
|
|
478
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
479
|
+
.join('');
|
|
480
|
+
};
|
|
481
|
+
// Helper function for HMAC-SHA256
|
|
482
|
+
const hmacSha256 = async (key, message) => {
|
|
483
|
+
const encoder = new TextEncoder();
|
|
484
|
+
let keyBuffer;
|
|
485
|
+
if (typeof key === 'string') {
|
|
486
|
+
keyBuffer = encoder.encode(key);
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
keyBuffer = new Uint8Array(key);
|
|
490
|
+
}
|
|
491
|
+
const messageBuffer = encoder.encode(message);
|
|
492
|
+
const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
493
|
+
return await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);
|
|
494
|
+
};
|
|
495
|
+
// Helper function to convert ArrayBuffer to hex string
|
|
496
|
+
const arrayBufferToHex = (buffer) => {
|
|
497
|
+
return Array.from(new Uint8Array(buffer))
|
|
498
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
499
|
+
.join('');
|
|
500
|
+
};
|
|
501
|
+
/**
|
|
502
|
+
* Fetch profile picture from API with headers, get S3 URL, generate presigned URL, and return as blob URL
|
|
503
|
+
* This function:
|
|
504
|
+
* 1. Fetches the profile picture path from the API
|
|
505
|
+
* 2. Extracts the S3 filePath from the JSON response
|
|
506
|
+
* 3. Generates a presigned URL for the S3 object
|
|
507
|
+
* 4. Fetches the image using the presigned URL
|
|
508
|
+
* 5. Converts it to a blob URL that can be used in img src
|
|
380
509
|
* @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
|
|
381
510
|
* @param messageId - Optional message ID for X-Message-Id header (default: generated UUID)
|
|
382
511
|
* @param correlationId - Optional correlation ID for X-Correlation-Id header (default: generated UUID)
|
|
512
|
+
* @param awsConfig - AWS configuration (accessKeyId, secretAccessKey, region, bucket)
|
|
383
513
|
* @returns Promise that resolves to blob URL string or null if fetch fails
|
|
384
514
|
*/
|
|
385
|
-
const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012", messageId, correlationId) => {
|
|
515
|
+
const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012", messageId, correlationId, awsConfig) => {
|
|
386
516
|
try {
|
|
387
517
|
const profilePictureUrl = getProfilePictureUrl(baseUrl);
|
|
388
518
|
if (!profilePictureUrl) {
|
|
389
519
|
return null;
|
|
390
520
|
}
|
|
521
|
+
// AWS credentials (default values provided by user)
|
|
522
|
+
const defaultAwsConfig = {
|
|
523
|
+
accessKeyId: "AKIAVRUVTJGLBCYZEI5L",
|
|
524
|
+
secretAccessKey: "kbMVqmx6s29njcS5P48qAqpXlb1oir6+b7zu1Qxi",
|
|
525
|
+
region: "ap-south-2",
|
|
526
|
+
bucket: "development-varminer-test"
|
|
527
|
+
};
|
|
528
|
+
const finalAwsConfig = awsConfig || defaultAwsConfig;
|
|
391
529
|
// Generate message ID and correlation ID if not provided
|
|
392
530
|
const msgId = messageId || `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
393
531
|
const corrId = correlationId || `corr-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
394
|
-
// Fetch the
|
|
395
|
-
const
|
|
532
|
+
// Step 1: Fetch the profile picture path from API (returns JSON with filePath)
|
|
533
|
+
const apiResponse = await fetch(profilePictureUrl, {
|
|
396
534
|
method: 'GET',
|
|
397
535
|
headers: {
|
|
398
536
|
'X-Message-Id': msgId,
|
|
399
537
|
'X-Correlation-Id': corrId,
|
|
400
538
|
},
|
|
401
539
|
});
|
|
402
|
-
// Check if the response is successful
|
|
403
|
-
if (!
|
|
404
|
-
console.warn(`Failed to fetch profile picture: ${
|
|
540
|
+
// Check if the API response is successful
|
|
541
|
+
if (!apiResponse.ok) {
|
|
542
|
+
console.warn(`Failed to fetch profile picture path: ${apiResponse.status} ${apiResponse.statusText}`);
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
// Parse JSON response
|
|
546
|
+
const apiData = await apiResponse.json();
|
|
547
|
+
// Extract filePath from response
|
|
548
|
+
const s3Url = apiData?.filePath;
|
|
549
|
+
if (!s3Url || typeof s3Url !== 'string') {
|
|
550
|
+
console.warn('Profile picture API response does not contain valid filePath');
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
// Step 2: Generate presigned URL for S3 object
|
|
554
|
+
const presignedUrl = await generateS3PresignedUrl(s3Url, finalAwsConfig.accessKeyId, finalAwsConfig.secretAccessKey, finalAwsConfig.region);
|
|
555
|
+
// Step 3: Fetch the image using presigned URL
|
|
556
|
+
const imageResponse = await fetch(presignedUrl, {
|
|
557
|
+
method: 'GET',
|
|
558
|
+
});
|
|
559
|
+
// Check if the image response is successful
|
|
560
|
+
if (!imageResponse.ok) {
|
|
561
|
+
console.warn(`Failed to fetch profile picture image: ${imageResponse.status} ${imageResponse.statusText}`);
|
|
405
562
|
return null;
|
|
406
563
|
}
|
|
407
564
|
// Check if the response is an image
|
|
408
|
-
const contentType =
|
|
565
|
+
const contentType = imageResponse.headers.get('content-type');
|
|
409
566
|
if (!contentType || !contentType.startsWith('image/')) {
|
|
410
567
|
console.warn(`Profile picture response is not an image: ${contentType}`);
|
|
411
568
|
return null;
|
|
412
569
|
}
|
|
413
|
-
// Convert response to blob
|
|
414
|
-
const blob = await
|
|
415
|
-
// Create blob URL
|
|
570
|
+
// Step 4: Convert response to blob
|
|
571
|
+
const blob = await imageResponse.blob();
|
|
572
|
+
// Step 5: Create blob URL
|
|
416
573
|
const blobUrl = URL.createObjectURL(blob);
|
|
417
574
|
return blobUrl;
|
|
418
575
|
}
|
|
@@ -458,7 +615,8 @@ const languages = [
|
|
|
458
615
|
{ code: "en", name: "English", flag: GBFlag },
|
|
459
616
|
{ code: "es", name: "Spanish", flag: ESFlag },
|
|
460
617
|
];
|
|
461
|
-
const LanguageSelector = ({ currentLanguage, onLanguageChange,
|
|
618
|
+
const LanguageSelector = ({ currentLanguage, onLanguageChange: _onLanguageChange, // reserved for parent callback; we reload the page on select
|
|
619
|
+
}) => {
|
|
462
620
|
const [anchorEl, setAnchorEl] = React.useState(null);
|
|
463
621
|
const open = Boolean(anchorEl);
|
|
464
622
|
const currentLang = languages.find((lang) => lang.code === currentLanguage) || languages[0];
|
|
@@ -819,7 +977,13 @@ const AppHeader = ({ language: languageProp }) => {
|
|
|
819
977
|
};
|
|
820
978
|
const handleSignOutClick = () => {
|
|
821
979
|
handleClose();
|
|
822
|
-
|
|
980
|
+
try {
|
|
981
|
+
localStorage.clear();
|
|
982
|
+
sessionStorage.clear();
|
|
983
|
+
}
|
|
984
|
+
catch (e) {
|
|
985
|
+
console.warn("Clear storage on logout:", e);
|
|
986
|
+
}
|
|
823
987
|
const path = finalRoutes.logout.startsWith('/')
|
|
824
988
|
? finalRoutes.logout
|
|
825
989
|
: `/${finalRoutes.logout}`;
|
|
@@ -969,7 +1133,17 @@ const AppHeader = ({ language: languageProp }) => {
|
|
|
969
1133
|
|
|
970
1134
|
exports.AppHeader = AppHeader;
|
|
971
1135
|
exports.DrawerProvider = DrawerProvider;
|
|
1136
|
+
exports.USER_DETAILS_STORAGE_KEY = USER_DETAILS_STORAGE_KEY;
|
|
1137
|
+
exports.fetchProfilePictureAsBlobUrl = fetchProfilePictureAsBlobUrl;
|
|
1138
|
+
exports.getAllDataFromStorage = getAllDataFromStorage;
|
|
1139
|
+
exports.getI18nLocaleFromStorage = getI18nLocaleFromStorage;
|
|
1140
|
+
exports.getMessageCountFromStorage = getMessageCountFromStorage;
|
|
1141
|
+
exports.getNotificationCountFromStorage = getNotificationCountFromStorage;
|
|
1142
|
+
exports.getProfilePictureUrl = getProfilePictureUrl;
|
|
1143
|
+
exports.getStoredUserDetails = getStoredUserDetails;
|
|
972
1144
|
exports.getTranslations = getTranslations;
|
|
1145
|
+
exports.getUserDataFromStorage = getUserDataFromStorage;
|
|
1146
|
+
exports.setI18nLocaleToStorage = setI18nLocaleToStorage;
|
|
973
1147
|
exports.translations = translations;
|
|
974
1148
|
exports.useDrawer = useDrawer;
|
|
975
1149
|
//# sourceMappingURL=index.js.map
|