diff --git a/.changeset/happy-insects-talk.md b/.changeset/happy-insects-talk.md new file mode 100644 index 0000000..0a15dcd --- /dev/null +++ b/.changeset/happy-insects-talk.md @@ -0,0 +1,5 @@ +--- +"@saleor/app-sdk": minor +--- + +Added useAuthenticatedFetch hook with can construct decorated window.fetch with pre-defined headers with required AppBridge state. Can be used with createProtectedHandler diff --git a/.changeset/tall-birds-sort.md b/.changeset/tall-birds-sort.md new file mode 100644 index 0000000..da83371 --- /dev/null +++ b/.changeset/tall-birds-sort.md @@ -0,0 +1,5 @@ +--- +"@saleor/app-sdk": patch +--- + +Remove MANAGE_APPS from possible permissions, because App should not have it. Mutations that requires MANAGE_APPS will not work with App Token even if permission is set diff --git a/docs/protected-handlers.md b/docs/protected-handlers.md index 21a739d..4aecd6c 100644 --- a/docs/protected-handlers.md +++ b/docs/protected-handlers.md @@ -24,6 +24,7 @@ export type ProtectedHandlerContext = { - the API URL has been registered, with help of the APL - the request has `authorization-bearer` - the auth token is a valid JWT token created by the Saleor running on the given URL +- user has required permissions in the token For example purposes our endpoint will only log welcome message: @@ -44,8 +45,10 @@ export const handler = async ( /** * If any of the requirements is failed, an error response will be returned. * Otherwise, provided handler function fill be called. + * + * Last argument is optional array of permissions that will be checked. If user doesn't have them, will return 401 before handler is called */ -export default createProtectedHandler(handler, saleorApp.apl); +export default createProtectedHandler(handler, saleorApp.apl, ["MANAGE_ORDERS"]); ``` To make your requests successfully communicate with the backend, `saleor-api-url` and `authorization-bearer` headers are required: @@ -65,3 +68,30 @@ fetch("/api/protected", { ``` If you want to read more about `appBridgeState`, check [App Bridge](./app-bridge.md) documentation. + +### Using `useAuthenticatedFetch()` hook + +Instead of manually attaching headers with AppBridge context, you can use `useAuthenticatedFetch()` hook + +Since it requires AppBridge, it's only available in browser context. It depends on `Window` object, +so your app will break if Next.js tries to render it server-side. Hence, ensure component that uses the hook is imported with dynamic() + +Component must be within `AppBridgeProvider` to have access to the AppBridge + +```tsx +import { useAuthenticatedFetch } from "@saleor/app-sdk/app-bridge"; +import { useEffect } from "react"; + +export const ClientComponent = () => { + const fetch = useAuthenticatedFetch(); + + useEffect(() => { + /** + * Auth headers are set up automatically, so you can just call the fetch function + */ + fetch("/api/protected"); + }, [fetch]); + + return
Your UI
; +}; +``` diff --git a/src/app-bridge/fetch.ts b/src/app-bridge/fetch.ts index b2763b0..2a7477d 100644 --- a/src/app-bridge/fetch.ts +++ b/src/app-bridge/fetch.ts @@ -1,4 +1,8 @@ -import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_DOMAIN_HEADER } from "../const"; +import { + SALEOR_API_URL_HEADER, + SALEOR_AUTHORIZATION_BEARER_HEADER, + SALEOR_DOMAIN_HEADER, +} from "../const"; import { AppBridge } from "./app-bridge"; import { useAppBridge } from "./app-bridge-provider"; @@ -10,11 +14,12 @@ type HasAppBridgeState = Pick; export const createAuthenticatedFetch = (appBridge: HasAppBridgeState, fetch = global.fetch): typeof global.fetch => (input, init) => { - const { token, domain } = appBridge.getState(); + const { token, domain, saleorApiUrl } = appBridge.getState(); const headers = new Headers(init?.headers); headers.set(SALEOR_DOMAIN_HEADER, domain); headers.set(SALEOR_AUTHORIZATION_BEARER_HEADER, token ?? ""); + headers.set(SALEOR_API_URL_HEADER, saleorApiUrl ?? ""); const clonedInit: RequestInit = { ...(init ?? {}), @@ -27,7 +32,7 @@ export const createAuthenticatedFetch = /** * Hook working only in browser context. Ensure parent component is dynamic() and mounted in the browser. */ -export const useAuthenticatedFetch = (fetch = global.fetch) => { +export const useAuthenticatedFetch = (fetch = window.fetch): typeof window.fetch => { const { appBridge } = useAppBridge(); if (!appBridge) { diff --git a/src/types.ts b/src/types.ts index e3c8349..f57769e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,7 +22,6 @@ export type Permission = | "MANAGE_USERS" | "MANAGE_STAFF" | "IMPERSONATE_USER" - | "MANAGE_APPS" | "MANAGE_OBSERVABILITY" | "MANAGE_CHECKOUTS" | "HANDLE_CHECKOUTS"