mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-07-17 17:21:48 +00:00
Handle wikitree authcode in apps.tsx
This commit is contained in:
@@ -103,7 +103,7 @@ We will gradually eliminate React state variables in `src/app.tsx` and derive th
|
|||||||
* [x] **Step 4.1:** Create `src/hooks/use_google_auth.ts` to manage Google Drive authentication token updates. Remove `hasGoogleToken` state from `src/app.tsx`.
|
* [x] **Step 4.1:** Create `src/hooks/use_google_auth.ts` to manage Google Drive authentication token updates. Remove `hasGoogleToken` state from `src/app.tsx`.
|
||||||
* [x] **Step 4.2:** Create `src/pages/view_page.tsx` containing layout rendering (`renderMainArea`), asynchronous data loaders, view state management, `revokeObjectUrls` cleanups, and `<GoogleAuthModal>` error fallback state and rendering.
|
* [x] **Step 4.2:** Create `src/pages/view_page.tsx` containing layout rendering (`renderMainArea`), asynchronous data loaders, view state management, `revokeObjectUrls` cleanups, and `<GoogleAuthModal>` error fallback state and rendering.
|
||||||
* [x] **Step 4.3:** Create `src/pages/intro_page.tsx` rendering the landing screen and its own local header.
|
* [x] **Step 4.3:** Create `src/pages/intro_page.tsx` rendering the landing screen and its own local header.
|
||||||
* [ ] **Step 4.4:** Refactor `src/app.tsx` to serve as a pure routing switch routing to `<IntroPage />` or `<ViewPage />`. In `App`, handle Google Drive redirection synchronously during the render pass by returning a `<Navigate replace />` element (preventing render flashing), and ensure the redirection parser checks both the router `location.search` and the external `window.location.search` to handle HashRouter query structure robustly. Once external parameters (like `state` or `authcode`) are detected, they must be stripped from `window.location.search` using `window.history.replaceState` to prevent redirection loops on page refresh. Remove all layout state logic from the root file, including moving the `useWebMcpBridge` hook invocation into `<ViewPage />`. Run final verification scripts (`npm run check:all`).
|
* [x] **Step 4.4:** Refactor `src/app.tsx` to serve as a pure routing switch routing to `<IntroPage />` or `<ViewPage />`. In `App`, handle Google Drive redirection synchronously during the render pass by returning a `<Navigate replace />` element (preventing render flashing), and ensure the redirection parser checks both the router `location.search` and the external `window.location.search` to handle HashRouter query structure robustly. Once external parameters (like `state` or `authcode`) are detected, they must be stripped from `window.location.search` using `window.history.replaceState` to prevent redirection loops on page refresh. Remove all layout state logic from the root file, including moving the `useWebMcpBridge` hook invocation into `<ViewPage />`. Run final verification scripts (`npm run check:all`).
|
||||||
|
|
||||||
## Future Considerations
|
## Future Considerations
|
||||||
|
|
||||||
|
|||||||
86
src/app.tsx
86
src/app.tsx
@@ -1,6 +1,6 @@
|
|||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
import {useEffect, useRef} from 'react';
|
import {useMemo} from 'react';
|
||||||
import {Navigate, Route, Routes, useLocation, useNavigate} from 'react-router';
|
import {Navigate, Route, Routes, useLocation} from 'react-router';
|
||||||
import {IntroPage} from './pages/intro_page';
|
import {IntroPage} from './pages/intro_page';
|
||||||
import {ViewPage} from './pages/view_page';
|
import {ViewPage} from './pages/view_page';
|
||||||
import {getStaticUrl} from './util/url_args';
|
import {getStaticUrl} from './util/url_args';
|
||||||
@@ -9,20 +9,27 @@ const staticUrl = getStaticUrl();
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Root App component that orchestrates top-level routing, Google Drive "Open with"
|
* Root App component that orchestrates top-level routing, Google Drive "Open with"
|
||||||
* payload interception and redirection.
|
* payload interception, and WikiTree auth code redirection.
|
||||||
*/
|
*/
|
||||||
export function App() {
|
export function App() {
|
||||||
const navigate = useNavigate();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
/** Prevents the Google Drive "Open with" state from being processed more than once. */
|
// Synchronously parse and evaluate redirect query parameters from both external window and router searches.
|
||||||
const stateProcessed = useRef(false);
|
const redirectTarget = useMemo(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Google Drive "Open with" flow state checking.
|
const windowSearch = queryString.parse(window.location.search);
|
||||||
useEffect(() => {
|
const routerSearch = queryString.parse(location.search);
|
||||||
const search = queryString.parse(location.search);
|
|
||||||
const stateParam = search.state;
|
let redirectPath: string | null = null;
|
||||||
if (typeof stateParam === 'string' && !stateProcessed.current) {
|
const mergedParams = {...routerSearch, ...windowSearch};
|
||||||
|
let paramsModified = false;
|
||||||
|
|
||||||
|
// 1. Handle Google Drive "Open with" action
|
||||||
|
const stateParam = mergedParams.state;
|
||||||
|
if (typeof stateParam === 'string') {
|
||||||
try {
|
try {
|
||||||
const parsedState = JSON.parse(stateParam);
|
const parsedState = JSON.parse(stateParam);
|
||||||
if (
|
if (
|
||||||
@@ -31,29 +38,58 @@ export function App() {
|
|||||||
Array.isArray(parsedState.ids) &&
|
Array.isArray(parsedState.ids) &&
|
||||||
parsedState.ids.length > 0
|
parsedState.ids.length > 0
|
||||||
) {
|
) {
|
||||||
stateProcessed.current = true;
|
|
||||||
const fileId = parsedState.ids[0];
|
const fileId = parsedState.ids[0];
|
||||||
// Soft redirect to view file
|
redirectPath = '/view';
|
||||||
navigate(
|
mergedParams.source = 'google-drive';
|
||||||
{
|
mergedParams.fileId = fileId;
|
||||||
pathname: '/view',
|
delete mergedParams.state;
|
||||||
search: queryString.stringify({
|
paramsModified = true;
|
||||||
source: 'google-drive',
|
|
||||||
fileId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{replace: true},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Silently catch JSON parsing errors for state parameters not meant for us (e.g. from other auth tools)
|
// Silently catch JSON parsing errors for state parameters not meant for us
|
||||||
console.warn(
|
console.warn(
|
||||||
'Google Drive state query parameter JSON parsing failed or action mismatch:',
|
'Google Drive state query parameter JSON parsing failed or action mismatch:',
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [navigate, location.search]);
|
|
||||||
|
// 2. Handle WikiTree authcode presence
|
||||||
|
if (windowSearch.authcode) {
|
||||||
|
redirectPath = redirectPath || location.pathname;
|
||||||
|
paramsModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramsModified || windowSearch.state || windowSearch.authcode) {
|
||||||
|
// Strip external state / authcode parameters from window.location.search to prevent redirect loops.
|
||||||
|
const cleanWindowSearch = {...windowSearch};
|
||||||
|
delete cleanWindowSearch.state;
|
||||||
|
delete cleanWindowSearch.authcode;
|
||||||
|
|
||||||
|
const cleanWindowSearchStr = queryString.stringify(cleanWindowSearch);
|
||||||
|
const newUrl =
|
||||||
|
window.location.origin +
|
||||||
|
window.location.pathname +
|
||||||
|
(cleanWindowSearchStr ? '?' + cleanWindowSearchStr : '') +
|
||||||
|
window.location.hash;
|
||||||
|
window.history.replaceState(null, '', newUrl);
|
||||||
|
|
||||||
|
return {
|
||||||
|
pathname: redirectPath || location.pathname,
|
||||||
|
search: queryString.stringify(mergedParams),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [location.pathname, location.search]);
|
||||||
|
|
||||||
|
if (redirectTarget) {
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
<Route path="*" element={<Navigate to={redirectTarget} replace />} />
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const isViewPage = location.pathname === '/view';
|
const isViewPage = location.pathname === '/view';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user