Skip to content

Commit

Permalink
#110 Find a prettier fix for loading pages before getting actor , si…
Browse files Browse the repository at this point in the history
…gn in button
  • Loading branch information
joepio committed Nov 16, 2021
1 parent a1fe766 commit 7f99309
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 32 deletions.
6 changes: 5 additions & 1 deletion data-browser/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { QueryParamProvider } from 'use-query-params';
import { BrowserRouter, Route } from 'react-router-dom';
import { Store, urls } from '@tomic/lib';
import { StoreContext } from '@tomic/react';
import { initAgentFromLocalStorage, StoreContext } from '@tomic/react';

import { GlobalStyle, ThemeWrapper } from './styling';
import { Routes } from './routes/Routes';
Expand Down Expand Up @@ -30,6 +30,10 @@ store.errorHandler = e => {
};
/** Setup bugsnag for error handling */
const ErrorBoundary = initBugsnag();
/** Initialize the agent from localstorage */
const agent = initAgentFromLocalStorage();
agent && store.setAgent(agent);

/** Fetch all the Properties and Classes - this helps speed up the app. */
store.fetchResource(urls.properties.getAll);
store.fetchResource(urls.classes.getAll);
Expand Down
21 changes: 14 additions & 7 deletions data-browser/src/components/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { paths } from '../routes/paths';
import { ErrorLook } from '../views/ResourceInline';
import { openURL } from '../helpers/navigation';
import { isUnauthorized } from '@tomic/lib/src/error';
import { SignInButton } from './SignInButton';

/** Amount of pixels where the sidebar automatically shows */
export const SIDEBAR_TOGGLE_WIDTH = 600;
Expand Down Expand Up @@ -220,13 +221,19 @@ const SideBarDrive = React.memo(function SBD({
})
) : drive.loading ? null : (
<SideBarErr>
{drive.error
? isUnauthorized(drive.error)
? agent
? 'unauthorized'
: 'Sign in to get access'
: drive.error.message
: 'this should not happen'}
{drive.error ? (
drive.isUnauthorized() ? (
agent ? (
'unauthorized'
) : (
<SignInButton />
)
) : (
drive.error.message
)
) : (
'this should not happen'
)}
</SideBarErr>
)}
</>
Expand Down
21 changes: 21 additions & 0 deletions data-browser/src/components/SignInButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as React from 'react';
import { useHistory } from 'react-router-dom';
import { paths } from '../routes/paths';
import { Button } from './Button';

/**
* Button that currently links to the Agent Settings page. Should probably open
* in a Modal.
*/
export function SignInButton() {
const history = useHistory();
return (
<Button
type='button'
onClick={() => history.push(paths.agentSettings)}
title='Go the the User Settings page'
>
Sign in
</Button>
);
}
15 changes: 12 additions & 3 deletions data-browser/src/views/ErrorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ContainerNarrow } from '../components/Containers';
import { ErrorLook } from './ResourceInline';
import { Button } from '../components/Button';
import { isUnauthorized } from '@tomic/lib/src/error';
import { SignInButton } from '../components/SignInButton';

type ErrorPageProps = {
resource: Resource;
Expand All @@ -23,9 +24,17 @@ function ErrorPage({ resource }: ErrorPageProps): JSX.Element {
return (
<ContainerNarrow>
<h1>Unauthorized</h1>
{agent ? null : <p>Try signing in</p>}
<p>{resource.error.message}</p>
<Button onClick={() => store.fetchResource(subject)}>Retry</Button>
{agent ? (
<>
<p>{resource.error.message}</p>
<Button onClick={() => store.fetchResource(subject)}>Retry</Button>
</>
) : (
<>
<p>{"You don't have access to this, try signing in:"}</p>
<SignInButton />
</>
)}
</ContainerNarrow>
);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ export function removeQueryParamsFromURL(subject: string): string {
return subject;
}

/** Creates an x-atomic-signature header */
async function signRequest(
/** Creates authentication headers and signs the request */
export async function signRequest(
/** The resource meant to be fetched */
subject: string,
agent: Agent,
Expand Down
6 changes: 6 additions & 0 deletions lib/src/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Store } from './store';
import { valToArray } from './value';
import { Agent } from './agent';
import { JSONValue } from '.';
import { isUnauthorized } from './error';

/** Contains the PropertyURL / Value combinations */
export type PropVals = Map<string, JSONValue>;
Expand Down Expand Up @@ -152,6 +153,11 @@ export class Resource {
return this.propvals;
}

/** Returns true is the resource had an `Unauthorized` 401 response. */
isUnauthorized(): boolean {
return this.error != undefined && isUnauthorized(this.error);
}

/** Removes the resource form both the server and locally */
async destroy(store: Store, agent?: Agent): Promise<void> {
const newCommitBuilder = new CommitBuilder(this.getSubject());
Expand Down
6 changes: 6 additions & 0 deletions lib/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,12 @@ export class Store {
*/
setAgent(agent: Agent): void {
this.agent = agent;
// TODO: maybe iterate over all loaded resources, check if they have an Unauthorized error, and retry these.
this.resources.forEach(r => {
if (r.isUnauthorized()) {
this.fetchResource(r.getSubject());
}
});
}

/** Sets the Base URL, without the trailing slash. */
Expand Down
18 changes: 0 additions & 18 deletions react/src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,13 @@ export function useResource(
} = { allowIncomplete: true, newResource: false },
): Resource {
const { newResource, allowIncomplete } = opts;
const [agent] = useCurrentAgent();
const store = useStore();
const [resource, setResource] = useState<Resource>(
store.getResourceLoading(subject, {
newResource,
allowIncomplete,
}),
);
// We automatically retry fetching a resource if it's response is 401, because the first response fires _before_ the agent is loaded
const [triedWith, setTriedWith] = useState(agent?.subject);

// When the agent changes and there is an error, retry the request
useEffect(() => {
if (
resource.error &&
isUnauthorized(resource.error) &&
agent?.subject !== triedWith
) {
// We need to check if the authorize call failed because the user was _publicAgent_ (i.e. no agent).
// Otherwise, this will loop forever.
resource.error.message.includes(urls.instances.publicAgent) &&
store.fetchResource(subject);
setTriedWith(agent?.subject);
}
}, [agent, resource]);

// If the subject changes, make sure to change the resource!
useEffect(() => {
Expand Down
15 changes: 14 additions & 1 deletion react/src/useCurrentAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Agent } from '@tomic/lib';
import { useStore } from './hooks';
import { useLocalStorage } from './useLocalStorage';

const AGENT_LOCAL_STORAGE_KEY = 'agent';

/**
* A hook for using and adjusting the Agent. Persists the agent to LocalStorage.
* Only use this hook once inside your app! The best place to use this, is
Expand All @@ -11,7 +13,7 @@ import { useLocalStorage } from './useLocalStorage';
export const useCurrentAgent = (): [Agent | null, (agent?: Agent) => void] => {
// Localstorage for cross-session persistence of JSON object
const [agentJSON, setAgentJSON] = useLocalStorage<Agent | null>(
'agent',
AGENT_LOCAL_STORAGE_KEY,
null,
);
// In memory representation of the full Agent
Expand Down Expand Up @@ -42,3 +44,14 @@ export const useCurrentAgent = (): [Agent | null, (agent?: Agent) => void] => {

return [agent, setAgentJSON];
};

/** Gets the Agent from local storage, if any. Useful when initializing app */
export function initAgentFromLocalStorage(): Agent | null {
const lsItem = localStorage.getItem(AGENT_LOCAL_STORAGE_KEY);
if (lsItem == null) {
return null;
}
const agentJSON = JSON.parse(lsItem);
const agent: Agent | null = agentJSON && Agent.fromJSON(agentJSON);
return agent;
}

0 comments on commit 7f99309

Please sign in to comment.