diff --git a/.changeset/young-maps-grin.md b/.changeset/young-maps-grin.md new file mode 100644 index 000000000..5871c4427 --- /dev/null +++ b/.changeset/young-maps-grin.md @@ -0,0 +1,5 @@ +--- +'@powersync/diagnostics-app': patch +--- + +Added support for a "token" query parameter, when supplied to the root URL it will be used as the API token for the diagnostics tool instead of needing to be provided manually to the login form. diff --git a/tools/diagnostics-app/src/app/page.tsx b/tools/diagnostics-app/src/app/page.tsx index 81e757efe..1c061f501 100644 --- a/tools/diagnostics-app/src/app/page.tsx +++ b/tools/diagnostics-app/src/app/page.tsx @@ -1,14 +1,33 @@ import React from 'react'; import { CircularProgress, Grid, styled } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useSearchParams } from 'react-router-dom'; import { DEFAULT_ENTRY_ROUTE, LOGIN_ROUTE } from './router'; import { connector } from '@/library/powersync/ConnectionManager'; +import { getTokenEndpoint } from '@/library/powersync/TokenConnector'; +import { SyncClientImplementation } from '@powersync/web'; export default function EntryPage() { const navigate = useNavigate(); + const [searchParams] = useSearchParams(); React.useEffect(() => { - if (connector.hasCredentials()) { + if (searchParams.has('token')) { + (async () => { + const token = searchParams.get('token')!; + const endpoint = getTokenEndpoint(token); + if (endpoint == null) { + throw new Error('endpoint is required'); + } + + await connector.signIn({ + token, + endpoint, + clientImplementation: SyncClientImplementation.RUST + }); + + navigate(DEFAULT_ENTRY_ROUTE); + })(); + } else if (connector.hasCredentials()) { navigate(DEFAULT_ENTRY_ROUTE); } else { navigate(LOGIN_ROUTE); diff --git a/tools/diagnostics-app/src/components/widgets/LoginDetailsWidget.tsx b/tools/diagnostics-app/src/components/widgets/LoginDetailsWidget.tsx index ef7900416..df8030f5c 100644 --- a/tools/diagnostics-app/src/components/widgets/LoginDetailsWidget.tsx +++ b/tools/diagnostics-app/src/components/widgets/LoginDetailsWidget.tsx @@ -13,6 +13,7 @@ import { } from '@mui/material'; import { Formik, FormikErrors } from 'formik'; import { SyncClientImplementation } from '@powersync/web'; +import { getTokenEndpoint } from '@/library/powersync/TokenConnector'; export type LoginDetailsFormValues = { token: string; @@ -192,30 +193,3 @@ namespace S { margin-bottom: 20px; `; } - -function getTokenEndpoint(token: string) { - try { - const [head, body, signature] = token.split('.'); - const payload = JSON.parse(atob(body)); - const aud = payload.aud as string | string[] | undefined; - const audiences = Array.isArray(aud) ? aud : [aud]; - - // Prioritize public powersync URL - for (let aud of audiences) { - if (aud?.match(/^https?:.*.journeyapps.com/)) { - return aud; - } - } - - // Fallback to any URL - for (let aud of audiences) { - if (aud?.match(/^https?:/)) { - return aud; - } - } - - return null; - } catch (e) { - return null; - } -} diff --git a/tools/diagnostics-app/src/library/powersync/TokenConnector.ts b/tools/diagnostics-app/src/library/powersync/TokenConnector.ts index 1b088cafc..5130665f0 100644 --- a/tools/diagnostics-app/src/library/powersync/TokenConnector.ts +++ b/tools/diagnostics-app/src/library/powersync/TokenConnector.ts @@ -78,3 +78,30 @@ function checkJWT(token: string) { throw new Error(`Token must be a JWT: Not all parts are base64 encoded`); } } + +export function getTokenEndpoint(token: string): string | null { + try { + const [head, body, signature] = token.split('.'); + const payload = JSON.parse(atob(body)); + const aud = payload.aud as string | string[] | undefined; + const audiences = Array.isArray(aud) ? aud : [aud]; + + // Prioritize public powersync URL + for (let aud of audiences) { + if (aud?.match(/^https?:.*.journeyapps.com/)) { + return aud; + } + } + + // Fallback to any URL + for (let aud of audiences) { + if (aud?.match(/^https?:/)) { + return aud; + } + } + + return null; + } catch (e) { + return null; + } +}