Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/young-maps-grin.md
Original file line number Diff line number Diff line change
@@ -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.
23 changes: 21 additions & 2 deletions tools/diagnostics-app/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
27 changes: 27 additions & 0 deletions tools/diagnostics-app/src/library/powersync/TokenConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}