varminer-app-header 2.2.1 → 2.2.3
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.d.ts +28 -37
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +144 -294
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +145 -294
- package/dist/index.js.map +1 -1
- package/dist/utils/localStorage.d.ts +26 -35
- package/dist/utils/localStorage.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -79,21 +79,38 @@ interface UserDetailsItem {
|
|
|
79
79
|
* Does not write or overwrite this key; IAM app is the source of truth.
|
|
80
80
|
*/
|
|
81
81
|
declare function getStoredUserDetails(): UserDetailsItem | null;
|
|
82
|
+
/** Unique localStorage key for this header package. Used only for access token; user details come from IAM (linn-i-am-userDetails). */
|
|
83
|
+
declare const PERSIST_HEADER_KEY = "persist:varminer-app-header";
|
|
84
|
+
/** Auth shape stored under PERSIST_HEADER_KEY. Host app should call setHeaderAuth after login so the header can make authenticated calls. */
|
|
85
|
+
interface HeaderAuthState {
|
|
86
|
+
accessToken?: string;
|
|
87
|
+
refreshToken?: string;
|
|
88
|
+
user_id?: number | string;
|
|
89
|
+
[key: string]: unknown;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Store auth (e.g. accessToken) in the header's own persist key. Call this after login so the header can use the token for profile picture etc.
|
|
93
|
+
* User details are not stored here; they come from IAM (linn-i-am-userDetails).
|
|
94
|
+
*/
|
|
95
|
+
declare function setHeaderAuth(auth: HeaderAuthState | null): void;
|
|
96
|
+
/**
|
|
97
|
+
* User display data: from IAM only (linn-i-am-userDetails). No dependency on persist:userdb.
|
|
98
|
+
*/
|
|
82
99
|
declare const getUserDataFromStorage: () => {
|
|
83
|
-
name:
|
|
84
|
-
email:
|
|
85
|
-
role:
|
|
86
|
-
avatar
|
|
87
|
-
initials
|
|
100
|
+
name: string;
|
|
101
|
+
email: string;
|
|
102
|
+
role: string;
|
|
103
|
+
avatar?: string;
|
|
104
|
+
initials?: string;
|
|
88
105
|
} | null;
|
|
89
106
|
declare const getNotificationCountFromStorage: () => number;
|
|
90
107
|
declare const getMessageCountFromStorage: () => number | undefined;
|
|
91
108
|
declare const getI18nLocaleFromStorage: () => string | null;
|
|
92
109
|
declare const setI18nLocaleToStorage: (locale: string) => void;
|
|
93
110
|
/**
|
|
94
|
-
* Get
|
|
95
|
-
*
|
|
96
|
-
* @returns
|
|
111
|
+
* Get auth (and decoded token) for header use. Reads from header's own persist key first; falls back to persist:userdb only for access token.
|
|
112
|
+
* User details are not read from userdb—use getStoredUserDetails / getUserDataFromStorage (IAM) for that.
|
|
113
|
+
* @returns Object with auth, decodedToken, and other keys (user/userDetails/profile null when using header key)
|
|
97
114
|
*/
|
|
98
115
|
declare const getAllDataFromStorage: () => any;
|
|
99
116
|
/**
|
|
@@ -102,33 +119,7 @@ declare const getAllDataFromStorage: () => any;
|
|
|
102
119
|
* @returns Profile picture URL or null if user_id is not available
|
|
103
120
|
*/
|
|
104
121
|
declare const getProfilePictureUrl: (baseUrl?: string) => string | null;
|
|
105
|
-
|
|
106
|
-
* Get profile picture upload URL for the object store API.
|
|
107
|
-
* URL format: {baseUrl}/v1/objectStore/profilePictureUpload?tenantId=&userId=&roleId=
|
|
108
|
-
* @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
|
|
109
|
-
* @returns Full upload URL with query params, or null if tenantId, userId, or roleId is missing
|
|
110
|
-
*/
|
|
111
|
-
declare const getProfilePictureUploadUrl: (baseUrl?: string) => string | null;
|
|
112
|
-
/**
|
|
113
|
-
* Fetch profile picture from API with headers, get S3 URL, generate presigned URL, and return as blob URL
|
|
114
|
-
* This function:
|
|
115
|
-
* 1. Fetches the profile picture path from the API
|
|
116
|
-
* 2. Extracts the S3 filePath from the JSON response
|
|
117
|
-
* 3. Generates a presigned URL for the S3 object
|
|
118
|
-
* 4. Fetches the image using the presigned URL
|
|
119
|
-
* 5. Converts it to a blob URL that can be used in img src
|
|
120
|
-
* @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
|
|
121
|
-
* @param messageId - Optional message ID for X-Message-Id header (default: generated UUID)
|
|
122
|
-
* @param correlationId - Optional correlation ID for X-Correlation-Id header (default: generated UUID)
|
|
123
|
-
* @param awsConfig - AWS configuration (accessKeyId, secretAccessKey, region, bucket)
|
|
124
|
-
* @returns Promise that resolves to blob URL string or null if fetch fails
|
|
125
|
-
*/
|
|
126
|
-
declare const fetchProfilePictureAsBlobUrl: (baseUrl?: string, messageId?: string, correlationId?: string, awsConfig?: {
|
|
127
|
-
accessKeyId: string;
|
|
128
|
-
secretAccessKey: string;
|
|
129
|
-
region?: string;
|
|
130
|
-
bucket?: string;
|
|
131
|
-
}) => Promise<string | null>;
|
|
122
|
+
declare const fetchProfilePictureAsBlobUrl: (baseUrl?: string) => Promise<string | null>;
|
|
132
123
|
|
|
133
|
-
export { AppHeader, DrawerProvider, USER_DETAILS_STORAGE_KEY, fetchProfilePictureAsBlobUrl, getAllDataFromStorage, getI18nLocaleFromStorage, getMessageCountFromStorage, getNotificationCountFromStorage,
|
|
134
|
-
export type { AppHeaderProps, SupportedLanguage, Translations, UserDetailsItem, UserDetailsStoredResponse, UserProfile };
|
|
124
|
+
export { AppHeader, DrawerProvider, PERSIST_HEADER_KEY, USER_DETAILS_STORAGE_KEY, fetchProfilePictureAsBlobUrl, getAllDataFromStorage, getI18nLocaleFromStorage, getMessageCountFromStorage, getNotificationCountFromStorage, getProfilePictureUrl, getStoredUserDetails, getTranslations, getUserDataFromStorage, setHeaderAuth, setI18nLocaleToStorage, translations, useDrawer };
|
|
125
|
+
export type { AppHeaderProps, HeaderAuthState, SupportedLanguage, Translations, UserDetailsItem, UserDetailsStoredResponse, UserProfile };
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EACL,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,+BAA+B,EAC/B,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EACL,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,sBAAsB,EACtB,+BAA+B,EAC/B,0BAA0B,EAC1B,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,yBAAyB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.esm.js
CHANGED
|
@@ -57,77 +57,38 @@ function getStoredUserDetails() {
|
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
initials: name ? name.split(/\s+/).map((s) => s[0]).join("").slice(0, 2).toUpperCase() : undefined,
|
|
72
|
-
};
|
|
60
|
+
// --- Header-owned persist store (unique key, no conflict with other apps) ---
|
|
61
|
+
/** Unique localStorage key for this header package. Used only for access token; user details come from IAM (linn-i-am-userDetails). */
|
|
62
|
+
const PERSIST_HEADER_KEY = "persist:varminer-app-header";
|
|
63
|
+
/**
|
|
64
|
+
* Store auth (e.g. accessToken) in the header's own persist key. Call this after login so the header can use the token for profile picture etc.
|
|
65
|
+
* User details are not stored here; they come from IAM (linn-i-am-userDetails).
|
|
66
|
+
*/
|
|
67
|
+
function setHeaderAuth(auth) {
|
|
68
|
+
try {
|
|
69
|
+
const payload = auth ? { auth } : {};
|
|
70
|
+
localStorage.setItem(PERSIST_HEADER_KEY, JSON.stringify(payload));
|
|
73
71
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
const parsedOuter = JSON.parse(userDbString);
|
|
78
|
-
const parseNested = (value) => {
|
|
79
|
-
if (typeof value === 'string') {
|
|
80
|
-
try {
|
|
81
|
-
return JSON.parse(value);
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return value;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return value;
|
|
88
|
-
};
|
|
89
|
-
let userData = null;
|
|
90
|
-
if (parsedOuter.userDetails) {
|
|
91
|
-
const parsedUserDetails = parseNested(parsedOuter.userDetails);
|
|
92
|
-
userData = parsedUserDetails.user || parsedUserDetails.data || parsedUserDetails;
|
|
93
|
-
}
|
|
94
|
-
if (!userData && parsedOuter.user) {
|
|
95
|
-
const parsedUser = parseNested(parsedOuter.user);
|
|
96
|
-
userData = parsedUser.user || parsedUser.data || parsedUser.currentUser || parsedUser;
|
|
97
|
-
}
|
|
98
|
-
if (!userData && parsedOuter.authDetails) {
|
|
99
|
-
const parsedAuthDetails = parseNested(parsedOuter.authDetails);
|
|
100
|
-
if (parsedAuthDetails.auth) {
|
|
101
|
-
userData = parsedAuthDetails.auth.user || parsedAuthDetails.auth.userData;
|
|
102
|
-
}
|
|
103
|
-
if (!userData) {
|
|
104
|
-
userData = parsedAuthDetails.user || parsedAuthDetails.userData || parsedAuthDetails.currentUser;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (!userData && parsedOuter.profile) {
|
|
108
|
-
const parsedProfile = parseNested(parsedOuter.profile);
|
|
109
|
-
userData = parsedProfile.user || parsedProfile.data || parsedProfile;
|
|
110
|
-
}
|
|
111
|
-
if (userData) {
|
|
112
|
-
let name = userData.name || userData.fullName || userData.userName || "";
|
|
113
|
-
if (!name && (userData.firstName || userData.lastName)) {
|
|
114
|
-
name = [userData.firstName, userData.lastName].filter(Boolean).join(' ');
|
|
115
|
-
}
|
|
116
|
-
return {
|
|
117
|
-
name: name || "",
|
|
118
|
-
email: userData.email || userData.emailAddress || "",
|
|
119
|
-
role: userData.role || userData.userRole || userData.designation || userData.title || "",
|
|
120
|
-
avatar: userData.avatar || userData.profilePicture || userData.imageUrl || userData.profileImage,
|
|
121
|
-
initials: userData.initials,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
catch (err) {
|
|
126
|
-
console.error("Error parsing user data from localStorage:", err);
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
console.error("Error setting header auth:", err);
|
|
129
74
|
}
|
|
130
|
-
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* User display data: from IAM only (linn-i-am-userDetails). No dependency on persist:userdb.
|
|
78
|
+
*/
|
|
79
|
+
const getUserDataFromStorage = () => {
|
|
80
|
+
const iamUser = getStoredUserDetails();
|
|
81
|
+
if (!iamUser)
|
|
82
|
+
return null;
|
|
83
|
+
const name = [iamUser.firstName, iamUser.lastName].filter(Boolean).join(" ").trim();
|
|
84
|
+
const role = iamUser.roles?.length ? iamUser.roles[0] : iamUser.workInfo?.jobTitle ?? "";
|
|
85
|
+
return {
|
|
86
|
+
name: name || "",
|
|
87
|
+
email: iamUser.email || "",
|
|
88
|
+
role: role || "",
|
|
89
|
+
avatar: undefined,
|
|
90
|
+
initials: name ? name.split(/\s+/).map((s) => s[0]).join("").slice(0, 2).toUpperCase() : undefined,
|
|
91
|
+
};
|
|
131
92
|
};
|
|
132
93
|
const getNotificationCountFromStorage = () => {
|
|
133
94
|
const userDbString = localStorage.getItem("persist:userdb");
|
|
@@ -243,26 +204,91 @@ const decodeJWT = (token) => {
|
|
|
243
204
|
return null;
|
|
244
205
|
}
|
|
245
206
|
};
|
|
207
|
+
const emptyStorageResult = () => ({
|
|
208
|
+
auth: null,
|
|
209
|
+
user: null,
|
|
210
|
+
authDetails: null,
|
|
211
|
+
userDetails: null,
|
|
212
|
+
profile: null,
|
|
213
|
+
notifications: null,
|
|
214
|
+
messages: null,
|
|
215
|
+
app: null,
|
|
216
|
+
decodedToken: null,
|
|
217
|
+
rawData: null,
|
|
218
|
+
});
|
|
246
219
|
/**
|
|
247
|
-
* Get
|
|
248
|
-
*
|
|
249
|
-
* @returns
|
|
220
|
+
* Get auth (and decoded token) for header use. Reads from header's own persist key first; falls back to persist:userdb only for access token.
|
|
221
|
+
* User details are not read from userdb—use getStoredUserDetails / getUserDataFromStorage (IAM) for that.
|
|
222
|
+
* @returns Object with auth, decodedToken, and other keys (user/userDetails/profile null when using header key)
|
|
250
223
|
*/
|
|
251
224
|
const getAllDataFromStorage = () => {
|
|
225
|
+
// Prefer header-owned persist key (unique, no conflict with other apps)
|
|
226
|
+
const headerStr = localStorage.getItem(PERSIST_HEADER_KEY);
|
|
227
|
+
if (headerStr) {
|
|
228
|
+
try {
|
|
229
|
+
const parsed = JSON.parse(headerStr);
|
|
230
|
+
const auth = parsed?.auth;
|
|
231
|
+
const accessToken = auth?.accessToken ?? auth?.access_token ?? auth?.token;
|
|
232
|
+
if (accessToken && typeof accessToken === "string") {
|
|
233
|
+
const decodedToken = decodeJWT(accessToken);
|
|
234
|
+
return {
|
|
235
|
+
...emptyStorageResult(),
|
|
236
|
+
auth: auth,
|
|
237
|
+
decodedToken: decodedToken ? Object.fromEntries(Object.entries(decodedToken).map(([k, v]) => [k, v ?? null])) : null,
|
|
238
|
+
rawData: parsed,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
console.error("Error parsing header persist:", err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Fallback: try IAM persist key (persist:linn-i-am) then persist:userdb
|
|
247
|
+
const tryParsePersistForAuth = (raw) => {
|
|
248
|
+
if (!raw)
|
|
249
|
+
return null;
|
|
250
|
+
try {
|
|
251
|
+
const outer = JSON.parse(raw);
|
|
252
|
+
const parseNested = (v) => typeof v === "string" ? (() => { try {
|
|
253
|
+
return JSON.parse(v);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return v;
|
|
257
|
+
} })() : v;
|
|
258
|
+
for (const key of Object.keys(outer)) {
|
|
259
|
+
const parsed = parseNested(outer[key]);
|
|
260
|
+
if (parsed?.accessToken || parsed?.access_token || parsed?.token || parsed?.refreshToken)
|
|
261
|
+
return parsed;
|
|
262
|
+
if (parsed?.auth && typeof parsed.auth === "object") {
|
|
263
|
+
const a = parsed.auth;
|
|
264
|
+
if (a.accessToken || a.access_token || a.token)
|
|
265
|
+
return a;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
/* ignore */
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
};
|
|
274
|
+
const iamPersist = localStorage.getItem("persist:linn-i-am");
|
|
275
|
+
const iamAuth = tryParsePersistForAuth(iamPersist);
|
|
276
|
+
if (iamAuth) {
|
|
277
|
+
const token = (iamAuth.accessToken ?? iamAuth.access_token ?? iamAuth.token);
|
|
278
|
+
if (token) {
|
|
279
|
+
const decodedToken = decodeJWT(token);
|
|
280
|
+
return {
|
|
281
|
+
...emptyStorageResult(),
|
|
282
|
+
auth: iamAuth,
|
|
283
|
+
decodedToken: decodedToken ? Object.fromEntries(Object.entries(decodedToken).map(([k, v]) => [k, v ?? null])) : null,
|
|
284
|
+
rawData: iamAuth,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Fallback: read access token from persist:userdb (e.g. legacy or shared auth)
|
|
252
289
|
const userDbString = localStorage.getItem("persist:userdb");
|
|
253
290
|
if (!userDbString) {
|
|
254
|
-
return
|
|
255
|
-
auth: null,
|
|
256
|
-
user: null,
|
|
257
|
-
authDetails: null,
|
|
258
|
-
userDetails: null,
|
|
259
|
-
profile: null,
|
|
260
|
-
notifications: null,
|
|
261
|
-
messages: null,
|
|
262
|
-
app: null,
|
|
263
|
-
decodedToken: null,
|
|
264
|
-
rawData: null,
|
|
265
|
-
};
|
|
291
|
+
return emptyStorageResult();
|
|
266
292
|
}
|
|
267
293
|
try {
|
|
268
294
|
const parsedOuter = JSON.parse(userDbString);
|
|
@@ -281,17 +307,14 @@ const getAllDataFromStorage = () => {
|
|
|
281
307
|
// Parse all top-level keys dynamically (no hardcoding)
|
|
282
308
|
const parsedData = {};
|
|
283
309
|
let authData = null;
|
|
284
|
-
// Helper to recursively find auth data (contains accessToken or refreshToken)
|
|
310
|
+
// Helper to recursively find auth data (contains accessToken, access_token, token, or refreshToken)
|
|
285
311
|
const findAuthData = (obj) => {
|
|
286
312
|
if (!obj || typeof obj !== 'object')
|
|
287
313
|
return null;
|
|
288
|
-
|
|
289
|
-
if (obj.accessToken || obj.refreshToken) {
|
|
314
|
+
if (obj.accessToken || obj.access_token || obj.token || obj.refreshToken)
|
|
290
315
|
return obj;
|
|
291
|
-
}
|
|
292
|
-
// Recursively search in nested objects
|
|
293
316
|
for (const key in obj) {
|
|
294
|
-
if (
|
|
317
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
295
318
|
const nested = findAuthData(obj[key]);
|
|
296
319
|
if (nested)
|
|
297
320
|
return nested;
|
|
@@ -309,9 +332,11 @@ const getAllDataFromStorage = () => {
|
|
|
309
332
|
parsedData.auth = authData;
|
|
310
333
|
}
|
|
311
334
|
});
|
|
312
|
-
// Get accessToken and decode it
|
|
335
|
+
// Get accessToken (support accessToken, access_token, token) and decode it
|
|
313
336
|
let decodedToken = null;
|
|
314
|
-
const accessToken = authData?.accessToken
|
|
337
|
+
const accessToken = authData?.accessToken ?? authData?.access_token ?? authData?.token ??
|
|
338
|
+
parsedData.auth?.accessToken ?? parsedData.auth?.access_token ?? parsedData.auth?.token ??
|
|
339
|
+
parsedData.authDetails?.accessToken ?? parsedData.authDetails?.access_token ?? parsedData.authDetails?.token;
|
|
315
340
|
if (accessToken) {
|
|
316
341
|
decodedToken = decodeJWT(accessToken);
|
|
317
342
|
}
|
|
@@ -340,19 +365,7 @@ const getAllDataFromStorage = () => {
|
|
|
340
365
|
}
|
|
341
366
|
catch (err) {
|
|
342
367
|
console.error("Error parsing all data from localStorage:", err);
|
|
343
|
-
return {
|
|
344
|
-
auth: null,
|
|
345
|
-
user: null,
|
|
346
|
-
authDetails: null,
|
|
347
|
-
userDetails: null,
|
|
348
|
-
profile: null,
|
|
349
|
-
notifications: null,
|
|
350
|
-
messages: null,
|
|
351
|
-
app: null,
|
|
352
|
-
decodedToken: null,
|
|
353
|
-
rawData: null,
|
|
354
|
-
error: err instanceof Error ? err.message : 'Unknown error',
|
|
355
|
-
};
|
|
368
|
+
return { ...emptyStorageResult(), error: err instanceof Error ? err.message : "Unknown error" };
|
|
356
369
|
}
|
|
357
370
|
};
|
|
358
371
|
/**
|
|
@@ -374,206 +387,43 @@ const getProfilePictureUrl = (baseUrl = "http://objectstore.impact0mics.local:90
|
|
|
374
387
|
}
|
|
375
388
|
};
|
|
376
389
|
/**
|
|
377
|
-
*
|
|
378
|
-
* URL
|
|
379
|
-
* @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
|
|
380
|
-
* @returns Full upload URL with query params, or null if tenantId, userId, or roleId is missing
|
|
381
|
-
*/
|
|
382
|
-
const getProfilePictureUploadUrl = (baseUrl = "http://objectstore.impact0mics.local:9012") => {
|
|
383
|
-
try {
|
|
384
|
-
const allData = getAllDataFromStorage();
|
|
385
|
-
const iamUser = getStoredUserDetails();
|
|
386
|
-
const tenantId = allData.decodedToken?.tenant_id ??
|
|
387
|
-
allData.decodedToken?.tenant ??
|
|
388
|
-
allData.auth?.tenant_id ??
|
|
389
|
-
allData.auth?.tenant ??
|
|
390
|
-
null;
|
|
391
|
-
const userId = allData.decodedToken?.user_id ??
|
|
392
|
-
allData.auth?.user_id ??
|
|
393
|
-
iamUser?.userId ??
|
|
394
|
-
null;
|
|
395
|
-
const roleId = allData.decodedToken?.role_id ??
|
|
396
|
-
allData.auth?.role_id ??
|
|
397
|
-
null;
|
|
398
|
-
if (tenantId == null || userId == null || roleId == null)
|
|
399
|
-
return null;
|
|
400
|
-
const cleanBaseUrl = baseUrl.replace(/\/$/, "");
|
|
401
|
-
const params = new URLSearchParams({
|
|
402
|
-
tenantId: String(tenantId),
|
|
403
|
-
userId: String(userId),
|
|
404
|
-
roleId: String(roleId),
|
|
405
|
-
});
|
|
406
|
-
return `${cleanBaseUrl}/v1/objectStore/profilePictureUpload?${params.toString()}`;
|
|
407
|
-
}
|
|
408
|
-
catch (err) {
|
|
409
|
-
console.error("Error getting profile picture upload URL:", err);
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
/**
|
|
414
|
-
* Generate AWS S3 presigned URL for accessing S3 object
|
|
415
|
-
* @param s3Url - Full S3 URL (e.g., https://bucket.s3.region.amazonaws.com/key)
|
|
416
|
-
* @param accessKeyId - AWS Access Key ID
|
|
417
|
-
* @param secretAccessKey - AWS Secret Access Key
|
|
418
|
-
* @param region - AWS Region (default: ap-south-2)
|
|
419
|
-
* @param expiresIn - Expiration time in seconds (default: 3600 = 1 hour)
|
|
420
|
-
* @returns Presigned URL string
|
|
421
|
-
*/
|
|
422
|
-
const generateS3PresignedUrl = async (s3Url, accessKeyId, secretAccessKey, region = "ap-south-2", expiresIn = 3600) => {
|
|
423
|
-
try {
|
|
424
|
-
// Parse S3 URL to extract bucket, region, and key
|
|
425
|
-
// Format: https://bucket.s3.region.amazonaws.com/key or https://bucket.s3-region.amazonaws.com/key
|
|
426
|
-
const url = new URL(s3Url);
|
|
427
|
-
const hostnameParts = url.hostname.split('.');
|
|
428
|
-
let bucket = hostnameParts[0];
|
|
429
|
-
let extractedRegion = region;
|
|
430
|
-
// Try to extract region from hostname (format: bucket.s3.region.amazonaws.com)
|
|
431
|
-
if (hostnameParts.length >= 3 && hostnameParts[1] === 's3') {
|
|
432
|
-
extractedRegion = hostnameParts[2] || region;
|
|
433
|
-
}
|
|
434
|
-
else if (hostnameParts.length >= 2 && hostnameParts[1].startsWith('s3-')) {
|
|
435
|
-
// Format: bucket.s3-region.amazonaws.com
|
|
436
|
-
extractedRegion = hostnameParts[1].substring(3) || region;
|
|
437
|
-
}
|
|
438
|
-
const key = url.pathname.substring(1); // Remove leading slash
|
|
439
|
-
// AWS credentials
|
|
440
|
-
const awsAccessKeyId = accessKeyId;
|
|
441
|
-
const awsSecretAccessKey = secretAccessKey;
|
|
442
|
-
const awsRegion = extractedRegion;
|
|
443
|
-
// Current timestamp
|
|
444
|
-
const now = new Date();
|
|
445
|
-
const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
446
|
-
const amzDate = dateStamp + 'T' + now.toISOString().slice(11, 19).replace(/[:-]/g, '') + 'Z';
|
|
447
|
-
// Step 1: Create canonical request
|
|
448
|
-
const canonicalUri = '/' + encodeURIComponent(key).replace(/%2F/g, '/');
|
|
449
|
-
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`;
|
|
450
|
-
const canonicalHeaders = `host:${bucket}.s3.${awsRegion}.amazonaws.com\n`;
|
|
451
|
-
const signedHeaders = 'host';
|
|
452
|
-
const payloadHash = 'UNSIGNED-PAYLOAD';
|
|
453
|
-
const canonicalRequest = `GET\n${canonicalUri}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
|
|
454
|
-
// Step 2: Create string to sign
|
|
455
|
-
const algorithm = 'AWS4-HMAC-SHA256';
|
|
456
|
-
const credentialScope = `${dateStamp}/${awsRegion}/s3/aws4_request`;
|
|
457
|
-
const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${await sha256(canonicalRequest)}`;
|
|
458
|
-
// Step 3: Calculate signature
|
|
459
|
-
const kDate = await hmacSha256('AWS4' + awsSecretAccessKey, dateStamp);
|
|
460
|
-
const kRegion = await hmacSha256(kDate, awsRegion);
|
|
461
|
-
const kService = await hmacSha256(kRegion, 's3');
|
|
462
|
-
const kSigning = await hmacSha256(kService, 'aws4_request');
|
|
463
|
-
const signature = await hmacSha256(kSigning, stringToSign);
|
|
464
|
-
// Convert signature to hex
|
|
465
|
-
const signatureHex = arrayBufferToHex(signature);
|
|
466
|
-
// Step 4: Construct presigned URL
|
|
467
|
-
const presignedUrl = `https://${bucket}.s3.${awsRegion}.amazonaws.com${canonicalUri}?${canonicalQuerystring}&X-Amz-Signature=${signatureHex}`;
|
|
468
|
-
return presignedUrl;
|
|
469
|
-
}
|
|
470
|
-
catch (err) {
|
|
471
|
-
console.error("Error generating S3 presigned URL:", err);
|
|
472
|
-
throw err;
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
// Helper function for SHA-256 hashing
|
|
476
|
-
const sha256 = async (message) => {
|
|
477
|
-
const msgBuffer = new TextEncoder().encode(message);
|
|
478
|
-
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
|
479
|
-
return Array.from(new Uint8Array(hashBuffer))
|
|
480
|
-
.map(b => b.toString(16).padStart(2, '0'))
|
|
481
|
-
.join('');
|
|
482
|
-
};
|
|
483
|
-
// Helper function for HMAC-SHA256
|
|
484
|
-
const hmacSha256 = async (key, message) => {
|
|
485
|
-
const encoder = new TextEncoder();
|
|
486
|
-
let keyBuffer;
|
|
487
|
-
if (typeof key === 'string') {
|
|
488
|
-
keyBuffer = encoder.encode(key);
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
491
|
-
keyBuffer = new Uint8Array(key);
|
|
492
|
-
}
|
|
493
|
-
const messageBuffer = encoder.encode(message);
|
|
494
|
-
const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
495
|
-
return await crypto.subtle.sign('HMAC', cryptoKey, messageBuffer);
|
|
496
|
-
};
|
|
497
|
-
// Helper function to convert ArrayBuffer to hex string
|
|
498
|
-
const arrayBufferToHex = (buffer) => {
|
|
499
|
-
return Array.from(new Uint8Array(buffer))
|
|
500
|
-
.map(b => b.toString(16).padStart(2, '0'))
|
|
501
|
-
.join('');
|
|
502
|
-
};
|
|
503
|
-
/**
|
|
504
|
-
* Fetch profile picture from API with headers, get S3 URL, generate presigned URL, and return as blob URL
|
|
505
|
-
* This function:
|
|
506
|
-
* 1. Fetches the profile picture path from the API
|
|
507
|
-
* 2. Extracts the S3 filePath from the JSON response
|
|
508
|
-
* 3. Generates a presigned URL for the S3 object
|
|
509
|
-
* 4. Fetches the image using the presigned URL
|
|
510
|
-
* 5. Converts it to a blob URL that can be used in img src
|
|
390
|
+
* Fetch profile picture from object store API (plain GET with Authorization header).
|
|
391
|
+
* API returns the image directly; response is converted to a blob URL for use in img src.
|
|
511
392
|
* @param baseUrl - Base URL for the object store API (default: http://objectstore.impact0mics.local:9012)
|
|
512
|
-
* @param messageId - Optional message ID for X-Message-Id header (default: generated UUID)
|
|
513
|
-
* @param correlationId - Optional correlation ID for X-Correlation-Id header (default: generated UUID)
|
|
514
|
-
* @param awsConfig - AWS configuration (accessKeyId, secretAccessKey, region, bucket)
|
|
515
393
|
* @returns Promise that resolves to blob URL string or null if fetch fails
|
|
516
394
|
*/
|
|
517
|
-
|
|
395
|
+
/** Resolve access token from auth object (supports accessToken, access_token, token). */
|
|
396
|
+
function getAccessTokenFromAuth(auth) {
|
|
397
|
+
if (!auth || typeof auth !== "object")
|
|
398
|
+
return undefined;
|
|
399
|
+
const token = auth.accessToken ??
|
|
400
|
+
auth.access_token ??
|
|
401
|
+
auth.token;
|
|
402
|
+
return typeof token === "string" && token.length > 0 ? token : undefined;
|
|
403
|
+
}
|
|
404
|
+
const fetchProfilePictureAsBlobUrl = async (baseUrl = "http://objectstore.impact0mics.local:9012") => {
|
|
518
405
|
try {
|
|
519
406
|
const profilePictureUrl = getProfilePictureUrl(baseUrl);
|
|
520
|
-
if (!profilePictureUrl)
|
|
521
|
-
return null;
|
|
522
|
-
}
|
|
523
|
-
// AWS credentials (default values provided by user)
|
|
524
|
-
const defaultAwsConfig = {
|
|
525
|
-
accessKeyId: "AKIAVRUVTJGLBCYZEI5L",
|
|
526
|
-
secretAccessKey: "kbMVqmx6s29njcS5P48qAqpXlb1oir6+b7zu1Qxi",
|
|
527
|
-
region: "ap-south-2",
|
|
528
|
-
bucket: "development-varminer-test"
|
|
529
|
-
};
|
|
530
|
-
const finalAwsConfig = awsConfig || defaultAwsConfig;
|
|
531
|
-
// Generate message ID and correlation ID if not provided
|
|
532
|
-
const msgId = messageId || `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
533
|
-
const corrId = correlationId || `corr-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
534
|
-
// Step 1: Fetch the profile picture path from API (returns JSON with filePath)
|
|
535
|
-
const apiResponse = await fetch(profilePictureUrl, {
|
|
536
|
-
method: 'GET',
|
|
537
|
-
headers: {
|
|
538
|
-
'X-Message-Id': msgId,
|
|
539
|
-
'X-Correlation-Id': corrId,
|
|
540
|
-
},
|
|
541
|
-
});
|
|
542
|
-
// Check if the API response is successful
|
|
543
|
-
if (!apiResponse.ok) {
|
|
544
|
-
console.warn(`Failed to fetch profile picture path: ${apiResponse.status} ${apiResponse.statusText}`);
|
|
545
|
-
return null;
|
|
546
|
-
}
|
|
547
|
-
// Parse JSON response
|
|
548
|
-
const apiData = await apiResponse.json();
|
|
549
|
-
// Extract filePath from response
|
|
550
|
-
const s3Url = apiData?.filePath;
|
|
551
|
-
if (!s3Url || typeof s3Url !== 'string') {
|
|
552
|
-
console.warn('Profile picture API response does not contain valid filePath');
|
|
407
|
+
if (!profilePictureUrl)
|
|
553
408
|
return null;
|
|
409
|
+
const allData = getAllDataFromStorage();
|
|
410
|
+
const accessToken = getAccessTokenFromAuth(allData.auth);
|
|
411
|
+
const headers = {};
|
|
412
|
+
if (accessToken) {
|
|
413
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
554
414
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
const imageResponse = await fetch(presignedUrl, {
|
|
559
|
-
method: 'GET',
|
|
560
|
-
});
|
|
561
|
-
// Check if the image response is successful
|
|
562
|
-
if (!imageResponse.ok) {
|
|
563
|
-
console.warn(`Failed to fetch profile picture image: ${imageResponse.status} ${imageResponse.statusText}`);
|
|
415
|
+
const response = await fetch(profilePictureUrl, { method: "GET", headers });
|
|
416
|
+
if (!response.ok) {
|
|
417
|
+
console.warn(`Failed to fetch profile picture: ${response.status} ${response.statusText}`);
|
|
564
418
|
return null;
|
|
565
419
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if (!contentType || !contentType.startsWith('image/')) {
|
|
420
|
+
const contentType = response.headers.get("content-type");
|
|
421
|
+
if (!contentType || !contentType.startsWith("image/")) {
|
|
569
422
|
console.warn(`Profile picture response is not an image: ${contentType}`);
|
|
570
423
|
return null;
|
|
571
424
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
// Step 5: Create blob URL
|
|
575
|
-
const blobUrl = URL.createObjectURL(blob);
|
|
576
|
-
return blobUrl;
|
|
425
|
+
const blob = await response.blob();
|
|
426
|
+
return URL.createObjectURL(blob);
|
|
577
427
|
}
|
|
578
428
|
catch (err) {
|
|
579
429
|
console.error("Error fetching profile picture:", err);
|
|
@@ -1164,5 +1014,5 @@ const AppHeader = ({ language: languageProp }) => {
|
|
|
1164
1014
|
}, children: jsx(MoreIcon, {}) }) })] }) }), renderMobileMenu, renderMenu] }));
|
|
1165
1015
|
};
|
|
1166
1016
|
|
|
1167
|
-
export { AppHeader, DrawerProvider, USER_DETAILS_STORAGE_KEY, fetchProfilePictureAsBlobUrl, getAllDataFromStorage, getI18nLocaleFromStorage, getMessageCountFromStorage, getNotificationCountFromStorage,
|
|
1017
|
+
export { AppHeader, DrawerProvider, PERSIST_HEADER_KEY, USER_DETAILS_STORAGE_KEY, fetchProfilePictureAsBlobUrl, getAllDataFromStorage, getI18nLocaleFromStorage, getMessageCountFromStorage, getNotificationCountFromStorage, getProfilePictureUrl, getStoredUserDetails, getTranslations, getUserDataFromStorage, setHeaderAuth, setI18nLocaleToStorage, translations, useDrawer };
|
|
1168
1018
|
//# sourceMappingURL=index.esm.js.map
|