@canmingir/link 1.2.49 → 1.2.51
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/package.json +1 -1
- package/src/http/index.js +39 -10
- package/src/http/user.js +89 -19
- package/src/widgets/SettingsDialog.jsx +75 -40
package/package.json
CHANGED
package/src/http/index.js
CHANGED
|
@@ -40,6 +40,20 @@ instance.interceptors.response.use(
|
|
|
40
40
|
async (error) => {
|
|
41
41
|
publish("LOADED", { loading: false });
|
|
42
42
|
const statusCode = error.response?.status;
|
|
43
|
+
const identityProvider = storage.get("link", "identityProvider");
|
|
44
|
+
|
|
45
|
+
if (statusCode === 500 && identityProvider === "DEMO") {
|
|
46
|
+
const token = storage.get("link", "accessToken");
|
|
47
|
+
try {
|
|
48
|
+
const decodedToken = jwtDecode(token);
|
|
49
|
+
if (decodedToken.exp * 1000 < Date.now()) {
|
|
50
|
+
return Promise.reject(error);
|
|
51
|
+
}
|
|
52
|
+
} catch (_) {
|
|
53
|
+
return Promise.reject(error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
switch (statusCode) {
|
|
44
58
|
case 400:
|
|
45
59
|
publish("GLOBAL_MESSAGE_POSTED", {
|
|
@@ -78,7 +92,7 @@ instance.interceptors.response.use(
|
|
|
78
92
|
break;
|
|
79
93
|
}
|
|
80
94
|
return Promise.reject(error);
|
|
81
|
-
}
|
|
95
|
+
},
|
|
82
96
|
);
|
|
83
97
|
|
|
84
98
|
export const fetcher = (url) => instance.get(url).then((res) => res.data);
|
|
@@ -128,26 +142,41 @@ const refreshInterceptor =
|
|
|
128
142
|
: createAuthRefreshInterceptor.default;
|
|
129
143
|
|
|
130
144
|
refreshInterceptor(instance, refreshAuthLogic, {
|
|
131
|
-
statusCodes: [401, 403],
|
|
132
|
-
|
|
133
|
-
|
|
145
|
+
statusCodes: [401, 403, 500],
|
|
146
|
+
pauseInstanceWhileRefreshing: true,
|
|
147
|
+
shouldRefresh: (error) => {
|
|
148
|
+
const { base } = config();
|
|
134
149
|
const token = storage.get("link", "accessToken");
|
|
150
|
+
const identityProvider = storage.get("link", "identityProvider");
|
|
151
|
+
const statusCode = error.response?.status;
|
|
135
152
|
|
|
136
153
|
if (!token) {
|
|
137
154
|
window.location.href = `${window.location.origin}${base}/login`;
|
|
138
155
|
return false;
|
|
139
156
|
}
|
|
140
157
|
|
|
158
|
+
if (identityProvider === "DEMO") {
|
|
159
|
+
if (statusCode !== 500) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const decodedToken = jwtDecode(token);
|
|
165
|
+
return decodedToken.exp * 1000 < Date.now();
|
|
166
|
+
} catch (_) {
|
|
167
|
+
// If we can't decode, attempt refresh once.
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (statusCode === 500) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
141
176
|
try {
|
|
142
177
|
const decodedToken = jwtDecode(token);
|
|
143
178
|
if (decodedToken.exp * 1000 < Date.now()) {
|
|
144
179
|
return true;
|
|
145
|
-
} else {
|
|
146
|
-
publish("GLOBAL_MESSAGE_POSTED", {
|
|
147
|
-
status: true,
|
|
148
|
-
message: "UNAUTHORIZED",
|
|
149
|
-
severity: "warning",
|
|
150
|
-
});
|
|
151
180
|
}
|
|
152
181
|
} catch (err) {
|
|
153
182
|
window.location.href = `${window.location.origin}${base}/login`;
|
package/src/http/user.js
CHANGED
|
@@ -43,7 +43,7 @@ instance.getUserDetails = async () => {
|
|
|
43
43
|
|
|
44
44
|
if (isExpired) {
|
|
45
45
|
console.log(
|
|
46
|
-
"Access token expired, refreshing before fetching user details..."
|
|
46
|
+
"Access token expired, refreshing before fetching user details...",
|
|
47
47
|
);
|
|
48
48
|
const { appId } = config();
|
|
49
49
|
const projectId = storage.get("projectId");
|
|
@@ -54,12 +54,16 @@ instance.getUserDetails = async () => {
|
|
|
54
54
|
appId,
|
|
55
55
|
projectId,
|
|
56
56
|
identityProvider,
|
|
57
|
+
...(identityProvider === "DEMO" && {
|
|
58
|
+
username: "admin",
|
|
59
|
+
password: "admin",
|
|
60
|
+
}),
|
|
57
61
|
});
|
|
58
62
|
|
|
59
63
|
accessToken = data.accessToken;
|
|
60
64
|
storage.set("link", "accessToken", accessToken);
|
|
61
65
|
console.log(
|
|
62
|
-
"Access token refreshed successfully, now fetching user details"
|
|
66
|
+
"Access token refreshed successfully, now fetching user details",
|
|
63
67
|
);
|
|
64
68
|
}
|
|
65
69
|
} catch (error) {
|
|
@@ -86,25 +90,91 @@ instance.getUserDetails = async () => {
|
|
|
86
90
|
};
|
|
87
91
|
|
|
88
92
|
instance.getPermittedUsers = async () => {
|
|
89
|
-
const
|
|
90
|
-
const
|
|
93
|
+
const { appId } = config();
|
|
94
|
+
const projectId = storage.get("projectId");
|
|
95
|
+
const identityProvider = storage.get("link", "identityProvider");
|
|
96
|
+
|
|
91
97
|
const response = await http.get("/permissions");
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
const uniqueUserIds = [
|
|
100
|
+
...new Set(
|
|
101
|
+
response.data
|
|
102
|
+
.filter((p) => p.appId === appId && p.projectId === projectId)
|
|
103
|
+
.map((p) => p.userId),
|
|
104
|
+
),
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const UUID_REGEX =
|
|
108
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
109
|
+
|
|
110
|
+
if (identityProvider?.toUpperCase() === "GITHUB") {
|
|
111
|
+
const results = await Promise.allSettled(
|
|
112
|
+
uniqueUserIds.map(async (userId) => {
|
|
113
|
+
if (UUID_REGEX.test(userId)) {
|
|
114
|
+
return {
|
|
115
|
+
id: String(userId),
|
|
116
|
+
identityProvider,
|
|
117
|
+
name: String(userId),
|
|
118
|
+
displayName: null,
|
|
119
|
+
avatarUrl: `https://api.dicebear.com/7.x/bottts/svg?seed=${userId}`,
|
|
120
|
+
email: null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const { data } = await axios.get(
|
|
124
|
+
`https://api.github.com/user/${userId}`,
|
|
125
|
+
{ headers: { Accept: "application/vnd.github+json" } },
|
|
126
|
+
);
|
|
127
|
+
return {
|
|
128
|
+
id: String(data.id),
|
|
129
|
+
identityProvider: "GITHUB",
|
|
130
|
+
name: data.login,
|
|
131
|
+
displayName: data.name || null,
|
|
132
|
+
avatarUrl: data.avatar_url || null,
|
|
133
|
+
email: data.email || null,
|
|
134
|
+
};
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return results
|
|
139
|
+
.filter((r) => r.status === "fulfilled" && r.value !== null)
|
|
140
|
+
.map((r) => r.value);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (identityProvider?.toUpperCase() === "DEMO") {
|
|
144
|
+
return [
|
|
145
|
+
{
|
|
146
|
+
id: "1001",
|
|
147
|
+
identityProvider: "DEMO",
|
|
148
|
+
name: "admin",
|
|
149
|
+
displayName: "Demo Admin",
|
|
150
|
+
avatarUrl: `https://api.dicebear.com/7.x/bottts/svg?seed=1001`,
|
|
151
|
+
email: "admin@demo.local",
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (identityProvider?.toUpperCase() === "COGNITO") {
|
|
157
|
+
const accessToken = await storage.get("link", "accessToken");
|
|
158
|
+
let currentUserId = null;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const decoded = jwtDecode(accessToken);
|
|
162
|
+
currentUserId = decoded.sub;
|
|
163
|
+
} catch (_) {}
|
|
164
|
+
|
|
165
|
+
return uniqueUserIds
|
|
166
|
+
.filter((id) => !UUID_REGEX.test(id))
|
|
167
|
+
.map((id) => ({
|
|
168
|
+
id: String(id),
|
|
169
|
+
identityProvider: "COGNITO",
|
|
170
|
+
name: id === currentUserId ? "You" : "Cognito User",
|
|
171
|
+
displayName: id === currentUserId ? "Cognito Admin" : null,
|
|
172
|
+
avatarUrl: `https://api.dicebear.com/7.x/bottts/svg?seed=${id}`,
|
|
173
|
+
email: null,
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return [];
|
|
108
178
|
};
|
|
109
179
|
|
|
110
180
|
export default instance;
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import Iconify from "../components/Iconify";
|
|
2
|
-
import config from "../config/config";
|
|
3
|
-
import { useEvent } from "@nucleoidai/react-event";
|
|
4
|
-
import useSettings from "../hooks/useSettings";
|
|
5
|
-
import { useSettingsContext } from "../components/settings/context";
|
|
6
|
-
import { useUser } from "../hooks/use-user";
|
|
7
|
-
|
|
8
1
|
import {
|
|
9
2
|
Avatar,
|
|
10
3
|
Box,
|
|
@@ -30,6 +23,13 @@ import {
|
|
|
30
23
|
import { Button, Dialog, DialogActions, DialogContent } from "@mui/material";
|
|
31
24
|
import React, { useEffect, useState } from "react";
|
|
32
25
|
|
|
26
|
+
import Iconify from "../components/Iconify";
|
|
27
|
+
import config from "../config/config";
|
|
28
|
+
import { useEvent } from "@nucleoidai/react-event";
|
|
29
|
+
import useSettings from "../hooks/useSettings";
|
|
30
|
+
import { useSettingsContext } from "../components/settings/context";
|
|
31
|
+
import { useUser } from "../hooks/use-user";
|
|
32
|
+
|
|
33
33
|
let pkg = {
|
|
34
34
|
name: "",
|
|
35
35
|
version: "",
|
|
@@ -173,7 +173,7 @@ function SettingsDialog({ handleClose, open }) {
|
|
|
173
173
|
color: "white",
|
|
174
174
|
minHeight: 600,
|
|
175
175
|
},
|
|
176
|
-
}
|
|
176
|
+
},
|
|
177
177
|
}}
|
|
178
178
|
>
|
|
179
179
|
<DialogContent>
|
|
@@ -211,16 +211,24 @@ const Permission = () => {
|
|
|
211
211
|
|
|
212
212
|
useEffect(() => {
|
|
213
213
|
getPermittedUsers();
|
|
214
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
214
215
|
}, [event, event2]);
|
|
215
216
|
|
|
216
217
|
return (
|
|
217
|
-
<Stack
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
<Stack
|
|
219
|
+
direction="column"
|
|
220
|
+
spacing={2}
|
|
221
|
+
sx={{
|
|
222
|
+
p: 2,
|
|
223
|
+
}}
|
|
224
|
+
>
|
|
220
225
|
<Typography variant="h6">Users</Typography>
|
|
221
|
-
<Typography
|
|
222
|
-
|
|
223
|
-
|
|
226
|
+
<Typography
|
|
227
|
+
variant="subtitle2"
|
|
228
|
+
sx={{
|
|
229
|
+
color: "text.secondary",
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
224
232
|
Users with access to the project.
|
|
225
233
|
</Typography>
|
|
226
234
|
<List>
|
|
@@ -238,7 +246,7 @@ const Permission = () => {
|
|
|
238
246
|
}}
|
|
239
247
|
>
|
|
240
248
|
<ListItemAvatar>
|
|
241
|
-
<Avatar src={user.
|
|
249
|
+
<Avatar src={user.avatarUrl} alt={user.name} />
|
|
242
250
|
</ListItemAvatar>
|
|
243
251
|
<ListItemText primary={user.name} />
|
|
244
252
|
<ListItemSecondaryAction>
|
|
@@ -306,13 +314,20 @@ const Settings = () => {
|
|
|
306
314
|
};
|
|
307
315
|
|
|
308
316
|
return (
|
|
309
|
-
<Stack
|
|
310
|
-
|
|
311
|
-
|
|
317
|
+
<Stack
|
|
318
|
+
direction="column"
|
|
319
|
+
spacing={2}
|
|
320
|
+
sx={{
|
|
321
|
+
p: 2,
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
312
324
|
<Typography variant="h6">Settings</Typography>
|
|
313
|
-
<Typography
|
|
314
|
-
|
|
315
|
-
|
|
325
|
+
<Typography
|
|
326
|
+
variant="subtitle2"
|
|
327
|
+
sx={{
|
|
328
|
+
color: "text.secondary",
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
316
331
|
Configure your application settings.
|
|
317
332
|
</Typography>
|
|
318
333
|
<List>
|
|
@@ -373,9 +388,13 @@ const About = () => {
|
|
|
373
388
|
const description = pkg.description;
|
|
374
389
|
|
|
375
390
|
return (
|
|
376
|
-
<Stack
|
|
377
|
-
|
|
378
|
-
|
|
391
|
+
<Stack
|
|
392
|
+
direction="column"
|
|
393
|
+
spacing={2}
|
|
394
|
+
sx={{
|
|
395
|
+
p: 2,
|
|
396
|
+
}}
|
|
397
|
+
>
|
|
379
398
|
<Box
|
|
380
399
|
sx={{
|
|
381
400
|
p: 3,
|
|
@@ -386,9 +405,13 @@ const About = () => {
|
|
|
386
405
|
"linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.01))",
|
|
387
406
|
}}
|
|
388
407
|
>
|
|
389
|
-
<Stack
|
|
390
|
-
|
|
391
|
-
|
|
408
|
+
<Stack
|
|
409
|
+
direction="row"
|
|
410
|
+
spacing={2.5}
|
|
411
|
+
sx={{
|
|
412
|
+
alignItems: "center",
|
|
413
|
+
}}
|
|
414
|
+
>
|
|
392
415
|
<Avatar
|
|
393
416
|
src={iconSrc}
|
|
394
417
|
variant="rounded"
|
|
@@ -404,9 +427,12 @@ const About = () => {
|
|
|
404
427
|
</Avatar>
|
|
405
428
|
|
|
406
429
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
407
|
-
<Typography
|
|
408
|
-
|
|
409
|
-
|
|
430
|
+
<Typography
|
|
431
|
+
variant="h5"
|
|
432
|
+
sx={{
|
|
433
|
+
fontWeight: 600,
|
|
434
|
+
}}
|
|
435
|
+
>
|
|
410
436
|
{appName.toUpperCase()}
|
|
411
437
|
</Typography>
|
|
412
438
|
|
|
@@ -416,8 +442,9 @@ const About = () => {
|
|
|
416
442
|
sx={{
|
|
417
443
|
color: "text.secondary",
|
|
418
444
|
mt: 0.5,
|
|
419
|
-
maxWidth: 520
|
|
420
|
-
}}
|
|
445
|
+
maxWidth: 520,
|
|
446
|
+
}}
|
|
447
|
+
>
|
|
421
448
|
{description}
|
|
422
449
|
</Typography>
|
|
423
450
|
)}
|
|
@@ -460,9 +487,12 @@ const About = () => {
|
|
|
460
487
|
textAlign: "center",
|
|
461
488
|
}}
|
|
462
489
|
>
|
|
463
|
-
<Typography
|
|
464
|
-
|
|
465
|
-
|
|
490
|
+
<Typography
|
|
491
|
+
variant="caption"
|
|
492
|
+
sx={{
|
|
493
|
+
color: "text.secondary",
|
|
494
|
+
}}
|
|
495
|
+
>
|
|
466
496
|
© 2026 greycollar.ai. All rights reserved.
|
|
467
497
|
</Typography>
|
|
468
498
|
</Box>
|
|
@@ -473,15 +503,20 @@ const About = () => {
|
|
|
473
503
|
|
|
474
504
|
const InfoRow = ({ label, value, link }) => {
|
|
475
505
|
return (
|
|
476
|
-
<Stack
|
|
477
|
-
|
|
478
|
-
|
|
506
|
+
<Stack
|
|
507
|
+
direction="row"
|
|
508
|
+
spacing={1.5}
|
|
509
|
+
sx={{
|
|
510
|
+
alignItems: "center",
|
|
511
|
+
}}
|
|
512
|
+
>
|
|
479
513
|
<Typography
|
|
480
514
|
variant="body2"
|
|
481
515
|
sx={{
|
|
482
516
|
color: "text.secondary",
|
|
483
|
-
minWidth: 110
|
|
484
|
-
}}
|
|
517
|
+
minWidth: 110,
|
|
518
|
+
}}
|
|
519
|
+
>
|
|
485
520
|
{label}
|
|
486
521
|
</Typography>
|
|
487
522
|
{link ? (
|