varminer-app-header 2.2.2 → 2.2.4

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
@@ -248,8 +248,8 @@ const getAllDataFromStorage = () => {
248
248
  try {
249
249
  const parsed = JSON.parse(headerStr);
250
250
  const auth = parsed?.auth;
251
- const accessToken = auth?.accessToken;
252
- if (accessToken) {
251
+ const accessToken = auth?.accessToken ?? auth?.access_token ?? auth?.token;
252
+ if (accessToken && typeof accessToken === "string") {
253
253
  const decodedToken = decodeJWT(accessToken);
254
254
  return {
255
255
  ...emptyStorageResult(),
@@ -263,6 +263,48 @@ const getAllDataFromStorage = () => {
263
263
  console.error("Error parsing header persist:", err);
264
264
  }
265
265
  }
266
+ // Fallback: try IAM persist key (persist:linn-i-am) then persist:userdb
267
+ const tryParsePersistForAuth = (raw) => {
268
+ if (!raw)
269
+ return null;
270
+ try {
271
+ const outer = JSON.parse(raw);
272
+ const parseNested = (v) => typeof v === "string" ? (() => { try {
273
+ return JSON.parse(v);
274
+ }
275
+ catch {
276
+ return v;
277
+ } })() : v;
278
+ for (const key of Object.keys(outer)) {
279
+ const parsed = parseNested(outer[key]);
280
+ if (parsed?.accessToken || parsed?.access_token || parsed?.token || parsed?.refreshToken)
281
+ return parsed;
282
+ if (parsed?.auth && typeof parsed.auth === "object") {
283
+ const a = parsed.auth;
284
+ if (a.accessToken || a.access_token || a.token)
285
+ return a;
286
+ }
287
+ }
288
+ }
289
+ catch {
290
+ /* ignore */
291
+ }
292
+ return null;
293
+ };
294
+ const iamPersist = localStorage.getItem("persist:linn-i-am");
295
+ const iamAuth = tryParsePersistForAuth(iamPersist);
296
+ if (iamAuth) {
297
+ const token = (iamAuth.accessToken ?? iamAuth.access_token ?? iamAuth.token);
298
+ if (token) {
299
+ const decodedToken = decodeJWT(token);
300
+ return {
301
+ ...emptyStorageResult(),
302
+ auth: iamAuth,
303
+ decodedToken: decodedToken ? Object.fromEntries(Object.entries(decodedToken).map(([k, v]) => [k, v ?? null])) : null,
304
+ rawData: iamAuth,
305
+ };
306
+ }
307
+ }
266
308
  // Fallback: read access token from persist:userdb (e.g. legacy or shared auth)
267
309
  const userDbString = localStorage.getItem("persist:userdb");
268
310
  if (!userDbString) {
@@ -285,17 +327,14 @@ const getAllDataFromStorage = () => {
285
327
  // Parse all top-level keys dynamically (no hardcoding)
286
328
  const parsedData = {};
287
329
  let authData = null;
288
- // Helper to recursively find auth data (contains accessToken or refreshToken)
330
+ // Helper to recursively find auth data (contains accessToken, access_token, token, or refreshToken)
289
331
  const findAuthData = (obj) => {
290
332
  if (!obj || typeof obj !== 'object')
291
333
  return null;
292
- // Check if this object itself is auth data
293
- if (obj.accessToken || obj.refreshToken) {
334
+ if (obj.accessToken || obj.access_token || obj.token || obj.refreshToken)
294
335
  return obj;
295
- }
296
- // Recursively search in nested objects
297
336
  for (const key in obj) {
298
- if (obj.hasOwnProperty(key)) {
337
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
299
338
  const nested = findAuthData(obj[key]);
300
339
  if (nested)
301
340
  return nested;
@@ -313,9 +352,11 @@ const getAllDataFromStorage = () => {
313
352
  parsedData.auth = authData;
314
353
  }
315
354
  });
316
- // Get accessToken and decode it
355
+ // Get accessToken (support accessToken, access_token, token) and decode it
317
356
  let decodedToken = null;
318
- const accessToken = authData?.accessToken || parsedData.auth?.accessToken || parsedData.authDetails?.accessToken;
357
+ const accessToken = authData?.accessToken ?? authData?.access_token ?? authData?.token ??
358
+ parsedData.auth?.accessToken ?? parsedData.auth?.access_token ?? parsedData.auth?.token ??
359
+ parsedData.authDetails?.accessToken ?? parsedData.authDetails?.access_token ?? parsedData.authDetails?.token;
319
360
  if (accessToken) {
320
361
  decodedToken = decodeJWT(accessToken);
321
362
  }
@@ -366,175 +407,159 @@ const getProfilePictureUrl = (baseUrl = "http://objectstore.impact0mics.local:90
366
407
  }
367
408
  };
368
409
  /**
369
- * Generate AWS S3 presigned URL for accessing S3 object
370
- * @param s3Url - Full S3 URL (e.g., https://bucket.s3.region.amazonaws.com/key)
371
- * @param accessKeyId - AWS Access Key ID
372
- * @param secretAccessKey - AWS Secret Access Key
373
- * @param region - AWS Region (default: ap-south-2)
374
- * @param expiresIn - Expiration time in seconds (default: 3600 = 1 hour)
375
- * @returns Presigned URL string
410
+ * Fetch profile picture from object store API (plain GET with Authorization header).
411
+ * API returns the image directly; response is converted to a blob URL for use in img src.
412
+ * @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
413
+ * @returns Promise that resolves to blob URL string or null if fetch fails
376
414
  */
377
- const generateS3PresignedUrl = async (s3Url, accessKeyId, secretAccessKey, region = "ap-south-2", expiresIn = 3600) => {
415
+ /** Resolve access token from auth object (supports accessToken, access_token, token). */
416
+ function getAccessTokenFromAuth(auth) {
417
+ if (!auth || typeof auth !== "object")
418
+ return undefined;
419
+ const token = auth.accessToken ??
420
+ auth.access_token ??
421
+ auth.token;
422
+ return typeof token === "string" && token.length > 0 ? token : undefined;
423
+ }
424
+ /** True if string looks like a JWT (three base64 parts) or a bearer token, not JSON. */
425
+ function looksLikeToken(s) {
426
+ return s.length > 0 && !s.trimStart().startsWith("{") && !s.trimStart().startsWith("[");
427
+ }
428
+ /** Recursively find first string value at keys accessToken, access_token, or token. */
429
+ function findTokenInObject(obj) {
430
+ if (obj === null || obj === undefined)
431
+ return undefined;
432
+ if (typeof obj === "string")
433
+ return looksLikeToken(obj) ? obj : undefined;
434
+ if (typeof obj !== "object")
435
+ return undefined;
436
+ const rec = obj;
437
+ const direct = rec.accessToken ?? rec.access_token ?? rec.token;
438
+ if (typeof direct === "string" && looksLikeToken(direct))
439
+ return direct;
440
+ for (const key of Object.keys(rec)) {
441
+ const found = findTokenInObject(rec[key]);
442
+ if (found)
443
+ return found;
444
+ }
445
+ return undefined;
446
+ }
447
+ /** Parse redux-persist style value (may be double stringified). */
448
+ function parsePersistValue(raw) {
449
+ if (!raw)
450
+ return null;
378
451
  try {
379
- // Parse S3 URL to extract bucket, region, and key
380
- // Format: https://bucket.s3.region.amazonaws.com/key or https://bucket.s3-region.amazonaws.com/key
381
- const url = new URL(s3Url);
382
- const hostnameParts = url.hostname.split('.');
383
- let bucket = hostnameParts[0];
384
- let extractedRegion = region;
385
- // Try to extract region from hostname (format: bucket.s3.region.amazonaws.com)
386
- if (hostnameParts.length >= 3 && hostnameParts[1] === 's3') {
387
- extractedRegion = hostnameParts[2] || region;
388
- }
389
- else if (hostnameParts.length >= 2 && hostnameParts[1].startsWith('s3-')) {
390
- // Format: bucket.s3-region.amazonaws.com
391
- extractedRegion = hostnameParts[1].substring(3) || region;
452
+ const first = JSON.parse(raw);
453
+ if (typeof first === "string") {
454
+ try {
455
+ return JSON.parse(first);
456
+ }
457
+ catch {
458
+ return first;
459
+ }
392
460
  }
393
- const key = url.pathname.substring(1); // Remove leading slash
394
- // AWS credentials
395
- const awsAccessKeyId = accessKeyId;
396
- const awsSecretAccessKey = secretAccessKey;
397
- const awsRegion = extractedRegion;
398
- // Current timestamp
399
- const now = new Date();
400
- const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
401
- const amzDate = dateStamp + 'T' + now.toISOString().slice(11, 19).replace(/[:-]/g, '') + 'Z';
402
- // Step 1: Create canonical request
403
- const canonicalUri = '/' + encodeURIComponent(key).replace(/%2F/g, '/');
404
- 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`;
405
- const canonicalHeaders = `host:${bucket}.s3.${awsRegion}.amazonaws.com\n`;
406
- const signedHeaders = 'host';
407
- const payloadHash = 'UNSIGNED-PAYLOAD';
408
- const canonicalRequest = `GET\n${canonicalUri}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
409
- // Step 2: Create string to sign
410
- const algorithm = 'AWS4-HMAC-SHA256';
411
- const credentialScope = `${dateStamp}/${awsRegion}/s3/aws4_request`;
412
- const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${await sha256(canonicalRequest)}`;
413
- // Step 3: Calculate signature
414
- const kDate = await hmacSha256('AWS4' + awsSecretAccessKey, dateStamp);
415
- const kRegion = await hmacSha256(kDate, awsRegion);
416
- const kService = await hmacSha256(kRegion, 's3');
417
- const kSigning = await hmacSha256(kService, 'aws4_request');
418
- const signature = await hmacSha256(kSigning, stringToSign);
419
- // Convert signature to hex
420
- const signatureHex = arrayBufferToHex(signature);
421
- // Step 4: Construct presigned URL
422
- const presignedUrl = `https://${bucket}.s3.${awsRegion}.amazonaws.com${canonicalUri}?${canonicalQuerystring}&X-Amz-Signature=${signatureHex}`;
423
- return presignedUrl;
424
- }
425
- catch (err) {
426
- console.error("Error generating S3 presigned URL:", err);
427
- throw err;
428
- }
429
- };
430
- // Helper function for SHA-256 hashing
431
- const sha256 = async (message) => {
432
- const msgBuffer = new TextEncoder().encode(message);
433
- const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
434
- return Array.from(new Uint8Array(hashBuffer))
435
- .map(b => b.toString(16).padStart(2, '0'))
436
- .join('');
437
- };
438
- // Helper function for HMAC-SHA256
439
- const hmacSha256 = async (key, message) => {
440
- const encoder = new TextEncoder();
441
- let keyBuffer;
442
- if (typeof key === 'string') {
443
- keyBuffer = encoder.encode(key);
461
+ return first;
444
462
  }
445
- else {
446
- keyBuffer = new Uint8Array(key);
463
+ catch {
464
+ return null;
447
465
  }
448
- const messageBuffer = encoder.encode(message);
449
- const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
450
- return await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);
451
- };
452
- // Helper function to convert ArrayBuffer to hex string
453
- const arrayBufferToHex = (buffer) => {
454
- return Array.from(new Uint8Array(buffer))
455
- .map(b => b.toString(16).padStart(2, '0'))
456
- .join('');
457
- };
466
+ }
467
+ /** Get a nested value that may be a string (need parse) or already an object. */
468
+ function parseNestedValue(val) {
469
+ if (val == null)
470
+ return null;
471
+ if (typeof val === "string")
472
+ return parsePersistValue(val);
473
+ return val;
474
+ }
458
475
  /**
459
- * Fetch profile picture from API with headers, get S3 URL, generate presigned URL, and return as blob URL
460
- * This function:
461
- * 1. Fetches the profile picture path from the API
462
- * 2. Extracts the S3 filePath from the JSON response
463
- * 3. Generates a presigned URL for the S3 object
464
- * 4. Fetches the image using the presigned URL
465
- * 5. Converts it to a blob URL that can be used in img src
466
- * @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
467
- * @param messageId - Optional message ID for X-Message-Id header (default: generated UUID)
468
- * @param correlationId - Optional correlation ID for X-Correlation-Id header (default: generated UUID)
469
- * @param awsConfig - AWS configuration (accessKeyId, secretAccessKey, region, bucket)
470
- * @returns Promise that resolves to blob URL string or null if fetch fails
476
+ * Extract access token from persist:userdb shape: authDetails (string) parse auth.accessToken.
477
+ * Structure: { authDetails: "{\"auth\":{\"accessToken\":\"...\"}}", profileInformation: "...", _persist: "..." }
478
+ */
479
+ function getTokenFromUserDbPersist(parsed) {
480
+ const authDetails = parsed.authDetails;
481
+ const inner = parseNestedValue(authDetails);
482
+ const obj = typeof inner === "object" && inner !== null ? inner : null;
483
+ if (!obj)
484
+ return undefined;
485
+ const auth = obj.auth;
486
+ const authObj = typeof auth === "object" && auth !== null ? auth : null;
487
+ if (!authObj)
488
+ return undefined;
489
+ const token = authObj.accessToken ??
490
+ authObj.access_token ??
491
+ authObj.token;
492
+ return typeof token === "string" && looksLikeToken(token) ? token : undefined;
493
+ }
494
+ /**
495
+ * Get access token from all known storage keys (header, IAM, userdb, and plain keys).
496
+ * persist:userdb shape: authDetails (stringified) contains auth.accessToken.
471
497
  */
472
- const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012", messageId, correlationId, awsConfig) => {
498
+ function getAccessTokenForRequest() {
499
+ const keysToTry = [
500
+ PERSIST_HEADER_KEY,
501
+ "persist:linn-i-am",
502
+ "persist:userdb",
503
+ "token",
504
+ "accessToken",
505
+ "auth",
506
+ ];
507
+ for (const key of keysToTry) {
508
+ const raw = localStorage.getItem(key);
509
+ if (!raw)
510
+ continue;
511
+ const parsed = key.startsWith("persist:") ? parsePersistValue(raw) : (() => { try {
512
+ return JSON.parse(raw);
513
+ }
514
+ catch {
515
+ return raw;
516
+ } })();
517
+ const token = findTokenInObject(parsed);
518
+ if (token)
519
+ return token;
520
+ if (key.startsWith("persist:")) {
521
+ const outer = typeof parsed === "object" && parsed !== null ? parsed : null;
522
+ if (outer) {
523
+ if (key === "persist:userdb") {
524
+ const fromUserDb = getTokenFromUserDbPersist(outer);
525
+ if (fromUserDb)
526
+ return fromUserDb;
527
+ }
528
+ for (const k of Object.keys(outer)) {
529
+ const inner = parseNestedValue(outer[k]);
530
+ const t = findTokenInObject(inner);
531
+ if (t)
532
+ return t;
533
+ }
534
+ }
535
+ }
536
+ }
537
+ return getAccessTokenFromAuth(getAllDataFromStorage().auth);
538
+ }
539
+ const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012", accessTokenOverride) => {
473
540
  try {
474
541
  const profilePictureUrl = getProfilePictureUrl(baseUrl);
475
- if (!profilePictureUrl) {
542
+ if (!profilePictureUrl)
476
543
  return null;
477
- }
478
- // AWS credentials (default values provided by user)
479
- const defaultAwsConfig = {
480
- accessKeyId: "AKIAVRUVTJGLBCYZEI5L",
481
- secretAccessKey: "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",
482
- region: "ap-south-2",
483
- bucket: "development-varminer-test"
484
- };
485
- const finalAwsConfig = awsConfig || defaultAwsConfig;
486
- // Generate message ID and correlation ID if not provided
487
- const msgId = messageId || `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
488
- const corrId = correlationId || `corr-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
489
- const allData = getAllDataFromStorage();
490
- const accessToken = allData.auth?.accessToken;
491
- const headers = {
492
- 'X-Message-Id': msgId,
493
- 'X-Correlation-Id': corrId,
494
- };
544
+ const accessToken = (typeof accessTokenOverride === "string" && accessTokenOverride.length > 0
545
+ ? accessTokenOverride
546
+ : null) ?? getAccessTokenForRequest();
547
+ const headers = {};
495
548
  if (accessToken) {
496
- headers['Authorization'] = `Bearer ${accessToken}`;
497
- }
498
- // Step 1: Fetch the profile picture path from API (returns JSON with filePath)
499
- const apiResponse = await fetch(profilePictureUrl, {
500
- method: 'GET',
501
- headers,
502
- });
503
- // Check if the API response is successful
504
- if (!apiResponse.ok) {
505
- console.warn(`Failed to fetch profile picture path: ${apiResponse.status} ${apiResponse.statusText}`);
506
- return null;
549
+ headers["Authorization"] = `Bearer ${accessToken}`;
507
550
  }
508
- // Parse JSON response
509
- const apiData = await apiResponse.json();
510
- // Extract filePath from response
511
- const s3Url = apiData?.filePath;
512
- if (!s3Url || typeof s3Url !== 'string') {
513
- console.warn('Profile picture API response does not contain valid filePath');
514
- return null;
515
- }
516
- // Step 2: Generate presigned URL for S3 object
517
- const presignedUrl = await generateS3PresignedUrl(s3Url, finalAwsConfig.accessKeyId, finalAwsConfig.secretAccessKey, finalAwsConfig.region);
518
- // Step 3: Fetch the image using presigned URL
519
- const imageResponse = await fetch(presignedUrl, {
520
- method: 'GET',
521
- });
522
- // Check if the image response is successful
523
- if (!imageResponse.ok) {
524
- console.warn(`Failed to fetch profile picture image: ${imageResponse.status} ${imageResponse.statusText}`);
551
+ const response = await fetch(profilePictureUrl, { method: "GET", headers });
552
+ if (!response.ok) {
553
+ console.warn(`Failed to fetch profile picture: ${response.status} ${response.statusText}`);
525
554
  return null;
526
555
  }
527
- // Check if the response is an image
528
- const contentType = imageResponse.headers.get('content-type');
529
- if (!contentType || !contentType.startsWith('image/')) {
556
+ const contentType = response.headers.get("content-type");
557
+ if (!contentType || !contentType.startsWith("image/")) {
530
558
  console.warn(`Profile picture response is not an image: ${contentType}`);
531
559
  return null;
532
560
  }
533
- // Step 4: Convert response to blob
534
- const blob = await imageResponse.blob();
535
- // Step 5: Create blob URL
536
- const blobUrl = URL.createObjectURL(blob);
537
- return blobUrl;
561
+ const blob = await response.blob();
562
+ return URL.createObjectURL(blob);
538
563
  }
539
564
  catch (err) {
540
565
  console.error("Error fetching profile picture:", err);
@@ -654,7 +679,7 @@ const DEFAULT_ROUTES = {
654
679
  profile: "/user/profile",
655
680
  logout: "/user/login",
656
681
  };
657
- const AppHeader = ({ language: languageProp }) => {
682
+ const AppHeader = ({ language: languageProp, accessToken: accessTokenProp }) => {
658
683
  // Get initial language from props, URL, localStorage, or default to 'en'
659
684
  const getInitialLanguage = () => {
660
685
  // Priority 1: Props
@@ -751,12 +776,18 @@ const AppHeader = ({ language: languageProp }) => {
751
776
  userRole = userRole || profileData.role || profileData.userRole || "";
752
777
  }
753
778
  }
779
+ const avatarSrc = storedUser?.avatar ??
780
+ allData.auth?.avatar ??
781
+ allData.profile?.avatar;
782
+ const initialsVal = storedUser?.initials ??
783
+ allData.auth?.initials ??
784
+ allData.profile?.initials;
754
785
  return {
755
786
  name: userName || "",
756
787
  email: userEmail || "",
757
788
  role: userRole || "",
758
- avatar: storedUser?.avatar || allData.auth?.avatar || allData.profile?.avatar || undefined,
759
- initials: storedUser?.initials || allData.auth?.initials || allData.profile?.initials || undefined,
789
+ avatar: typeof avatarSrc === "string" ? avatarSrc : undefined,
790
+ initials: typeof initialsVal === "string" ? initialsVal : undefined,
760
791
  };
761
792
  });
762
793
  const [notificationCount, setNotificationCount] = React.useState(() => {
@@ -771,8 +802,8 @@ const AppHeader = ({ language: languageProp }) => {
771
802
  // Fetch profile picture from API when component mounts or user data changes
772
803
  React.useEffect(() => {
773
804
  const fetchProfilePicture = async () => {
774
- // Try to fetch profile picture from API
775
- const blobUrl = await fetchProfilePictureAsBlobUrl();
805
+ const token = accessTokenProp ?? getAccessTokenForRequest();
806
+ const blobUrl = await fetchProfilePictureAsBlobUrl(undefined, token ?? undefined);
776
807
  if (blobUrl) {
777
808
  // Clean up previous blob URL if it exists
778
809
  setProfilePictureBlobUrl((prevUrl) => {
@@ -793,7 +824,7 @@ const AppHeader = ({ language: languageProp }) => {
793
824
  return null;
794
825
  });
795
826
  };
796
- }, []); // Only run once on mount - fetch when component loads
827
+ }, [accessTokenProp]); // Refetch when accessToken prop changes (e.g. after login)
797
828
  React.useEffect(() => {
798
829
  const allData = getAllDataFromStorage();
799
830
  let userName = "";
@@ -1130,6 +1161,7 @@ exports.DrawerProvider = DrawerProvider;
1130
1161
  exports.PERSIST_HEADER_KEY = PERSIST_HEADER_KEY;
1131
1162
  exports.USER_DETAILS_STORAGE_KEY = USER_DETAILS_STORAGE_KEY;
1132
1163
  exports.fetchProfilePictureAsBlobUrl = fetchProfilePictureAsBlobUrl;
1164
+ exports.getAccessTokenForRequest = getAccessTokenForRequest;
1133
1165
  exports.getAllDataFromStorage = getAllDataFromStorage;
1134
1166
  exports.getI18nLocaleFromStorage = getI18nLocaleFromStorage;
1135
1167
  exports.getMessageCountFromStorage = getMessageCountFromStorage;