Compare commits

..
1 Commits
Author SHA1 Message Date
westfarn a57173e25e Make auth JWT-only for Capacitor WebView origins (#22)
Unit Tests / test (pull_request) Successful in 10s
Drop CSRF cookie dependency (js-cookie), store tokens via localStorage with
optional Capacitor Preferences mirror, and attach Authorization on every
request so Android/iOS shells work without third-party cookies.
2026-07-26 15:40:45 -05:00
5 changed files with 4 additions and 98 deletions
-1
View File
@@ -97,7 +97,6 @@ axiosInstance.interceptors.response.use(
if (error.response.status === 401 && isRefreshCall) {
await clearTokens();
applyAccessToken(null);
// Hash-safe on Capacitor (#24); plain path on web BrowserRouter.
redirectToAppPath("/signin/");
return Promise.reject(error);
}
+3 -3
View File
@@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import AppRouter from './llm-fe/platform/AppRouter';
import { BrowserRouter } from 'react-router-dom';
import { AuthProvider } from './llm-fe/contexts/AuthContext';
import { AccountProvider } from './llm-fe/contexts/AccountContext';
import { WebSocketProvider } from './llm-fe/contexts/WebSocketContext';
@@ -16,7 +16,7 @@ const root = ReactDOM.createRoot(
);
root.render(
<React.StrictMode>
<AppRouter>
<BrowserRouter>
<script async defer src="https://tianji.aimloperations.com/tracker.js" data-website-id="cm7x7m52m03kbddswbswrt17y"></script>
<AuthProvider>
<AccountProvider>
@@ -33,6 +33,6 @@ root.render(
</WebSocketProvider>
</AccountProvider>
</AuthProvider>
</AppRouter>
</BrowserRouter>
</React.StrictMode>
);
@@ -1,63 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { Route, Routes } from 'react-router-dom';
import AppRouter from './AppRouter';
import { getAppRouter, isNativePlatform } from './nativePlatform';
describe('AppRouter / getAppRouter (#24)', () => {
const originalCapacitor = window.Capacitor;
afterEach(() => {
if (originalCapacitor === undefined) {
delete window.Capacitor;
} else {
window.Capacitor = originalCapacitor;
}
});
it('uses BrowserRouter on web', () => {
delete window.Capacitor;
expect(isNativePlatform()).toBe(false);
const Router = getAppRouter();
expect(Router.name).toBe('BrowserRouter');
});
it('uses HashRouter on native Capacitor', () => {
window.Capacitor = { isNativePlatform: () => true };
expect(isNativePlatform()).toBe(true);
const Router = getAppRouter();
expect(Router.name).toBe('HashRouter');
});
it('renders nested routes under HashRouter without blank screen', () => {
window.Capacitor = { isNativePlatform: () => true };
window.location.hash = '#/account/';
render(
<AppRouter>
<Routes>
<Route path="/account/" element={<div>Account Page</div>} />
<Route path="/" element={<div>Home</div>} />
</Routes>
</AppRouter>,
);
expect(screen.getByText('Account Page')).toBeInTheDocument();
});
it('renders nested routes under BrowserRouter on web', () => {
delete window.Capacitor;
window.history.pushState({}, '', '/account/');
render(
<AppRouter>
<Routes>
<Route path="/account/" element={<div>Account Page</div>} />
<Route path="/" element={<div>Home</div>} />
</Routes>
</AppRouter>,
);
expect(screen.getByText('Account Page')).toBeInTheDocument();
});
});
-17
View File
@@ -1,17 +0,0 @@
import React, { ReactNode } from 'react';
import { getAppRouter } from './nativePlatform';
type AppRouterProps = {
children: ReactNode;
};
/**
* Single place that chooses BrowserRouter (web) vs HashRouter (Capacitor).
* Avoids sprinkling Capacitor.isNativePlatform() through the route tree (#24).
*/
const AppRouter = ({ children }: AppRouterProps): JSX.Element => {
const Router = getAppRouter();
return <Router>{children}</Router>;
};
export default AppRouter;
+1 -14
View File
@@ -1,5 +1,5 @@
/**
* Capacitor / WebView platform helpers (#24 / #22).
* Capacitor / WebView platform helpers.
* Prefer window.Capacitor so unit tests and web builds need no Capacitor package.
*/
@@ -50,16 +50,3 @@ export function redirectToAppPath(path) {
}
window.location.href = appHref(path);
}
/**
* Router class for the current runtime.
* HashRouter on Capacitor so nested routes reload without a server rewrite.
* BrowserRouter on web (nginx already rewrites to index.html).
*
* @returns {typeof import('react-router-dom').HashRouter | typeof import('react-router-dom').BrowserRouter}
*/
export function getAppRouter() {
// Lazy require keeps this module usable from axios (no React import cycle in tests).
const { BrowserRouter, HashRouter } = require("react-router-dom");
return isNativePlatform() ? HashRouter : BrowserRouter;
}