varminer-app-header 2.6.2 → 2.6.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/AppHeader.js +506 -0
- package/dist/DrawerContext.js +22 -0
- package/dist/LanguageSelector.js +82 -0
- package/dist/autoInjectTenantFallbackCss.d.ts +2 -0
- package/dist/autoInjectTenantFallbackCss.d.ts.map +1 -0
- package/dist/generatedTenantFallbackCss.d.ts +3 -0
- package/dist/generatedTenantFallbackCss.d.ts.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.css +1 -1
- package/dist/index.esm.js +104 -12
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +104 -12
- package/dist/index.js.map +1 -1
- package/dist/injectTenantFallbackCss.d.ts +2 -0
- package/dist/injectTenantFallbackCss.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/localStorage.d.ts +2 -1
- package/dist/utils/localStorage.d.ts.map +1 -1
- package/dist/utils/localStorage.js +571 -0
- package/dist/utils/translations.js +25 -0
- package/package.json +4 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injectTenantFallbackCss.d.ts","sourceRoot":"","sources":["../src/injectTenantFallbackCss.ts"],"names":[],"mappings":"AAiBA,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA6B7D"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -52,7 +52,8 @@ export interface HeaderAuthState {
|
|
|
52
52
|
*/
|
|
53
53
|
export declare function setHeaderAuth(auth: HeaderAuthState | null): void;
|
|
54
54
|
/**
|
|
55
|
-
* User display data:
|
|
55
|
+
* User display data: prefers linn-i-am-userDetails (IAM), falls back to
|
|
56
|
+
* persist:userdb → profileInformation.userDetails, then authDetails.auth.user.
|
|
56
57
|
*/
|
|
57
58
|
export declare const getUserDataFromStorage: () => {
|
|
58
59
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"localStorage.d.ts","sourceRoot":"","sources":["../../src/utils/localStorage.ts"],"names":[],"mappings":"AACA,uGAAuG;AACvG,eAAO,MAAM,wBAAwB,0BAA0B,CAAC;AAEhE,8DAA8D;AAC9D,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,YAAY,EAAE,eAAe,EAAE,CAAC;KACjC,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,eAAe,GAAG,IAAI,CAc7D;AAGD,uIAAuI;AACvI,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,6IAA6I;AAC7I,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI,CAOhE;AAED
|
|
1
|
+
{"version":3,"file":"localStorage.d.ts","sourceRoot":"","sources":["../../src/utils/localStorage.ts"],"names":[],"mappings":"AACA,uGAAuG;AACvG,eAAO,MAAM,wBAAwB,0BAA0B,CAAC;AAEhE,8DAA8D;AAC9D,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,YAAY,EAAE,eAAe,EAAE,CAAC;KACjC,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,eAAe,GAAG,IAAI,CAc7D;AAGD,uIAAuI;AACvI,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,6IAA6I;AAC7I,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI,CAOhE;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAO;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAoE7H,CAAC;AAEF,eAAO,MAAM,+BAA+B,QAAO,MAkClD,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,MAAM,GAAG,SAkCtD,CAAC;AAEF,eAAO,MAAM,wBAAwB,QAAO,MAAM,GAAG,IAOpD,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,IAMvD,CAAC;AA4CF;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,WAkJjC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,UAAU,MAAM,GAAG,IAAI,KAAG,MAAM,GAAG,IAavE,CAAC;AAyFF;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,SAAS,CA+B7D;AAED,eAAO,MAAM,4BAA4B,GACvC,UAAU,MAAM,GAAG,IAAI,EACvB,sBAAsB,MAAM,GAAG,IAAI,KAClC,OAAO,CAAC,MAAM,GAAG,IAAI,CA8FvB,CAAC"}
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
// --- IAM app user details (shared from IAM after verify-OTP, same origin) ---
|
|
2
|
+
/** localStorage key used by IAM app for user details (GET /v1/userManagement/userDetails response). */
|
|
3
|
+
export const USER_DETAILS_STORAGE_KEY = "linn-i-am-userDetails";
|
|
4
|
+
/**
|
|
5
|
+
* Read user details from IAM app localStorage (set after verify-OTP on same origin).
|
|
6
|
+
* Does not write or overwrite this key; IAM app is the source of truth.
|
|
7
|
+
*/
|
|
8
|
+
export function getStoredUserDetails() {
|
|
9
|
+
try {
|
|
10
|
+
const raw = localStorage.getItem(USER_DETAILS_STORAGE_KEY);
|
|
11
|
+
if (!raw)
|
|
12
|
+
return null;
|
|
13
|
+
const parsed = JSON.parse(raw);
|
|
14
|
+
if (parsed.status !== "SUCCESS" || !parsed.data?.usersDetails?.length) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return parsed.data.usersDetails[0];
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// --- Header-owned persist store (unique key, no conflict with other apps) ---
|
|
24
|
+
/** Unique localStorage key for this header package. Used only for access token; user details come from IAM (linn-i-am-userDetails). */
|
|
25
|
+
export const PERSIST_HEADER_KEY = "persist:varminer-app-header";
|
|
26
|
+
/**
|
|
27
|
+
* 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.
|
|
28
|
+
* User details are not stored here; they come from IAM (linn-i-am-userDetails).
|
|
29
|
+
*/
|
|
30
|
+
export function setHeaderAuth(auth) {
|
|
31
|
+
try {
|
|
32
|
+
const payload = auth ? { auth } : {};
|
|
33
|
+
localStorage.setItem(PERSIST_HEADER_KEY, JSON.stringify(payload));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error("Error setting header auth:", err);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* User display data: from IAM only (linn-i-am-userDetails). No dependency on persist:userdb.
|
|
41
|
+
*/
|
|
42
|
+
export const getUserDataFromStorage = () => {
|
|
43
|
+
const iamUser = getStoredUserDetails();
|
|
44
|
+
if (!iamUser)
|
|
45
|
+
return null;
|
|
46
|
+
const name = [iamUser.firstName, iamUser.lastName].filter(Boolean).join(" ").trim();
|
|
47
|
+
const role = iamUser.roles?.length ? iamUser.roles[0] : iamUser.workInfo?.jobTitle ?? "";
|
|
48
|
+
return {
|
|
49
|
+
name: name || "",
|
|
50
|
+
email: iamUser.email || "",
|
|
51
|
+
role: role || "",
|
|
52
|
+
avatar: undefined,
|
|
53
|
+
initials: name ? name.split(/\s+/).map((s) => s[0]).join("").slice(0, 2).toUpperCase() : undefined,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export const getNotificationCountFromStorage = () => {
|
|
57
|
+
const userDbString = localStorage.getItem("persist:userdb");
|
|
58
|
+
if (userDbString) {
|
|
59
|
+
try {
|
|
60
|
+
const parsedOuter = JSON.parse(userDbString);
|
|
61
|
+
const parseNested = (value) => {
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(value);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
};
|
|
72
|
+
if (parsedOuter.notifications) {
|
|
73
|
+
const parsedNotifications = parseNested(parsedOuter.notifications);
|
|
74
|
+
const count = parsedNotifications.count || parsedNotifications.unreadCount || parsedNotifications.total;
|
|
75
|
+
if (typeof count === 'number')
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
if (parsedOuter.app) {
|
|
79
|
+
const parsedApp = parseNested(parsedOuter.app);
|
|
80
|
+
if (parsedApp.notificationCount !== undefined) {
|
|
81
|
+
return parsedApp.notificationCount;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error("Error parsing notification count from localStorage:", err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
};
|
|
91
|
+
export const getMessageCountFromStorage = () => {
|
|
92
|
+
const userDbString = localStorage.getItem("persist:userdb");
|
|
93
|
+
if (userDbString) {
|
|
94
|
+
try {
|
|
95
|
+
const parsedOuter = JSON.parse(userDbString);
|
|
96
|
+
const parseNested = (value) => {
|
|
97
|
+
if (typeof value === 'string') {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(value);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
};
|
|
107
|
+
if (parsedOuter.messages) {
|
|
108
|
+
const parsedMessages = parseNested(parsedOuter.messages);
|
|
109
|
+
const count = parsedMessages.count || parsedMessages.unreadCount || parsedMessages.total;
|
|
110
|
+
if (typeof count === 'number')
|
|
111
|
+
return count;
|
|
112
|
+
}
|
|
113
|
+
if (parsedOuter.app) {
|
|
114
|
+
const parsedApp = parseNested(parsedOuter.app);
|
|
115
|
+
if (parsedApp.messageCount !== undefined) {
|
|
116
|
+
return parsedApp.messageCount;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.error("Error parsing message count from localStorage:", err);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
};
|
|
126
|
+
export const getI18nLocaleFromStorage = () => {
|
|
127
|
+
try {
|
|
128
|
+
return localStorage.getItem("lnc-i18n");
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
console.error("Error getting i18n locale from localStorage:", err);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
export const setI18nLocaleToStorage = (locale) => {
|
|
136
|
+
try {
|
|
137
|
+
localStorage.setItem("lnc-i18n", locale);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
console.error("Error setting i18n locale to localStorage:", err);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* Decodes a JWT token and returns its payload
|
|
145
|
+
* @param token - JWT token string
|
|
146
|
+
* @returns Decoded payload object or null if decoding fails
|
|
147
|
+
*/
|
|
148
|
+
const decodeJWT = (token) => {
|
|
149
|
+
try {
|
|
150
|
+
if (!token)
|
|
151
|
+
return null;
|
|
152
|
+
// JWT format: header.payload.signature
|
|
153
|
+
const parts = token.split('.');
|
|
154
|
+
if (parts.length !== 3)
|
|
155
|
+
return null;
|
|
156
|
+
// Decode the payload (second part)
|
|
157
|
+
const payload = parts[1];
|
|
158
|
+
// Base64 decode and parse JSON
|
|
159
|
+
// Replace URL-safe base64 characters and add padding if needed
|
|
160
|
+
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
161
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
162
|
+
const decoded = atob(padded);
|
|
163
|
+
return JSON.parse(decoded);
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
console.error("Error decoding JWT token:", err);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const emptyStorageResult = () => ({
|
|
171
|
+
auth: null,
|
|
172
|
+
user: null,
|
|
173
|
+
authDetails: null,
|
|
174
|
+
userDetails: null,
|
|
175
|
+
profile: null,
|
|
176
|
+
notifications: null,
|
|
177
|
+
messages: null,
|
|
178
|
+
app: null,
|
|
179
|
+
decodedToken: null,
|
|
180
|
+
rawData: null,
|
|
181
|
+
});
|
|
182
|
+
/**
|
|
183
|
+
* 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.
|
|
184
|
+
* User details are not read from userdb—use getStoredUserDetails / getUserDataFromStorage (IAM) for that.
|
|
185
|
+
* @returns Object with auth, decodedToken, and other keys (user/userDetails/profile null when using header key)
|
|
186
|
+
*/
|
|
187
|
+
export const getAllDataFromStorage = () => {
|
|
188
|
+
// Prefer header-owned persist key (unique, no conflict with other apps)
|
|
189
|
+
const headerStr = localStorage.getItem(PERSIST_HEADER_KEY);
|
|
190
|
+
if (headerStr) {
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(headerStr);
|
|
193
|
+
const auth = parsed?.auth;
|
|
194
|
+
const accessToken = auth?.accessToken ?? auth?.access_token ?? auth?.token;
|
|
195
|
+
if (accessToken && typeof accessToken === "string") {
|
|
196
|
+
const decodedToken = decodeJWT(accessToken);
|
|
197
|
+
return {
|
|
198
|
+
...emptyStorageResult(),
|
|
199
|
+
auth: auth,
|
|
200
|
+
decodedToken: decodedToken ? Object.fromEntries(Object.entries(decodedToken).map(([k, v]) => [k, v ?? null])) : null,
|
|
201
|
+
rawData: parsed,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
console.error("Error parsing header persist:", err);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Fallback: try IAM persist key (persist:linn-i-am) then persist:userdb
|
|
210
|
+
const tryParsePersistForAuth = (raw) => {
|
|
211
|
+
if (!raw)
|
|
212
|
+
return null;
|
|
213
|
+
try {
|
|
214
|
+
const outer = JSON.parse(raw);
|
|
215
|
+
const parseNested = (v) => typeof v === "string" ? (() => { try {
|
|
216
|
+
return JSON.parse(v);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return v;
|
|
220
|
+
} })() : v;
|
|
221
|
+
for (const key of Object.keys(outer)) {
|
|
222
|
+
const parsed = parseNested(outer[key]);
|
|
223
|
+
if (parsed?.accessToken || parsed?.access_token || parsed?.token || parsed?.refreshToken)
|
|
224
|
+
return parsed;
|
|
225
|
+
if (parsed?.auth && typeof parsed.auth === "object") {
|
|
226
|
+
const a = parsed.auth;
|
|
227
|
+
if (a.accessToken || a.access_token || a.token)
|
|
228
|
+
return a;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
/* ignore */
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
};
|
|
237
|
+
const iamPersist = localStorage.getItem("persist:linn-i-am");
|
|
238
|
+
const iamAuth = tryParsePersistForAuth(iamPersist);
|
|
239
|
+
if (iamAuth) {
|
|
240
|
+
const token = (iamAuth.accessToken ?? iamAuth.access_token ?? iamAuth.token);
|
|
241
|
+
if (token) {
|
|
242
|
+
const decodedToken = decodeJWT(token);
|
|
243
|
+
return {
|
|
244
|
+
...emptyStorageResult(),
|
|
245
|
+
auth: iamAuth,
|
|
246
|
+
decodedToken: decodedToken ? Object.fromEntries(Object.entries(decodedToken).map(([k, v]) => [k, v ?? null])) : null,
|
|
247
|
+
rawData: iamAuth,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Fallback: read access token from persist:userdb (e.g. legacy or shared auth)
|
|
252
|
+
const userDbString = localStorage.getItem("persist:userdb");
|
|
253
|
+
if (!userDbString) {
|
|
254
|
+
return emptyStorageResult();
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const parsedOuter = JSON.parse(userDbString);
|
|
258
|
+
// Helper function to parse nested JSON strings
|
|
259
|
+
const parseNested = (value) => {
|
|
260
|
+
if (typeof value === 'string') {
|
|
261
|
+
try {
|
|
262
|
+
return JSON.parse(value);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return value;
|
|
269
|
+
};
|
|
270
|
+
// Parse all top-level keys dynamically (no hardcoding)
|
|
271
|
+
const parsedData = {};
|
|
272
|
+
let authData = null;
|
|
273
|
+
// Helper to recursively find auth data (contains accessToken, access_token, token, or refreshToken)
|
|
274
|
+
const findAuthData = (obj) => {
|
|
275
|
+
if (!obj || typeof obj !== 'object')
|
|
276
|
+
return null;
|
|
277
|
+
if (obj.accessToken || obj.access_token || obj.token || obj.refreshToken)
|
|
278
|
+
return obj;
|
|
279
|
+
for (const key in obj) {
|
|
280
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
281
|
+
const nested = findAuthData(obj[key]);
|
|
282
|
+
if (nested)
|
|
283
|
+
return nested;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
};
|
|
288
|
+
// Parse all keys in parsedOuter dynamically
|
|
289
|
+
Object.keys(parsedOuter).forEach((key) => {
|
|
290
|
+
parsedData[key] = parseNested(parsedOuter[key]);
|
|
291
|
+
// Dynamically find auth data in any structure
|
|
292
|
+
const foundAuth = findAuthData(parsedData[key]);
|
|
293
|
+
if (foundAuth && !authData) {
|
|
294
|
+
authData = foundAuth;
|
|
295
|
+
parsedData.auth = authData;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
// Get accessToken (support accessToken, access_token, token) and decode it
|
|
299
|
+
let decodedToken = null;
|
|
300
|
+
const accessToken = authData?.accessToken ?? authData?.access_token ?? authData?.token ??
|
|
301
|
+
parsedData.auth?.accessToken ?? parsedData.auth?.access_token ?? parsedData.auth?.token ??
|
|
302
|
+
parsedData.authDetails?.accessToken ?? parsedData.authDetails?.access_token ?? parsedData.authDetails?.token;
|
|
303
|
+
if (accessToken) {
|
|
304
|
+
decodedToken = decodeJWT(accessToken);
|
|
305
|
+
}
|
|
306
|
+
// Extract all auth-related fields from authData dynamically (no hardcoding)
|
|
307
|
+
const authFields = authData ? Object.keys(authData).reduce((acc, key) => {
|
|
308
|
+
acc[key] = authData[key] !== undefined ? authData[key] : null;
|
|
309
|
+
return acc;
|
|
310
|
+
}, {}) : null;
|
|
311
|
+
// Build return object dynamically with all parsed data
|
|
312
|
+
const result = {
|
|
313
|
+
...Object.keys(parsedData).reduce((acc, key) => {
|
|
314
|
+
acc[key] = parsedData[key] !== undefined ? parsedData[key] : null;
|
|
315
|
+
return acc;
|
|
316
|
+
}, {}),
|
|
317
|
+
decodedToken: decodedToken ? Object.keys(decodedToken).reduce((acc, key) => {
|
|
318
|
+
acc[key] = decodedToken[key] !== undefined ? decodedToken[key] : null;
|
|
319
|
+
return acc;
|
|
320
|
+
}, {}) : null,
|
|
321
|
+
rawData: parsedOuter, // Include raw parsed data for any other fields
|
|
322
|
+
};
|
|
323
|
+
// Add auth field if we have authData
|
|
324
|
+
if (authFields) {
|
|
325
|
+
result.auth = authFields;
|
|
326
|
+
}
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
console.error("Error parsing all data from localStorage:", err);
|
|
331
|
+
return { ...emptyStorageResult(), error: err instanceof Error ? err.message : "Unknown error" };
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Get profile picture URL from object store API using user_id.
|
|
336
|
+
* @param baseUrl - Base URL for the object store API (e.g. from objectStoreUrl prop). When missing or empty, returns null.
|
|
337
|
+
* @returns Profile picture URL or null if user_id or baseUrl is not available
|
|
338
|
+
*/
|
|
339
|
+
export const getProfilePictureUrl = (baseUrl) => {
|
|
340
|
+
try {
|
|
341
|
+
const url = typeof baseUrl === "string" && baseUrl.trim() ? baseUrl.trim() : null;
|
|
342
|
+
if (!url)
|
|
343
|
+
return null;
|
|
344
|
+
const allData = getAllDataFromStorage();
|
|
345
|
+
const userId = allData.decodedToken?.user_id ?? allData.auth?.user_id ?? getStoredUserDetails()?.userId ?? null;
|
|
346
|
+
if (userId == null)
|
|
347
|
+
return null;
|
|
348
|
+
return `${url.replace(/\/$/, "")}/v1/objectStore/profilePicture/path/${userId}`;
|
|
349
|
+
}
|
|
350
|
+
catch (err) {
|
|
351
|
+
console.error("Error getting profile picture URL:", err);
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
/** Extract S3 object key from s3Uri (e.g. s3://bucket/key → key). */
|
|
356
|
+
function getKeyFromS3Uri(s3Uri) {
|
|
357
|
+
const match = /^s3:\/\/[^/]+\/(.+)$/.exec(s3Uri.trim());
|
|
358
|
+
return match ? match[1] : null;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Fetch profile picture: (1) GET path API for JSON with filePath/s3Uri, (2) GET download API with key; download returns JSON with base64 fileContent.
|
|
362
|
+
* Path API: { statusResponse, filePath, s3Uri, errors }. Download API: { statusResponse, fileName, contentType, fileContent (base64), errors }.
|
|
363
|
+
* @param baseUrl - Base URL for the object store API (e.g. from objectStoreUrl prop). When missing or empty, returns null without fetching.
|
|
364
|
+
* @returns Promise that resolves to blob URL string or null if fetch fails or baseUrl not provided
|
|
365
|
+
*/
|
|
366
|
+
/** Resolve access token from auth object (supports accessToken, access_token, token). */
|
|
367
|
+
function getAccessTokenFromAuth(auth) {
|
|
368
|
+
if (!auth || typeof auth !== "object")
|
|
369
|
+
return undefined;
|
|
370
|
+
const token = auth.accessToken ??
|
|
371
|
+
auth.access_token ??
|
|
372
|
+
auth.token;
|
|
373
|
+
return typeof token === "string" && token.length > 0 ? token : undefined;
|
|
374
|
+
}
|
|
375
|
+
/** True if string looks like a JWT (three base64 parts) or a bearer token, not JSON. */
|
|
376
|
+
function looksLikeToken(s) {
|
|
377
|
+
return s.length > 0 && !s.trimStart().startsWith("{") && !s.trimStart().startsWith("[");
|
|
378
|
+
}
|
|
379
|
+
/** Recursively find first string value at keys accessToken, access_token, or token. */
|
|
380
|
+
function findTokenInObject(obj) {
|
|
381
|
+
if (obj === null || obj === undefined)
|
|
382
|
+
return undefined;
|
|
383
|
+
if (typeof obj === "string")
|
|
384
|
+
return looksLikeToken(obj) ? obj : undefined;
|
|
385
|
+
if (typeof obj !== "object")
|
|
386
|
+
return undefined;
|
|
387
|
+
const rec = obj;
|
|
388
|
+
const direct = rec.accessToken ?? rec.access_token ?? rec.token;
|
|
389
|
+
if (typeof direct === "string" && looksLikeToken(direct))
|
|
390
|
+
return direct;
|
|
391
|
+
for (const key of Object.keys(rec)) {
|
|
392
|
+
const found = findTokenInObject(rec[key]);
|
|
393
|
+
if (found)
|
|
394
|
+
return found;
|
|
395
|
+
}
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
/** Parse redux-persist style value (may be double stringified). */
|
|
399
|
+
function parsePersistValue(raw) {
|
|
400
|
+
if (!raw)
|
|
401
|
+
return null;
|
|
402
|
+
try {
|
|
403
|
+
const first = JSON.parse(raw);
|
|
404
|
+
if (typeof first === "string") {
|
|
405
|
+
try {
|
|
406
|
+
return JSON.parse(first);
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return first;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return first;
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/** Get a nested value that may be a string (need parse) or already an object. */
|
|
419
|
+
function parseNestedValue(val) {
|
|
420
|
+
if (val == null)
|
|
421
|
+
return null;
|
|
422
|
+
if (typeof val === "string")
|
|
423
|
+
return parsePersistValue(val);
|
|
424
|
+
return val;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Extract access token from persist:userdb shape: authDetails (string) → parse → auth.accessToken.
|
|
428
|
+
* Structure: { authDetails: "{\"auth\":{\"accessToken\":\"...\"}}", profileInformation: "...", _persist: "..." }
|
|
429
|
+
*/
|
|
430
|
+
function getTokenFromUserDbPersist(parsed) {
|
|
431
|
+
const authDetails = parsed.authDetails;
|
|
432
|
+
const inner = parseNestedValue(authDetails);
|
|
433
|
+
const obj = typeof inner === "object" && inner !== null ? inner : null;
|
|
434
|
+
if (!obj)
|
|
435
|
+
return undefined;
|
|
436
|
+
const auth = obj.auth;
|
|
437
|
+
const authObj = typeof auth === "object" && auth !== null ? auth : null;
|
|
438
|
+
if (!authObj)
|
|
439
|
+
return undefined;
|
|
440
|
+
const token = authObj.accessToken ??
|
|
441
|
+
authObj.access_token ??
|
|
442
|
+
authObj.token;
|
|
443
|
+
return typeof token === "string" && looksLikeToken(token) ? token : undefined;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get access token from all known storage keys (header, IAM, userdb, and plain keys).
|
|
447
|
+
* persist:userdb shape: authDetails (stringified) contains auth.accessToken.
|
|
448
|
+
*/
|
|
449
|
+
export function getAccessTokenForRequest() {
|
|
450
|
+
const keysToTry = [
|
|
451
|
+
PERSIST_HEADER_KEY,
|
|
452
|
+
"persist:linn-i-am",
|
|
453
|
+
"persist:userdb",
|
|
454
|
+
"token",
|
|
455
|
+
"accessToken",
|
|
456
|
+
"auth",
|
|
457
|
+
];
|
|
458
|
+
for (const key of keysToTry) {
|
|
459
|
+
const raw = localStorage.getItem(key);
|
|
460
|
+
if (!raw)
|
|
461
|
+
continue;
|
|
462
|
+
const parsed = key.startsWith("persist:") ? parsePersistValue(raw) : (() => { try {
|
|
463
|
+
return JSON.parse(raw);
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return raw;
|
|
467
|
+
} })();
|
|
468
|
+
const token = findTokenInObject(parsed);
|
|
469
|
+
if (token)
|
|
470
|
+
return token;
|
|
471
|
+
if (key.startsWith("persist:")) {
|
|
472
|
+
const outer = typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
473
|
+
if (outer) {
|
|
474
|
+
if (key === "persist:userdb") {
|
|
475
|
+
const fromUserDb = getTokenFromUserDbPersist(outer);
|
|
476
|
+
if (fromUserDb)
|
|
477
|
+
return fromUserDb;
|
|
478
|
+
}
|
|
479
|
+
for (const k of Object.keys(outer)) {
|
|
480
|
+
const inner = parseNestedValue(outer[k]);
|
|
481
|
+
const t = findTokenInObject(inner);
|
|
482
|
+
if (t)
|
|
483
|
+
return t;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return getAccessTokenFromAuth(getAllDataFromStorage().auth);
|
|
489
|
+
}
|
|
490
|
+
export const fetchProfilePictureAsBlobUrl = async (baseUrl, accessTokenOverride) => {
|
|
491
|
+
try {
|
|
492
|
+
const profilePicturePathUrl = getProfilePictureUrl(baseUrl);
|
|
493
|
+
if (!profilePicturePathUrl)
|
|
494
|
+
return null;
|
|
495
|
+
const cleanBase = typeof baseUrl === "string" && baseUrl.trim() ? baseUrl.trim().replace(/\/$/, "") : "";
|
|
496
|
+
if (!cleanBase)
|
|
497
|
+
return null;
|
|
498
|
+
const accessToken = (typeof accessTokenOverride === "string" && accessTokenOverride.length > 0
|
|
499
|
+
? accessTokenOverride
|
|
500
|
+
: null) ?? getAccessTokenForRequest();
|
|
501
|
+
const headers = {
|
|
502
|
+
"X-Message-Id": `msg-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
503
|
+
"X-Correlation-Id": `corr-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
504
|
+
};
|
|
505
|
+
if (accessToken) {
|
|
506
|
+
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
507
|
+
}
|
|
508
|
+
// Step 1: GET path API → JSON with filePath, s3Uri
|
|
509
|
+
const pathResponse = await fetch(profilePicturePathUrl, { method: "GET", headers });
|
|
510
|
+
if (!pathResponse.ok) {
|
|
511
|
+
console.warn(`Failed to fetch profile picture path: ${pathResponse.status} ${pathResponse.statusText}`);
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
const pathData = (await pathResponse.json());
|
|
515
|
+
const status = pathData?.statusResponse?.status;
|
|
516
|
+
if (status !== "SUCCESS") {
|
|
517
|
+
console.warn("Profile picture path API did not return SUCCESS:", status, pathData?.errors);
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
// Key for download API: from s3Uri (s3://bucket/key) or response.key
|
|
521
|
+
let downloadKey = pathData.key ??
|
|
522
|
+
(pathData.s3Uri ? getKeyFromS3Uri(pathData.s3Uri) : null);
|
|
523
|
+
if (!downloadKey && pathData.filePath) {
|
|
524
|
+
try {
|
|
525
|
+
const u = new URL(pathData.filePath);
|
|
526
|
+
downloadKey = u.pathname.replace(/^\//, ""); // path without leading slash as key
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
// ignore
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (!downloadKey) {
|
|
533
|
+
console.warn("Profile picture response has no key/s3Uri/filePath for download");
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
// Step 2: GET download API → JSON with fileContent (base64) and contentType
|
|
537
|
+
const downloadUrl = `${cleanBase}/v1/objectStore/download?key=${encodeURIComponent(downloadKey)}`;
|
|
538
|
+
const imageResponse = await fetch(downloadUrl, { method: "GET", headers });
|
|
539
|
+
if (!imageResponse.ok) {
|
|
540
|
+
console.warn(`Failed to fetch profile picture image: ${imageResponse.status} ${imageResponse.statusText}`);
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
const downloadData = (await imageResponse.json());
|
|
544
|
+
const downloadStatus = downloadData?.statusResponse?.status;
|
|
545
|
+
if (downloadStatus !== "SUCCESS") {
|
|
546
|
+
console.warn("Profile picture download API did not return SUCCESS:", downloadStatus, downloadData?.errors);
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
const fileContent = downloadData?.fileContent;
|
|
550
|
+
const contentType = (downloadData?.contentType || "").trim() || "image/png";
|
|
551
|
+
if (!fileContent || typeof fileContent !== "string") {
|
|
552
|
+
console.warn("Profile picture download response has no fileContent");
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
if (!contentType.startsWith("image/")) {
|
|
556
|
+
console.warn(`Profile picture download contentType is not an image: ${contentType}`);
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
// Decode base64 fileContent → Blob → object URL
|
|
560
|
+
const binary = atob(fileContent);
|
|
561
|
+
const bytes = new Uint8Array(binary.length);
|
|
562
|
+
for (let i = 0; i < binary.length; i++)
|
|
563
|
+
bytes[i] = binary.charCodeAt(i);
|
|
564
|
+
const blob = new Blob([bytes], { type: contentType });
|
|
565
|
+
return URL.createObjectURL(blob);
|
|
566
|
+
}
|
|
567
|
+
catch (err) {
|
|
568
|
+
console.error("Error fetching profile picture:", err);
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const translations = {
|
|
2
|
+
en: {
|
|
3
|
+
accountSettings: 'Account Settings',
|
|
4
|
+
profile: 'Profile',
|
|
5
|
+
signOut: 'Sign out',
|
|
6
|
+
messages: 'Messages',
|
|
7
|
+
notifications: 'Notifications',
|
|
8
|
+
online: 'Online',
|
|
9
|
+
offline: 'Offline',
|
|
10
|
+
role: 'Role',
|
|
11
|
+
},
|
|
12
|
+
es: {
|
|
13
|
+
accountSettings: 'Configuración de cuenta',
|
|
14
|
+
profile: 'Perfil',
|
|
15
|
+
signOut: 'Cerrar sesión',
|
|
16
|
+
messages: 'Mensajes',
|
|
17
|
+
notifications: 'Notificaciones',
|
|
18
|
+
online: 'En línea',
|
|
19
|
+
offline: 'Desconectado',
|
|
20
|
+
role: 'Rol',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
export const getTranslations = (language = 'en') => {
|
|
24
|
+
return translations[language] || translations.en;
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "varminer-app-header",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A plug-and-play React header component with Material-UI, drawer toggle, notifications, and user profile menu",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"README.md"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"build": "rollup -c",
|
|
23
|
+
"build": "npm run generate:tenant && npm run sync:tenant-fallback && rollup -c",
|
|
24
24
|
"dev": "rollup -c -w",
|
|
25
25
|
"prepublishOnly": "npm run build",
|
|
26
26
|
"prepack": "npm run build",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"pack": "npm pack",
|
|
31
31
|
"test:link": "npm run build && npm link",
|
|
32
32
|
"test:pack": "npm run build && npm pack",
|
|
33
|
-
"generate:tenant": "node generate-tenant-css.js"
|
|
33
|
+
"generate:tenant": "node generate-tenant-css.js",
|
|
34
|
+
"sync:tenant-fallback": "node scripts/sync-tenant-fallback.mjs"
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
37
|
"react",
|