diff --git a/examples/next/pages/_document.page.tsx b/examples/next/pages/_document.page.tsx
index 4122e24f77a..52f07545e8c 100644
--- a/examples/next/pages/_document.page.tsx
+++ b/examples/next/pages/_document.page.tsx
@@ -20,8 +20,6 @@ class MyDocument extends Document {
React Example App
- React Example App
-
diff --git a/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/StorageBrowser.ts b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/StorageBrowser.ts
new file mode 100644
index 00000000000..6b6f2538710
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/StorageBrowser.ts
@@ -0,0 +1,26 @@
+import { Amplify } from 'aws-amplify';
+
+import {
+ createAmplifyAuthAdapter,
+ createStorageBrowser,
+ elementsDefault,
+} from '@aws-amplify/ui-react-storage/browser';
+import '@aws-amplify/ui-react-storage/styles.css';
+import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
+
+import config from './aws-exports';
+
+Amplify.configure(config);
+
+const defaultPrefixes = [
+ 'public/',
+ // intentionally added to test a prefix that should return 403 forbidden
+ 'forbidden/',
+ (identityId: string) => `protected/${identityId}/`,
+ (identityId: string) => `private/${identityId}/`,
+];
+
+export const { StorageBrowser } = createStorageBrowser({
+ elements: elementsDefault,
+ config: createAmplifyAuthAdapter({ options: { defaultPrefixes } }),
+});
diff --git a/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/[locations]/[location-detail]/index.page.tsx b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/[locations]/[location-detail]/index.page.tsx
new file mode 100644
index 00000000000..509b7c40096
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/[locations]/[location-detail]/index.page.tsx
@@ -0,0 +1,58 @@
+import { useRouter } from 'next/router';
+
+import { signOut } from 'aws-amplify/auth';
+import { Button, Flex } from '@aws-amplify/ui-react';
+
+import { StorageBrowser } from '../../StorageBrowser';
+
+import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
+import '@aws-amplify/ui-react-storage/styles.css';
+
+export default function Page() {
+ const router = useRouter();
+
+ if (!router.query.bucket) return null;
+
+ return (
+
+
+
+ {
+ router.replace({ query: { ...router.query, actionType } });
+ }}
+ onExit={() => {
+ router.back();
+ }}
+ />
+ {typeof router.query.actionType === 'string' ? (
+
+ ) : null}
+
+
+ );
+}
diff --git a/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/[locations]/index.page.tsx b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/[locations]/index.page.tsx
new file mode 100644
index 00000000000..e43bc321b4a
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/[locations]/index.page.tsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { useRouter } from 'next/router';
+
+import { signOut } from 'aws-amplify/auth';
+import { Button, Flex } from '@aws-amplify/ui-react';
+
+import { StorageBrowser } from '../StorageBrowser';
+
+import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
+import '@aws-amplify/ui-react-storage/styles.css';
+
+function Locations() {
+ const router = useRouter();
+
+ return (
+
+
+
+
+ {
+ router.push({
+ pathname: `${router.pathname}/location-detail`,
+ query: { ...destination },
+ });
+ }}
+ />
+
+
+
+ );
+}
+
+export default Locations;
diff --git a/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/aws-exports.js b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/aws-exports.js
new file mode 100644
index 00000000000..4245c81a219
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/aws-exports.js
@@ -0,0 +1,2 @@
+import awsExports from '@environments/storage/file-uploader/src/aws-exports';
+export default awsExports;
diff --git a/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/index.page.tsx b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/index.page.tsx
new file mode 100644
index 00000000000..9cfc40de58b
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/index.page.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { useRouter } from 'next/router';
+
+import useIsSignedIn from './useIsSignedIn';
+
+import { Authenticator } from '@aws-amplify/ui-react';
+
+import '@aws-amplify/ui-react-storage/styles.css';
+import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
+
+function Example() {
+ const router = useRouter();
+
+ useIsSignedIn({
+ onSignIn: () => {
+ router.push(`${router.pathname}/locations`);
+ },
+ });
+
+ return ;
+}
+
+export default Example;
diff --git a/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/useIsSignedIn.ts b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/useIsSignedIn.ts
new file mode 100644
index 00000000000..b3099704ccc
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/default-auth/routed/useIsSignedIn.ts
@@ -0,0 +1,71 @@
+import React from 'react';
+
+import { fetchAuthSession } from 'aws-amplify/auth';
+import { Hub, HubCallback } from '@aws-amplify/core';
+import { isFunction } from '@aws-amplify/ui';
+
+export interface UseIsSignedInParams {
+ onSignIn?: () => void;
+ onSignOut?: () => void;
+}
+
+interface UseIsSignedIn {
+ isSignedIn: boolean;
+}
+
+const INITIAL_STATE: UseIsSignedIn = { isSignedIn: false };
+
+/**
+ * listens for `Auth` sign in and sign out events
+ *
+ * @param {UseIsSignedInParams} params `onSignIn` and `onSignOut` event callbacks
+ * @returns {UseIsSignedIn} Outputs `isSignedIn`
+ */
+export default function useIsSignedIn({
+ onSignIn,
+ onSignOut,
+}: UseIsSignedInParams): UseIsSignedIn {
+ const [output, setOutput] = React.useState(
+ () => INITIAL_STATE
+ );
+
+ React.useEffect(() => {
+ fetchAuthSession().then(() => {
+ if (isFunction(onSignIn)) onSignIn();
+ });
+ }, [onSignIn]);
+
+ const handleEvent: HubCallback = React.useCallback(
+ ({ payload }) => {
+ switch (payload.event) {
+ case 'signIn':
+ case 'autoSignIn': {
+ if (isFunction(onSignIn)) {
+ onSignIn();
+ }
+ setOutput({ isSignedIn: true });
+ break;
+ }
+ case 'signOut': {
+ if (isFunction(onSignOut)) {
+ onSignOut();
+ }
+ setOutput({ isSignedIn: false });
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ },
+ [onSignIn, onSignOut]
+ );
+
+ React.useEffect(() => {
+ const unsubscribe = Hub.listen('auth', handleEvent, 'useIsSignedIn');
+
+ return unsubscribe;
+ }, [handleEvent]);
+
+ return output;
+}
diff --git a/examples/next/pages/ui/components/storage/storage-browser/managed-auth/index.page.tsx b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/index.page.tsx
index b8e23faef8e..8ae7e54e41a 100644
--- a/examples/next/pages/ui/components/storage/storage-browser/managed-auth/index.page.tsx
+++ b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/index.page.tsx
@@ -2,9 +2,8 @@ import React from 'react';
import { createStorageBrowser } from '@aws-amplify/ui-react-storage/browser';
-import { auth, managedAuthAdapter } from '../managedAuthAdapter';
-
-import { Button, Flex } from '@aws-amplify/ui-react';
+import { managedAuthAdapter } from '../managedAuthAdapter';
+import { SignIn, SignOutButton } from './routed/components';
import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
import '@aws-amplify/ui-react-storage/styles.css';
@@ -12,43 +11,13 @@ import '@aws-amplify/ui-react-storage/styles.css';
const { StorageBrowser } = createStorageBrowser({ config: managedAuthAdapter });
function Example() {
- const [authenticated, setAuthenticated] = React.useState(false);
- const [isLoading, setIsLoading] = React.useState(false);
- const [errorMessage, setErrorMessage] = React.useState();
+ const [showSignIn, setShowSignIn] = React.useState(false);
- return !authenticated ? (
-
-
- {isLoading ? Authenticating... : null}
- {errorMessage ? {errorMessage} : null}
-
+ return !showSignIn ? (
+ setShowSignIn(true)} />
) : (
<>
-
+ setShowSignIn(false)} />
>
);
diff --git a/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/StorageBrowser.ts b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/StorageBrowser.ts
new file mode 100644
index 00000000000..89a3b4d478b
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/StorageBrowser.ts
@@ -0,0 +1,16 @@
+import { Auth } from '../../managedAuthAdapter';
+import {
+ createManagedAuthAdapter,
+ createStorageBrowser,
+} from '@aws-amplify/ui-react-storage/browser';
+
+export const routedAuth = new Auth({ persistCredentials: true });
+
+const config = createManagedAuthAdapter({
+ credentialsProvider: routedAuth.credentialsProvider,
+ region: process.env.NEXT_PUBLIC_MANAGED_AUTH_REGION,
+ accountId: process.env.NEXT_PUBLIC_MANAGED_AUTH_ACCOUNT_ID,
+ registerAuthListener: routedAuth.registerAuthListener,
+});
+
+export const { StorageBrowser } = createStorageBrowser({ config });
diff --git a/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/[locations]/[location-detail]/index.page.tsx b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/[locations]/[location-detail]/index.page.tsx
new file mode 100644
index 00000000000..d590ed7279e
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/[locations]/[location-detail]/index.page.tsx
@@ -0,0 +1,52 @@
+import { useRouter } from 'next/router';
+
+import { Flex } from '@aws-amplify/ui-react';
+
+import { SignOutButton } from '../../components';
+import { StorageBrowser } from '../../StorageBrowser';
+
+import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
+import '@aws-amplify/ui-react-storage/styles.css';
+
+export default function Page() {
+ const { back, query, pathname, replace } = useRouter();
+
+ if (!query.bucket) return null;
+
+ return (
+
+ {
+ replace(pathname.replace('[locations]/[location-detail]', ''));
+ }}
+ />
+
+ {
+ replace({ query: { ...query, actionType } });
+ }}
+ onNavigate={(destination) => {
+ replace({ query: { ...destination } });
+ }}
+ onExit={() => {
+ back();
+ }}
+ />
+ {typeof query.actionType === 'string' ? (
+
+ ) : null}
+
+
+ );
+}
diff --git a/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/[locations]/index.page.tsx b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/[locations]/index.page.tsx
new file mode 100644
index 00000000000..60906f26802
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/[locations]/index.page.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { useRouter } from 'next/router';
+
+import { Flex } from '@aws-amplify/ui-react';
+
+import { SignOutButton } from '../components';
+import { StorageBrowser } from '../StorageBrowser';
+
+import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
+import '@aws-amplify/ui-react-storage/styles.css';
+
+function Locations() {
+ const router = useRouter();
+
+ return (
+
+ {
+ router.replace(router.pathname.replace('[locations]', ''));
+ }}
+ />
+
+
+ {
+ router.push({
+ pathname: `${router.pathname}/location-detail`,
+ query: { ...destination },
+ });
+ }}
+ />
+
+
+ );
+}
+
+export default Locations;
diff --git a/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/components.tsx b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/components.tsx
new file mode 100644
index 00000000000..62672c7ce3c
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/components.tsx
@@ -0,0 +1,54 @@
+import React from 'react';
+
+import { Button, Flex } from '@aws-amplify/ui-react';
+
+import { routedAuth } from './StorageBrowser';
+
+export function SignIn({ onSignIn }: { onSignIn?: () => void }) {
+ const [isLoading, setIsLoading] = React.useState(false);
+ const [errorMessage, setErrorMessage] = React.useState();
+
+ return (
+
+
+
+ {isLoading ? Authenticating... : null}
+ {errorMessage ? {errorMessage} : null}
+
+ );
+}
+
+export function SignOutButton({ onSignOut }: { onSignOut?: () => void }) {
+ return (
+
+ );
+}
diff --git a/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/index.page.tsx b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/index.page.tsx
new file mode 100644
index 00000000000..39451335171
--- /dev/null
+++ b/examples/next/pages/ui/components/storage/storage-browser/managed-auth/routed/index.page.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import { useRouter } from 'next/router';
+
+import { SignIn } from './components';
+
+import '@aws-amplify/ui-react-storage/storage-browser-styles.css';
+import '@aws-amplify/ui-react-storage/styles.css';
+
+function Example() {
+ const router = useRouter();
+
+ return (
+ {
+ router.push(`${router.pathname}/locations`);
+ }}
+ />
+ );
+}
+
+export default Example;
diff --git a/examples/next/pages/ui/components/storage/storage-browser/managedAuthAdapter.ts b/examples/next/pages/ui/components/storage/storage-browser/managedAuthAdapter.ts
index d67cb457254..f6f8051c1df 100644
--- a/examples/next/pages/ui/components/storage/storage-browser/managedAuthAdapter.ts
+++ b/examples/next/pages/ui/components/storage/storage-browser/managedAuthAdapter.ts
@@ -6,21 +6,48 @@ import {
type CredentialsProvider = CreateManagedAuthAdapterInput['credentialsProvider'];
type Credentials = Awaited>;
-class Auth {
+export class Auth {
+ #persistCredentials: boolean;
#credentials: Credentials | undefined;
#onAuthStatusChange: () => void | undefined;
+ constructor(options?: { persistCredentials?: boolean }) {
+ const { persistCredentials = false } = options ?? {};
+ this.#persistCredentials = persistCredentials;
+ }
+
+ #clearCredentials() {
+ this.#onAuthStatusChange?.();
+ this.#onAuthStatusChange = undefined;
+ localStorage.removeItem('creds');
+ this.#credentials = undefined;
+ }
+
#getCredentials(): Credentials {
+ if (this.#persistCredentials) {
+ return JSON.parse(localStorage.getItem('creds'));
+ }
+
return this.#credentials;
}
#setCredentials(credentials: Credentials) {
+ if (this.#persistCredentials) {
+ localStorage.setItem('creds', JSON.stringify(credentials));
+ }
this.#credentials = credentials;
}
- async #fetchCredentials(): Promise {
+ async #fetchCredentials(options?: {
+ forceRefresh?: boolean;
+ }): Promise {
+ const { forceRefresh = false } = options ?? {};
+
+ if (forceRefresh) {
+ this.#clearCredentials();
+ }
const credentials = this.#getCredentials();
- if (credentials) {
+ if (!forceRefresh && credentials) {
return credentials;
}
@@ -53,7 +80,7 @@ class Auth {
}
get credentialsProvider(): CredentialsProvider {
- return async () => await this.#fetchCredentials();
+ return (options) => this.#fetchCredentials(options);
}
registerAuthListener = (onAuthStatusChange: () => void) => {
@@ -61,12 +88,13 @@ class Auth {
};
async signIn(input?: {
+ forceRefresh?: boolean;
onSignIn?: () => void;
onError?: (e: Error) => void;
}): Promise {
- const { onError, onSignIn } = input ?? {};
+ const { forceRefresh, onError, onSignIn } = input ?? {};
try {
- await this.#fetchCredentials();
+ await this.#fetchCredentials({ forceRefresh });
onSignIn?.();
} catch (e) {
onError?.(e);
@@ -74,9 +102,8 @@ class Auth {
}
signOut(input?: { onSignOut?: () => void }) {
- this.#credentials = undefined;
- this.#onAuthStatusChange?.();
- input?.onSignOut();
+ this.#clearCredentials();
+ input?.onSignOut?.();
}
}