Add doc about SaleorAsyncWebhook (#109)
* Add doc about SaleorAsyncWebhook * Apply suggestions from code review Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com> * Update docs/saleor-async-webhook.md Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com> Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
This commit is contained in:
parent
6799c00098
commit
6fbf692a44
6 changed files with 162 additions and 19 deletions
|
@ -73,3 +73,9 @@ export type CreateAppRegisterHandlerOptions = {
|
|||
```
|
||||
|
||||
See [APL](./apl.md) for details what is Auth Persistence Layer in Saleor apps
|
||||
|
||||
### Async Webhook Handler
|
||||
|
||||
App SDK provides a utility that helps building (async) webhook handlers, so app can react on Saleor events.
|
||||
|
||||
Read about it [here](./saleor-async-webhook.md).
|
|
@ -45,9 +45,10 @@ localStorage.debug = "*";
|
|||
|
||||
Use the namespace name to enable debug logs for each module.
|
||||
|
||||
| Namespace name | Description |
|
||||
| --------------------- | -------------------------------------------------- |
|
||||
| \app-sdk:\* | Enable all |
|
||||
| app-sdk:AppBridge | Enable [AppBridge](./app-bridge.md) (browser only) |
|
||||
| app-sdk:Middleware:\* | Enable all middlewares (node only) |
|
||||
| app-sdk:APL:\* | Enable all APLs (node only) |
|
||||
| Namespace name | Description |
|
||||
|-------------------------------|----------------------------------------------------|
|
||||
| \app-sdk:\* | Enable all |
|
||||
| app-sdk:AppBridge | Enable [AppBridge](./app-bridge.md) (browser only) |
|
||||
| app-sdk:Middleware:\* | Enable all middlewares (node only) |
|
||||
| app-sdk:APL:\* | Enable all APLs (node only) |
|
||||
| app-sdk:SaleorAsyncWebhook:\* | Enable SaleorAsyncWebhook utility |
|
||||
|
|
144
docs/saleor-async-webhook.md
Normal file
144
docs/saleor-async-webhook.md
Normal file
|
@ -0,0 +1,144 @@
|
|||
# Saleor Async Webhook
|
||||
|
||||
Apps are usually connected via webhooks - one App sends a HTTP request to another App, informing about some event or requesting some action to be performed.
|
||||
|
||||
To inform your App about events originated from Saleor, you need to expose a webhook handler, which Saleor will call with POST request.
|
||||
|
||||
To avoid boilerplate, App SDK provides utility that abstracts connection details, allowing developers to focus on business logic.
|
||||
|
||||
Note - this utility works for Saleor Async Webhooks only. Support for Sync webhooks are not yet supported in SDK, but you can write your sync webhook handler
|
||||
from scratch.
|
||||
|
||||
## Creating async webhook with SaleorAsyncWebhook
|
||||
|
||||
### Creating webhook handler configuration
|
||||
|
||||
To use SaleorAsyncWebhook utility, first create a new instance. It can be created in your API handler file
|
||||
|
||||
```typescript
|
||||
// pages/api/webhooks/order-created.ts
|
||||
|
||||
/**
|
||||
* To be type safe, define payload from API. This should be imported from generated graphQL code
|
||||
*/
|
||||
type OrderPayload = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const orderCreatedWebhook = new SaleorAsyncWebhook<OrderPayload>({
|
||||
/**
|
||||
* Name of the webhook, not required
|
||||
*/
|
||||
name: "Order Created",
|
||||
/**
|
||||
* Relative path to the webhook, required
|
||||
*/
|
||||
webhookPath: "api/webhooks/order-created",
|
||||
/**
|
||||
* Event type, required
|
||||
*/
|
||||
asyncEvent: "ORDER_CREATED",
|
||||
/**
|
||||
* Decide if webhook created during app installation should be active or not
|
||||
*/
|
||||
isActive: true,
|
||||
/**
|
||||
* Provide APL, read more below
|
||||
*/
|
||||
apl: require("../lib/apl"),
|
||||
/**
|
||||
* Subscription query, telling Saleor what payload app expects
|
||||
*/
|
||||
query: "TODO",
|
||||
});
|
||||
```
|
||||
|
||||
- Check available events [here](https://docs.saleor.io/docs/3.x/developer/extending/apps/asynchronous-webhooks#available-webhook-events)
|
||||
- [Read more about APLs](./apl.md)
|
||||
- [Subscription query documentation](https://docs.saleor.io/docs/3.x/developer/extending/apps/subscription-webhook-payloads)
|
||||
|
||||
You can consider created `orderCreatedWebhook` a center point of your webhook configuration. Now, you need to create a handler and add it to manifest.
|
||||
|
||||
### Extending app manifest
|
||||
|
||||
Webhooks are created in Saleor when the App is installed. Saleor uses [AppManifest](https://docs.saleor.io/docs/3.x/developer/extending/apps/manifest) to get information about webhooks to create.
|
||||
`SaleorAsyncWebhook` utility can generate this manifest:
|
||||
|
||||
```typescript
|
||||
// pages/api/manifest
|
||||
|
||||
import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
|
||||
|
||||
import { orderCreatedWebhook } from "./order-created.ts";
|
||||
|
||||
export default createManifestHandler({
|
||||
manifestFactory({ appBaseUrl }) {
|
||||
return {
|
||||
/**
|
||||
* Add one or more webhook manifests.
|
||||
*/
|
||||
webhooks: [orderCreatedWebhook.getWebhookManifest(appBaseUrl)],
|
||||
// ...rest of your App's manifest
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now, try to read your manifest, in default Next.js config it will be `GET localhost:3000/api/manifest`. You should see webhook configuration as part of manifest response.
|
||||
|
||||
### Creating webhook domain logic
|
||||
|
||||
Now, let's create a handler that will process webhook data. Let's back to handler file `pages/api/webhooks/order-created.ts`.
|
||||
|
||||
```typescript
|
||||
type OrderPayload = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const orderCreatedWebhook = new SaleorAsyncWebhook<OrderPayload>({
|
||||
// ... your configuration
|
||||
});
|
||||
|
||||
export default orderCreatedWebhook.createHandler((req, res, context) => {
|
||||
const { baseUrl, event, payload, authData } = context;
|
||||
|
||||
console.log(payload.id); // type is inferred
|
||||
|
||||
// Perform some domain logic
|
||||
|
||||
// End with status 200
|
||||
return res.status(200).end();
|
||||
});
|
||||
```
|
||||
|
||||
### query vs subscriptionQueryAst
|
||||
|
||||
Subscription query can be specified using plain string or as `ASTNode` object created by `gql` tag.
|
||||
|
||||
If your project does not use any code generation for GraphQL operations, use the string. In case you are using [GraphQL Code Generator](https://the-guild.dev/graphql/codegen), which we highly recommend, you should pass a subscription as GraphQL ASTNode:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Subscription query, you can define it in the `.ts` file. If you write operations in separate `.graphql` files, codegen will also export them in the generated file.
|
||||
*/
|
||||
export const ExampleProductUpdatedSubscription = gql`
|
||||
${ProductUpdatedWebhookPayload}
|
||||
subscription ExampleProductUpdated {
|
||||
event {
|
||||
fragment ProductUpdatedWebhookPayload on ProductUpdated {
|
||||
product {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const productUpdatedWebhook = new SaleorAsyncWebhook<ProductUpdatedWebhookPayloadFragment>({
|
||||
name: "Example product updated webhook",
|
||||
webhookPath: "api/webhooks/saleor/product-updated",
|
||||
asyncEvent: "PRODUCT_UPDATED",
|
||||
apl: saleorApp.apl,
|
||||
subscriptionQueryAst: ExampleProductUpdatedSubscription,
|
||||
});
|
|
@ -1,11 +1,10 @@
|
|||
import { ASTNode } from "graphql";
|
||||
import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WebhookManifest } from "../..";
|
||||
import { APL } from "../../APL";
|
||||
import { createDebug } from "../../debug";
|
||||
import { gqlAstToString } from "../../gql-ast-to-string";
|
||||
import { WebhookEvent } from "../../types";
|
||||
import { WebhookEvent,WebhookManifest } from "../../types";
|
||||
import {
|
||||
processAsyncSaleorWebhook,
|
||||
SaleorWebhookError,
|
||||
|
@ -97,6 +96,8 @@ export class SaleorAsyncWebhook<TPayload = unknown> {
|
|||
/**
|
||||
* Returns full URL to the webhook, based on provided baseUrl.
|
||||
*
|
||||
* TODO: Shouldnt it be private?
|
||||
*
|
||||
* @param baseUrl Base URL used by your application
|
||||
*/
|
||||
getTargetUrl(baseUrl: string) {
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { AppWebhook as WebhookManifest } from "./types";
|
||||
|
||||
export * from "./const";
|
||||
export * from "./headers";
|
||||
export * from "./infer-webhooks";
|
||||
export * from "./saleor-app";
|
||||
export * from "./types";
|
||||
export * from "./urls";
|
||||
export { WebhookManifest };
|
|
@ -55,7 +55,7 @@ export interface AppExtension {
|
|||
url: string;
|
||||
}
|
||||
|
||||
export interface AppWebhook {
|
||||
export interface WebhookManifest {
|
||||
name: string;
|
||||
asyncEvents?: WebhookEvent[];
|
||||
syncEvents?: WebhookEvent[];
|
||||
|
@ -111,5 +111,5 @@ export interface AppManifest {
|
|||
|
||||
Be aware that subscription queries are required in manifest sections
|
||||
*/
|
||||
webhooks?: AppWebhook[];
|
||||
webhooks?: WebhookManifest[];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue