Skip to content

Commit b5d9947

Browse files
committed
Release DL Studio 1.1.0
- Check Research access through GET /users/me before loading workspace data. - Keep unauthorized users on the Research page with clear permission and retry states. - Guard permission checks against stale account requests and refresh-token changes. - Bump Windows and macOS packages to 1.1.0 and update the release date.
1 parent b1f4f01 commit b5d9947

8 files changed

Lines changed: 382 additions & 20 deletions

File tree

Mac/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Mac/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dl-studio-mac",
3-
"version": "1.0.9",
3+
"version": "1.1.0",
44
"private": true,
55
"description": "Desktop batch video downsampler for macOS.",
66
"main": "src/main/main.js",

Mac/scripts/app-shell.test.cjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,13 @@ assert.match(app, /CloudRepository/);
269269
assert.match(app, /loadCloudRepository/);
270270
assert.match(app, /activeNav === "Research"/);
271271
assert.match(app, /ResearchPage/);
272+
assert.match(app, /isResearchPermissionDenied/);
273+
assert.match(app, /fetchCurrentUser/);
274+
assert.match(app, /requestInfera\("\/users\/me"/);
275+
assert.match(app, /permissionKey \|\| permission\?\.permission_key/);
276+
assert.match(app, /researchAccessState\.status !== "allowed"/);
277+
assert.match(app, /status: permissionDenied \? "forbidden" : "error"/);
278+
assert.match(app, /state\.status === "forbidden"/);
272279
assert.match(app, /fetchResearchResources/);
273280
assert.match(app, /fetchResearchChats/);
274281
assert.match(app, /ResearchResourcesView/);

Mac/src/renderer/App.jsx

Lines changed: 181 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ const UPLOAD_STATUS_LABELS = {
143143
const APP_INFO = {
144144
name: "DL Studio",
145145
version: packageJson.version,
146-
updatedAt: "2026-07-07",
146+
updatedAt: "2026-07-14",
147147
engine: "FFmpeg / FFprobe",
148148
stack: "Electron + React"
149149
};
@@ -681,6 +681,17 @@ async function loginToInfera({ identifier, password }) {
681681
});
682682
}
683683

684+
async function fetchCurrentUser(token) {
685+
return requestInfera("/users/me", { token });
686+
}
687+
688+
function hasResearchAccess(user) {
689+
return (Array.isArray(user?.permissions) ? user.permissions : []).some((permission) => {
690+
const key = String(permission?.permissionKey || permission?.permission_key || "").trim().toLowerCase();
691+
return key === "research.access" && permission?.enabled === true;
692+
});
693+
}
694+
684695
async function fetchCloudRepository(token, spaceId = CLOUD_SPACES[0].id, options = {}) {
685696
const items = [];
686697
let total = null;
@@ -1180,6 +1191,7 @@ function App() {
11801191
const automationOptionsRef = useRef(automationOptions);
11811192
const automationEnqueueRunningRef = useRef(false);
11821193
const autoLoginPromptedRef = useRef(false);
1194+
const researchAccessRequestRef = useRef(0);
11831195
const [repositoryState, setRepositoryState] = useState({
11841196
status: "idle",
11851197
spaceId: cloudSpaceId,
@@ -1200,6 +1212,7 @@ function App() {
12001212
to: "",
12011213
userId: ""
12021214
});
1215+
const [researchAccessState, setResearchAccessState] = useState({ status: "idle", token: "", message: "" });
12031216
const [researchState, setResearchState] = useState({
12041217
status: "idle",
12051218
users: [],
@@ -1344,6 +1357,7 @@ function App() {
13441357
},
13451358
onExpired: () => {
13461359
const previousAuth = authStateRef.current;
1360+
researchAccessRequestRef.current += 1;
13471361
window.localStorage?.removeItem(AUTH_STORAGE_KEY);
13481362
authStateRef.current = null;
13491363
setAuthState(null);
@@ -1427,6 +1441,8 @@ function App() {
14271441
}
14281442

14291443
if (!authState?.token) {
1444+
researchAccessRequestRef.current += 1;
1445+
setResearchAccessState({ status: "auth", token: "", message: "" });
14301446
setResearchState((current) => ({
14311447
...current,
14321448
status: "auth",
@@ -1439,8 +1455,38 @@ function App() {
14391455
return;
14401456
}
14411457

1458+
if (
1459+
researchAccessState.token === authState.token &&
1460+
["checking", "allowed", "forbidden", "error"].includes(researchAccessState.status)
1461+
) {
1462+
return;
1463+
}
1464+
1465+
loadResearchAccess(authState);
1466+
}, [activeNav, authState?.token, researchAccessState.status, researchAccessState.token]);
1467+
1468+
useEffect(() => {
1469+
if (
1470+
activeNav !== "Research" ||
1471+
!authState?.token ||
1472+
researchAccessState.status !== "allowed" ||
1473+
researchAccessState.token !== authState.token
1474+
) {
1475+
return;
1476+
}
1477+
14421478
loadResearchData(researchTab, authState, researchFilters);
1443-
}, [activeNav, authState?.token, researchFilters.from, researchFilters.status, researchFilters.to, researchFilters.userId, researchTab]);
1479+
}, [
1480+
activeNav,
1481+
authState?.token,
1482+
researchAccessState.status,
1483+
researchAccessState.token,
1484+
researchFilters.from,
1485+
researchFilters.status,
1486+
researchFilters.to,
1487+
researchFilters.userId,
1488+
researchTab
1489+
]);
14441490

14451491
useEffect(() => {
14461492
if (!hasProcessingJobs && !isUploadActive(uploadState) && !transferQueue.some((item) => item.status === "uploading")) {
@@ -2493,7 +2539,7 @@ function App() {
24932539
if (activeNav === "Cloud") {
24942540
loadCloudRepository(nextAuth, cloudSpaceId, cloudRepositoryDateKey, cloudRepositoryStatusFilterId);
24952541
} else if (activeNav === "Research") {
2496-
loadResearchData(researchTab, nextAuth, researchFilters);
2542+
loadResearchAccess(nextAuth);
24972543
}
24982544
} catch (error) {
24992545
setLoginStatus({
@@ -2504,10 +2550,12 @@ function App() {
25042550
}
25052551

25062552
function logout() {
2553+
researchAccessRequestRef.current += 1;
25072554
window.localStorage?.removeItem(AUTH_STORAGE_KEY);
25082555
authStateRef.current = null;
25092556
setAuthState(null);
25102557
setLoginStatus({ status: "idle", message: "" });
2558+
setResearchAccessState({ status: "auth", token: "", message: "" });
25112559
setRepositoryState({
25122560
status: "auth",
25132561
spaceId: cloudSpaceId,
@@ -2522,6 +2570,57 @@ function App() {
25222570
});
25232571
}
25242572

2573+
async function loadResearchAccess(authOverride = authState) {
2574+
const token = authOverride?.token;
2575+
if (!token) {
2576+
researchAccessRequestRef.current += 1;
2577+
setResearchAccessState({ status: "auth", token: "", message: "" });
2578+
return;
2579+
}
2580+
2581+
const requestId = researchAccessRequestRef.current + 1;
2582+
researchAccessRequestRef.current = requestId;
2583+
setResearchAccessState({ status: "checking", token, message: "" });
2584+
try {
2585+
const currentUser = await fetchCurrentUser(token);
2586+
if (researchAccessRequestRef.current !== requestId) {
2587+
return;
2588+
}
2589+
2590+
const resolvedToken = authStateRef.current?.token || token;
2591+
if (!hasResearchAccess(currentUser)) {
2592+
setResearchAccessState({
2593+
status: "forbidden",
2594+
token: resolvedToken,
2595+
message: "当前账号未被授予 Research 访问权限,请联系管理员。"
2596+
});
2597+
setResearchState((current) => ({
2598+
...current,
2599+
status: "idle",
2600+
users: [],
2601+
resources: [],
2602+
resourcesTotal: 0,
2603+
chats: [],
2604+
chatsTotal: 0,
2605+
statistics: null,
2606+
message: ""
2607+
}));
2608+
return;
2609+
}
2610+
2611+
setResearchAccessState({ status: "allowed", token: resolvedToken, message: "" });
2612+
} catch (error) {
2613+
if (researchAccessRequestRef.current !== requestId || !authStateRef.current?.token) {
2614+
return;
2615+
}
2616+
setResearchAccessState({
2617+
status: "error",
2618+
token: authStateRef.current.token,
2619+
message: error.message || "无法验证 Research 访问权限"
2620+
});
2621+
}
2622+
}
2623+
25252624
async function loadResearchData(tabOverride = researchTab, authOverride = authState, filtersOverride = researchFilters, options = {}) {
25262625
const token = authOverride?.token;
25272626
const append = Boolean(options.append);
@@ -2579,9 +2678,17 @@ function App() {
25792678
}));
25802679
}
25812680
} catch (error) {
2681+
const permissionDenied = isResearchPermissionDenied(error);
2682+
if (permissionDenied) {
2683+
setResearchAccessState({
2684+
status: "forbidden",
2685+
token: authStateRef.current?.token || token,
2686+
message: "当前账号未被授予 Research 访问权限,请联系管理员。"
2687+
});
2688+
}
25822689
setResearchState((current) => ({
25832690
...current,
2584-
status: "error",
2691+
status: permissionDenied ? "forbidden" : "error",
25852692
message: getResearchErrorMessage(error)
25862693
}));
25872694
}
@@ -3106,6 +3213,7 @@ function App() {
31063213
)
31073214
) : activeNav === "Research" ? (
31083215
<ResearchPage
3216+
accessState={researchAccessState}
31093217
authState={authState}
31103218
filters={researchFilters}
31113219
onExportFilteredChats={exportFilteredResearchChats}
@@ -3117,6 +3225,7 @@ function App() {
31173225
onLoadMoreChats={loadMoreResearchChats}
31183226
onLoadMoreResources={loadMoreResearchResources}
31193227
onRefresh={() => loadResearchData(researchTab, authState, researchFilters)}
3228+
onRetryAccess={() => loadResearchAccess(authState)}
31203229
onSelectLoadedChats={selectLoadedResearchChats}
31213230
onSelectLoadedResources={selectLoadedResearchResources}
31223231
onTabChange={setResearchTab}
@@ -4456,6 +4565,7 @@ function EngineQueryEntryBody({ entry, mediaProxyUrl }) {
44564565
}
44574566

44584567
function ResearchPage({
4568+
accessState,
44594569
authState,
44604570
filters,
44614571
onExportFilteredChats,
@@ -4467,6 +4577,7 @@ function ResearchPage({
44674577
onLoadMoreChats,
44684578
onLoadMoreResources,
44694579
onRefresh,
4580+
onRetryAccess,
44704581
onSelectLoadedChats,
44714582
onSelectLoadedResources,
44724583
onTabChange,
@@ -4539,6 +4650,64 @@ function ResearchPage({
45394650
);
45404651
}
45414652

4653+
const accessStatus = state.status === "forbidden" ? "forbidden" : accessState?.status;
4654+
4655+
if (accessStatus === "idle" || accessStatus === "checking") {
4656+
return (
4657+
<section className="research-login-page">
4658+
<div className="research-login-card">
4659+
<div className="research-login-icon">
4660+
<LockKeyhole size={24} />
4661+
</div>
4662+
<div className="research-login-copy">
4663+
<h1>正在检查 Research 权限</h1>
4664+
<p>正在读取当前账号的访问权限。</p>
4665+
</div>
4666+
</div>
4667+
</section>
4668+
);
4669+
}
4670+
4671+
if (accessStatus === "forbidden") {
4672+
return (
4673+
<section className="research-login-page">
4674+
<div className="research-login-card">
4675+
<div className="research-login-icon">
4676+
<LockKeyhole size={24} />
4677+
</div>
4678+
<div className="research-login-copy">
4679+
<h1>无 Research 访问权限</h1>
4680+
<p>{accessState?.message || "当前账号未被授予 Research 访问权限,请联系管理员。"}</p>
4681+
</div>
4682+
<button className="primary-button" onClick={onRetryAccess} type="button">
4683+
<RotateCcw size={16} />
4684+
<span>重新检查</span>
4685+
</button>
4686+
</div>
4687+
</section>
4688+
);
4689+
}
4690+
4691+
if (accessStatus === "error") {
4692+
return (
4693+
<section className="research-login-page">
4694+
<div className="research-login-card">
4695+
<div className="research-login-icon">
4696+
<TriangleAlert size={24} />
4697+
</div>
4698+
<div className="research-login-copy">
4699+
<h1>无法验证 Research 权限</h1>
4700+
<p>{accessState?.message || "请检查网络后重试。"}</p>
4701+
</div>
4702+
<button className="primary-button" onClick={onRetryAccess} type="button">
4703+
<RotateCcw size={16} />
4704+
<span>重试</span>
4705+
</button>
4706+
</div>
4707+
</section>
4708+
);
4709+
}
4710+
45424711
return (
45434712
<section className="research-page">
45444713
<div className="research-tabs">
@@ -7085,13 +7254,18 @@ function toggleId(ids, id) {
70857254
return current.includes(normalizedId) ? current.filter((item) => item !== normalizedId) : [...current, normalizedId];
70867255
}
70877256

7257+
function isResearchPermissionDenied(error) {
7258+
const message = String(error?.message || "");
7259+
return Number(error?.status || error?.statusCode) === 403 || message.includes("403") || /research access denied/i.test(message);
7260+
}
7261+
70887262
function getResearchErrorMessage(error) {
70897263
const message = String(error?.message || "");
7090-
if (message.includes("401") || message.toLowerCase().includes("research admin login required")) {
7264+
if (isResearchPermissionDenied(error)) {
70917265
return "当前账号没有 Research 访问权限,请确认已授予 research.access";
70927266
}
7093-
if (message.includes("403")) {
7094-
return "当前账号无权访问所选 Research 数据";
7267+
if (message.includes("401") || message.toLowerCase().includes("research admin login required")) {
7268+
return "当前账号没有 Research 访问权限,请确认已授予 research.access";
70957269
}
70967270
return message || "无法读取 Research 数据";
70977271
}

Windows/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Windows/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dl-studio-windows",
3-
"version": "1.0.9",
3+
"version": "1.1.0",
44
"private": true,
55
"description": "Desktop batch video downsampler for Windows.",
66
"main": "src/main/main.js",

Windows/scripts/app-shell.test.cjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,13 @@ assert.match(app, /CloudRepository/);
223223
assert.match(app, /loadCloudRepository/);
224224
assert.match(app, /activeNav === "Research"/);
225225
assert.match(app, /ResearchPage/);
226+
assert.match(app, /isResearchPermissionDenied/);
227+
assert.match(app, /fetchCurrentUser/);
228+
assert.match(app, /requestInfera\("\/users\/me"/);
229+
assert.match(app, /permissionKey \|\| permission\?\.permission_key/);
230+
assert.match(app, /researchAccessState\.status !== "allowed"/);
231+
assert.match(app, /status: permissionDenied \? "forbidden" : "error"/);
232+
assert.match(app, /state\.status === "forbidden"/);
226233
assert.match(app, /fetchResearchResources/);
227234
assert.match(app, /fetchResearchChats/);
228235
assert.match(app, /ResearchResourcesView/);

0 commit comments

Comments
 (0)