@pinelab/vendure-plugin-webhook 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/dist/api/api-extension.d.ts +1 -0
- package/dist/api/api-extension.js +49 -0
- package/dist/api/request-transformer.d.ts +23 -0
- package/dist/api/request-transformer.js +12 -0
- package/dist/api/webhook.entity.d.ts +12 -0
- package/dist/api/webhook.entity.js +44 -0
- package/dist/api/webhook.resolver.d.ts +26 -0
- package/dist/api/webhook.resolver.js +121 -0
- package/dist/api/webhook.service.d.ts +61 -0
- package/dist/api/webhook.service.js +182 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +5 -0
- package/dist/generated/graphql-types.d.ts +114 -0
- package/dist/generated/graphql-types.js +46 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/ui/graphql-types.ts +121 -0
- package/dist/ui/queries.ts +44 -0
- package/dist/ui/webhook-nav.module.ts +22 -0
- package/dist/ui/webhook.component.html +119 -0
- package/dist/ui/webhook.component.ts +157 -0
- package/dist/ui/webhook.module.ts +31 -0
- package/dist/webhook.plugin.d.ts +34 -0
- package/dist/webhook.plugin.js +73 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Vendure Webhook plugin
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Triggers an outgoing webhook based on configured events. Events are specified in `vendure-config` and webhooks are configured per
|
|
6
|
+
channel via the admin UI.
|
|
7
|
+
|
|
8
|
+
YOu can use this plugin for example to trigger builds when ProductEvents or CollectionEvents occur, or send notifications to external
|
|
9
|
+
platforms when orders are placed by subscribing to OrderPlacedEvents!
|
|
10
|
+
|
|
11
|
+
## Breaking changes since v7.x
|
|
12
|
+
|
|
13
|
+
:warning: V7 of this plugin allows you to create multiple webhooks per channel for multiple different events. You have to manually recreate your webhooks after migration! (Don't forget your DB migration):
|
|
14
|
+
|
|
15
|
+
- Check what URL is triggered for what event in your current environment, and note it down somewhere.
|
|
16
|
+
- Install the new version, migrate, start the server, and go to `Settings > Webhook` in the Admin UI.
|
|
17
|
+
- Create the hook. You can leave the `Transformer` field blank: the plugin will send an empty post without a transfomer.
|
|
18
|
+
|
|
19
|
+
## Getting started
|
|
20
|
+
|
|
21
|
+
1. `yarn add vendure-plugin-webhook`
|
|
22
|
+
2. Add the `WebhookPlugin` to your plugins in your `vendure-config.ts`:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { WebhookPlugin } from 'vendure-plugin-webhook';
|
|
26
|
+
|
|
27
|
+
plugins: [
|
|
28
|
+
WebhookPlugin.init({
|
|
29
|
+
/**
|
|
30
|
+
* Optional: 'delay' waits and deduplicates events for 3000ms.
|
|
31
|
+
* If 4 events were fired for the same channel within 3 seconds,
|
|
32
|
+
* only 1 webhook call will be sent
|
|
33
|
+
*/
|
|
34
|
+
delay: 3000,
|
|
35
|
+
events: [ProductEvent, ProductVariantEvent],
|
|
36
|
+
/**
|
|
37
|
+
* Optional: A requestTransformer allows you to send custom headers
|
|
38
|
+
* and a custom body with your webhook call.
|
|
39
|
+
* If no transformers are specified
|
|
40
|
+
*/
|
|
41
|
+
requestTransformers: [],
|
|
42
|
+
}),
|
|
43
|
+
AdminUiPlugin.init({
|
|
44
|
+
port: 3002,
|
|
45
|
+
route: 'admin',
|
|
46
|
+
app: compileUiExtensions({
|
|
47
|
+
outputPath: path.join(__dirname, '__admin-ui'),
|
|
48
|
+
// Add the WebhookPlugin's UI to the admin
|
|
49
|
+
extensions: [WebhookPlugin.ui],
|
|
50
|
+
}),
|
|
51
|
+
}),
|
|
52
|
+
];
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
3. Run a DB migration to create the custom entities.
|
|
56
|
+
4. Start the server and assign the permission `SetWebhook` to administrators who should be able to configure webhooks.
|
|
57
|
+
5. Go to `settings > webhook` to configure webhooks
|
|
58
|
+
|
|
59
|
+
### Custom transformers
|
|
60
|
+
|
|
61
|
+
Request transformers are used to create a custom POST body and custom headers for your outgoing webhooks. The example below stringifies the contents of a ProductEvent.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { Logger, ProductEvent } from '@vendure/core';
|
|
65
|
+
import { RequestTransformer } from 'vendure-plugin-webhook';
|
|
66
|
+
|
|
67
|
+
export const stringifyProductTransformer = new RequestTransformer({
|
|
68
|
+
name: 'Stringify Product events',
|
|
69
|
+
supportedEvents: [ProductEvent],
|
|
70
|
+
transform: (event, injector) => {
|
|
71
|
+
if (event instanceof ProductEvent) {
|
|
72
|
+
return {
|
|
73
|
+
body: JSON.stringify(event),
|
|
74
|
+
headers: {
|
|
75
|
+
'x-custom-header': 'custom-example-header',
|
|
76
|
+
'content-type': 'application/json',
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
} else {
|
|
80
|
+
throw Error(`This transformer is only for ProductEvents!`);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// In your vendure-config's plugin array:
|
|
86
|
+
WebhookPlugin.init({
|
|
87
|
+
events: [ProductEvent],
|
|
88
|
+
requestTransformers: [stringifyProductTransformer],
|
|
89
|
+
});
|
|
90
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const adminSchema: import("graphql").DocumentNode;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.adminSchema = void 0;
|
|
7
|
+
const graphql_tag_1 = __importDefault(require("graphql-tag"));
|
|
8
|
+
exports.adminSchema = (0, graphql_tag_1.default) `
|
|
9
|
+
input WebhookInput {
|
|
10
|
+
event: String!
|
|
11
|
+
url: String!
|
|
12
|
+
transformerName: String
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Webhook {
|
|
16
|
+
id: ID!
|
|
17
|
+
event: String!
|
|
18
|
+
url: String!
|
|
19
|
+
requestTransformer: WebhookRequestTransformer
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type WebhookRequestTransformer {
|
|
23
|
+
name: String!
|
|
24
|
+
supportedEvents: [String!]!
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
extend type Mutation {
|
|
28
|
+
"""
|
|
29
|
+
Set all webhooks for the current channel. This will overwrite any existing webhooks.
|
|
30
|
+
"""
|
|
31
|
+
setWebhooks(webhooks: [WebhookInput!]!): [Webhook!]!
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
extend type Query {
|
|
35
|
+
"""
|
|
36
|
+
Get all webhooks for the current channel
|
|
37
|
+
"""
|
|
38
|
+
webhooks: [Webhook!]!
|
|
39
|
+
"""
|
|
40
|
+
Get all available Vendure events that can be used to trigger webhooks
|
|
41
|
+
"""
|
|
42
|
+
availableWebhookEvents: [String!]!
|
|
43
|
+
"""
|
|
44
|
+
"
|
|
45
|
+
Get all available webhook request transformers
|
|
46
|
+
"""
|
|
47
|
+
availableWebhookRequestTransformers: [WebhookRequestTransformer!]!
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Injector, RequestContext, Type, VendureEvent } from '@vendure/core';
|
|
2
|
+
export type TransformFn<T extends EventWithContext> = (event: T, injector: Injector) => WebhookRequest | Promise<WebhookRequest>;
|
|
3
|
+
export type EventWithContext = VendureEvent & {
|
|
4
|
+
ctx: RequestContext;
|
|
5
|
+
};
|
|
6
|
+
export interface WebhookRequest {
|
|
7
|
+
body?: ArrayBuffer | ArrayBufferView | string;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export declare class RequestTransformer<T extends Array<Type<EventWithContext>>> {
|
|
11
|
+
private readonly options;
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly supportedEvents: T;
|
|
14
|
+
readonly transform: TransformFn<EventWithContext>;
|
|
15
|
+
constructor(options: {
|
|
16
|
+
name: string;
|
|
17
|
+
/**
|
|
18
|
+
* The events that this transformer supports
|
|
19
|
+
*/
|
|
20
|
+
supportedEvents: T;
|
|
21
|
+
transform: TransformFn<EventWithContext>;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RequestTransformer = void 0;
|
|
4
|
+
class RequestTransformer {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
this.name = options.name;
|
|
8
|
+
this.supportedEvents = options.supportedEvents;
|
|
9
|
+
this.transform = options.transform;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.RequestTransformer = RequestTransformer;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DeepPartial, VendureEntity } from '@vendure/core';
|
|
2
|
+
/**
|
|
3
|
+
* Each `Webhook` entity represents 1 webhook call for the specified
|
|
4
|
+
* Event with the specified Url and Transformer
|
|
5
|
+
*/
|
|
6
|
+
export declare class Webhook extends VendureEntity {
|
|
7
|
+
constructor(input?: DeepPartial<Webhook>);
|
|
8
|
+
channelId: string;
|
|
9
|
+
url: string;
|
|
10
|
+
event: string;
|
|
11
|
+
transformerName?: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.Webhook = void 0;
|
|
13
|
+
const typeorm_1 = require("typeorm");
|
|
14
|
+
const core_1 = require("@vendure/core");
|
|
15
|
+
/**
|
|
16
|
+
* Each `Webhook` entity represents 1 webhook call for the specified
|
|
17
|
+
* Event with the specified Url and Transformer
|
|
18
|
+
*/
|
|
19
|
+
let Webhook = class Webhook extends core_1.VendureEntity {
|
|
20
|
+
constructor(input) {
|
|
21
|
+
super(input);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
__decorate([
|
|
25
|
+
(0, typeorm_1.Column)(),
|
|
26
|
+
__metadata("design:type", String)
|
|
27
|
+
], Webhook.prototype, "channelId", void 0);
|
|
28
|
+
__decorate([
|
|
29
|
+
(0, typeorm_1.Column)(),
|
|
30
|
+
__metadata("design:type", String)
|
|
31
|
+
], Webhook.prototype, "url", void 0);
|
|
32
|
+
__decorate([
|
|
33
|
+
(0, typeorm_1.Column)(),
|
|
34
|
+
__metadata("design:type", String)
|
|
35
|
+
], Webhook.prototype, "event", void 0);
|
|
36
|
+
__decorate([
|
|
37
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
38
|
+
__metadata("design:type", String)
|
|
39
|
+
], Webhook.prototype, "transformerName", void 0);
|
|
40
|
+
Webhook = __decorate([
|
|
41
|
+
(0, typeorm_1.Entity)(),
|
|
42
|
+
__metadata("design:paramtypes", [Object])
|
|
43
|
+
], Webhook);
|
|
44
|
+
exports.Webhook = Webhook;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PermissionDefinition, RequestContext } from '@vendure/core';
|
|
2
|
+
import { WebhookService } from './webhook.service';
|
|
3
|
+
import { Webhook, WebhookInput, WebhookRequestTransformer } from '../generated/graphql-types';
|
|
4
|
+
import { Webhook as WebhookEntity } from './webhook.entity';
|
|
5
|
+
import { RequestTransformer } from './request-transformer';
|
|
6
|
+
export declare const webhookPermission: PermissionDefinition;
|
|
7
|
+
/**
|
|
8
|
+
* Graphql resolvers for retrieving and updating webhook for channel
|
|
9
|
+
*/
|
|
10
|
+
export declare class WebhookResolver {
|
|
11
|
+
private webhookService;
|
|
12
|
+
constructor(webhookService: WebhookService);
|
|
13
|
+
setWebhooks(ctx: RequestContext, webhooks: WebhookInput[]): Promise<Webhook[]>;
|
|
14
|
+
webhooks(ctx: RequestContext): Promise<Webhook[]>;
|
|
15
|
+
availableWebhookEvents(): Promise<string[]>;
|
|
16
|
+
availableWebhookRequestTransformers(): Promise<WebhookRequestTransformer[]>;
|
|
17
|
+
}
|
|
18
|
+
export declare class WebhookRequestTransformerResolver {
|
|
19
|
+
private webhookService;
|
|
20
|
+
constructor(webhookService: WebhookService);
|
|
21
|
+
/**
|
|
22
|
+
* Resolve `webhook.transformerName` to the actual RequestTransformer object
|
|
23
|
+
*/
|
|
24
|
+
requestTransformer(ctx: RequestContext, webhook: WebhookEntity): Promise<WebhookRequestTransformer | undefined>;
|
|
25
|
+
}
|
|
26
|
+
export declare function mapToGraphqlTransformer(transformer: RequestTransformer<any>): WebhookRequestTransformer;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.mapToGraphqlTransformer = exports.WebhookRequestTransformerResolver = exports.WebhookResolver = exports.webhookPermission = void 0;
|
|
16
|
+
const graphql_1 = require("@nestjs/graphql");
|
|
17
|
+
const core_1 = require("@vendure/core");
|
|
18
|
+
const webhook_service_1 = require("./webhook.service");
|
|
19
|
+
const webhook_entity_1 = require("./webhook.entity");
|
|
20
|
+
// Permission needs to be defined first
|
|
21
|
+
exports.webhookPermission = new core_1.PermissionDefinition({
|
|
22
|
+
name: 'SetWebhook',
|
|
23
|
+
description: 'Allows setting a webhook URL',
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* Graphql resolvers for retrieving and updating webhook for channel
|
|
27
|
+
*/
|
|
28
|
+
let WebhookResolver = class WebhookResolver {
|
|
29
|
+
constructor(webhookService) {
|
|
30
|
+
this.webhookService = webhookService;
|
|
31
|
+
}
|
|
32
|
+
async setWebhooks(ctx, webhooks) {
|
|
33
|
+
return this.webhookService.saveWebhooks(ctx, webhooks);
|
|
34
|
+
}
|
|
35
|
+
async webhooks(ctx) {
|
|
36
|
+
return this.webhookService.getAllWebhooks(ctx);
|
|
37
|
+
}
|
|
38
|
+
async availableWebhookEvents() {
|
|
39
|
+
return this.webhookService.getAvailableEvents();
|
|
40
|
+
}
|
|
41
|
+
async availableWebhookRequestTransformers() {
|
|
42
|
+
const transformers = this.webhookService.getAvailableTransformers();
|
|
43
|
+
return transformers.map(mapToGraphqlTransformer);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
__decorate([
|
|
47
|
+
(0, graphql_1.Mutation)(),
|
|
48
|
+
(0, core_1.Allow)(exports.webhookPermission.Permission),
|
|
49
|
+
__param(0, (0, core_1.Ctx)()),
|
|
50
|
+
__param(1, (0, graphql_1.Args)('webhooks')),
|
|
51
|
+
__metadata("design:type", Function),
|
|
52
|
+
__metadata("design:paramtypes", [core_1.RequestContext, Array]),
|
|
53
|
+
__metadata("design:returntype", Promise)
|
|
54
|
+
], WebhookResolver.prototype, "setWebhooks", null);
|
|
55
|
+
__decorate([
|
|
56
|
+
(0, graphql_1.Query)(),
|
|
57
|
+
(0, core_1.Allow)(exports.webhookPermission.Permission),
|
|
58
|
+
__param(0, (0, core_1.Ctx)()),
|
|
59
|
+
__metadata("design:type", Function),
|
|
60
|
+
__metadata("design:paramtypes", [core_1.RequestContext]),
|
|
61
|
+
__metadata("design:returntype", Promise)
|
|
62
|
+
], WebhookResolver.prototype, "webhooks", null);
|
|
63
|
+
__decorate([
|
|
64
|
+
(0, graphql_1.Query)(),
|
|
65
|
+
(0, core_1.Allow)(exports.webhookPermission.Permission),
|
|
66
|
+
__metadata("design:type", Function),
|
|
67
|
+
__metadata("design:paramtypes", []),
|
|
68
|
+
__metadata("design:returntype", Promise)
|
|
69
|
+
], WebhookResolver.prototype, "availableWebhookEvents", null);
|
|
70
|
+
__decorate([
|
|
71
|
+
(0, graphql_1.Query)(),
|
|
72
|
+
(0, core_1.Allow)(exports.webhookPermission.Permission),
|
|
73
|
+
__metadata("design:type", Function),
|
|
74
|
+
__metadata("design:paramtypes", []),
|
|
75
|
+
__metadata("design:returntype", Promise)
|
|
76
|
+
], WebhookResolver.prototype, "availableWebhookRequestTransformers", null);
|
|
77
|
+
WebhookResolver = __decorate([
|
|
78
|
+
(0, graphql_1.Resolver)(),
|
|
79
|
+
__metadata("design:paramtypes", [webhook_service_1.WebhookService])
|
|
80
|
+
], WebhookResolver);
|
|
81
|
+
exports.WebhookResolver = WebhookResolver;
|
|
82
|
+
let WebhookRequestTransformerResolver = class WebhookRequestTransformerResolver {
|
|
83
|
+
constructor(webhookService) {
|
|
84
|
+
this.webhookService = webhookService;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve `webhook.transformerName` to the actual RequestTransformer object
|
|
88
|
+
*/
|
|
89
|
+
async requestTransformer(ctx, webhook) {
|
|
90
|
+
if (!webhook.transformerName) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const transformers = this.webhookService.getAvailableTransformers();
|
|
94
|
+
const transformer = transformers.find((t) => t.name === webhook.transformerName);
|
|
95
|
+
if (!transformer) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
return mapToGraphqlTransformer(transformer);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
__decorate([
|
|
102
|
+
(0, graphql_1.ResolveField)(),
|
|
103
|
+
__param(0, (0, core_1.Ctx)()),
|
|
104
|
+
__param(1, (0, graphql_1.Parent)()),
|
|
105
|
+
__metadata("design:type", Function),
|
|
106
|
+
__metadata("design:paramtypes", [core_1.RequestContext,
|
|
107
|
+
webhook_entity_1.Webhook]),
|
|
108
|
+
__metadata("design:returntype", Promise)
|
|
109
|
+
], WebhookRequestTransformerResolver.prototype, "requestTransformer", null);
|
|
110
|
+
WebhookRequestTransformerResolver = __decorate([
|
|
111
|
+
(0, graphql_1.Resolver)('Webhook'),
|
|
112
|
+
__metadata("design:paramtypes", [webhook_service_1.WebhookService])
|
|
113
|
+
], WebhookRequestTransformerResolver);
|
|
114
|
+
exports.WebhookRequestTransformerResolver = WebhookRequestTransformerResolver;
|
|
115
|
+
function mapToGraphqlTransformer(transformer) {
|
|
116
|
+
return {
|
|
117
|
+
name: transformer.name,
|
|
118
|
+
supportedEvents: transformer.supportedEvents.map((event) => event.name),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
exports.mapToGraphqlTransformer = mapToGraphqlTransformer;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { OnApplicationBootstrap } from '@nestjs/common';
|
|
2
|
+
import { ModuleRef } from '@nestjs/core';
|
|
3
|
+
import { EventBus, ID, RequestContext, TransactionalConnection } from '@vendure/core';
|
|
4
|
+
import { Webhook } from './webhook.entity';
|
|
5
|
+
import { EventWithContext, RequestTransformer } from './request-transformer';
|
|
6
|
+
import { WebhookPluginOptions } from '../webhook.plugin';
|
|
7
|
+
import { WebhookInput } from '../generated/graphql-types';
|
|
8
|
+
/**
|
|
9
|
+
* Service for updating and retrieving webhooks from db
|
|
10
|
+
*/
|
|
11
|
+
export declare class WebhookService implements OnApplicationBootstrap {
|
|
12
|
+
private eventBus;
|
|
13
|
+
private connection;
|
|
14
|
+
private moduleRef;
|
|
15
|
+
private options;
|
|
16
|
+
/**
|
|
17
|
+
* A queue of unique webhooks to call. This is used to prevent
|
|
18
|
+
* multiple calls to the same webhook for the same
|
|
19
|
+
* event within the configured `delay` time
|
|
20
|
+
*/
|
|
21
|
+
webhookQueue: Map<ID, Webhook>;
|
|
22
|
+
constructor(eventBus: EventBus, connection: TransactionalConnection, moduleRef: ModuleRef, options: WebhookPluginOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Subscribe to events specified in config
|
|
25
|
+
*/
|
|
26
|
+
onApplicationBootstrap(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Get the plugin's configured Events.
|
|
29
|
+
*/
|
|
30
|
+
getAvailableEvents(): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Get the plugin's configured Request Transformers.
|
|
33
|
+
*/
|
|
34
|
+
getAvailableTransformers(): RequestTransformer<any>[];
|
|
35
|
+
/**
|
|
36
|
+
* Get all configured webhooks for current channel
|
|
37
|
+
*/
|
|
38
|
+
getAllWebhooks(ctx: RequestContext): Promise<Webhook[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Get configured webhooks for given Event
|
|
41
|
+
*/
|
|
42
|
+
getWebhooksForEvent<T extends EventWithContext>(event: T): Promise<Webhook[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Save set of webhooks for current channel.
|
|
45
|
+
* Overrides any previously set hooks
|
|
46
|
+
*/
|
|
47
|
+
saveWebhooks(ctx: RequestContext, inputs: WebhookInput[]): Promise<Webhook[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Push the webhooks for the given Event to the queue,
|
|
50
|
+
* so they can be processed in batch
|
|
51
|
+
*/
|
|
52
|
+
addWebhookToQueue(event: EventWithContext): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Process all webhooks currently in the queue
|
|
55
|
+
*/
|
|
56
|
+
processQueue(event: EventWithContext): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Call the actual webhook with the configured Transformer for given Event
|
|
59
|
+
*/
|
|
60
|
+
callWebhook(webhook: Webhook, event: EventWithContext): Promise<void>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.WebhookService = void 0;
|
|
19
|
+
const common_1 = require("@nestjs/common");
|
|
20
|
+
const core_1 = require("@nestjs/core");
|
|
21
|
+
const core_2 = require("@vendure/core");
|
|
22
|
+
const webhook_entity_1 = require("./webhook.entity");
|
|
23
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
24
|
+
const constants_1 = require("../constants");
|
|
25
|
+
/**
|
|
26
|
+
* Service for updating and retrieving webhooks from db
|
|
27
|
+
*/
|
|
28
|
+
let WebhookService = class WebhookService {
|
|
29
|
+
constructor(eventBus, connection, moduleRef, options) {
|
|
30
|
+
this.eventBus = eventBus;
|
|
31
|
+
this.connection = connection;
|
|
32
|
+
this.moduleRef = moduleRef;
|
|
33
|
+
this.options = options;
|
|
34
|
+
/**
|
|
35
|
+
* A queue of unique webhooks to call. This is used to prevent
|
|
36
|
+
* multiple calls to the same webhook for the same
|
|
37
|
+
* event within the configured `delay` time
|
|
38
|
+
*/
|
|
39
|
+
this.webhookQueue = new Map();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Subscribe to events specified in config
|
|
43
|
+
*/
|
|
44
|
+
async onApplicationBootstrap() {
|
|
45
|
+
if (!this.options.events || this.options.events.length === 0) {
|
|
46
|
+
throw Error(`Please specify VendureEvents with Webhook.init() to use this plugin.`);
|
|
47
|
+
}
|
|
48
|
+
if (this.options.disabled) {
|
|
49
|
+
core_2.Logger.info(`Webhook plugin disabled,not listening for events`, constants_1.loggerCtx);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Subscribe to all configured events
|
|
53
|
+
this.options.events.forEach((configuredEvent) => {
|
|
54
|
+
this.eventBus.ofType(configuredEvent).subscribe(async (event) => {
|
|
55
|
+
try {
|
|
56
|
+
await this.addWebhookToQueue(event);
|
|
57
|
+
// Start processing after the given delay, because
|
|
58
|
+
// we might get multiple of the same events within the specified delay
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, this.options.delay));
|
|
60
|
+
await this.processQueue(event);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
core_2.Logger.error(`Failed to call webhook for event ${event.constructor.name} for channel ${event.ctx.channelId}: ${e}`, constants_1.loggerCtx);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
core_2.Logger.info(`Listening for ${configuredEvent.name}`, constants_1.loggerCtx);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get the plugin's configured Events.
|
|
71
|
+
*/
|
|
72
|
+
getAvailableEvents() {
|
|
73
|
+
return this.options.events.map((eventType) => eventType.name);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Get the plugin's configured Request Transformers.
|
|
77
|
+
*/
|
|
78
|
+
getAvailableTransformers() {
|
|
79
|
+
return this.options.requestTransformers || [];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get all configured webhooks for current channel
|
|
83
|
+
*/
|
|
84
|
+
async getAllWebhooks(ctx) {
|
|
85
|
+
return this.connection
|
|
86
|
+
.getRepository(ctx, webhook_entity_1.Webhook)
|
|
87
|
+
.find({ where: { channelId: String(ctx.channelId) } });
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get configured webhooks for given Event
|
|
91
|
+
*/
|
|
92
|
+
async getWebhooksForEvent(event) {
|
|
93
|
+
const eventName = event.constructor.name;
|
|
94
|
+
return this.connection.getRepository(event.ctx, webhook_entity_1.Webhook).find({
|
|
95
|
+
where: { channelId: String(event.ctx.channelId), event: eventName },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Save set of webhooks for current channel.
|
|
100
|
+
* Overrides any previously set hooks
|
|
101
|
+
*/
|
|
102
|
+
async saveWebhooks(ctx, inputs) {
|
|
103
|
+
const repository = this.connection.getRepository(ctx, webhook_entity_1.Webhook);
|
|
104
|
+
// Delete all current hooks
|
|
105
|
+
await repository.delete({ channelId: String(ctx.channelId) });
|
|
106
|
+
// Recreate all hooks
|
|
107
|
+
const webhooks = inputs.map((input) => ({
|
|
108
|
+
channelId: String(ctx.channelId),
|
|
109
|
+
url: input.url,
|
|
110
|
+
event: input.event,
|
|
111
|
+
transformerName: input.transformerName ?? undefined,
|
|
112
|
+
}));
|
|
113
|
+
await repository.save(webhooks);
|
|
114
|
+
return this.getAllWebhooks(ctx);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Push the webhooks for the given Event to the queue,
|
|
118
|
+
* so they can be processed in batch
|
|
119
|
+
*/
|
|
120
|
+
async addWebhookToQueue(event) {
|
|
121
|
+
const webhooks = await this.getWebhooksForEvent(event);
|
|
122
|
+
webhooks.map((webhook) => {
|
|
123
|
+
this.webhookQueue.set(webhook.id, webhook);
|
|
124
|
+
});
|
|
125
|
+
if (webhooks.length > 0) {
|
|
126
|
+
core_2.Logger.info(`Added ${webhooks.length} webhooks to the webhook queue for ${event.constructor.name}`, constants_1.loggerCtx);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Process all webhooks currently in the queue
|
|
131
|
+
*/
|
|
132
|
+
async processQueue(event) {
|
|
133
|
+
// Check if queue already handled
|
|
134
|
+
if (this.webhookQueue.size === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// Copy queue, and empty original
|
|
138
|
+
const webhooks = [];
|
|
139
|
+
this.webhookQueue.forEach((webhook) => webhooks.push(webhook));
|
|
140
|
+
this.webhookQueue.clear();
|
|
141
|
+
// Start calling the webhooks
|
|
142
|
+
await Promise.all(webhooks.map(async (webhook) => {
|
|
143
|
+
try {
|
|
144
|
+
await this.callWebhook(webhook, event);
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
core_2.Logger.error(`Failed to call webhook for event ${webhook.event} channel ${webhook.channelId}: ${e}`, constants_1.loggerCtx);
|
|
148
|
+
}
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Call the actual webhook with the configured Transformer for given Event
|
|
153
|
+
*/
|
|
154
|
+
async callWebhook(webhook, event) {
|
|
155
|
+
if (!webhook.transformerName) {
|
|
156
|
+
// No transformer, just call webhook without body
|
|
157
|
+
await (0, node_fetch_1.default)(webhook.url, { method: 'POST' });
|
|
158
|
+
return core_2.Logger.info(`Successfully triggered webhook for event ${webhook.event} for channel ${webhook.channelId} without transformer`, constants_1.loggerCtx);
|
|
159
|
+
}
|
|
160
|
+
// Have the configured transformer construct the request
|
|
161
|
+
const transformer = this.getAvailableTransformers().find((transformer) => transformer.name === webhook.transformerName);
|
|
162
|
+
if (!transformer) {
|
|
163
|
+
throw Error(`Could not find transformer ${webhook.transformerName}`);
|
|
164
|
+
}
|
|
165
|
+
const request = await transformer.transform(event, new core_2.Injector(this.moduleRef));
|
|
166
|
+
// Call the webhook with the constructed request
|
|
167
|
+
await (0, node_fetch_1.default)(webhook.url, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: request.headers,
|
|
170
|
+
body: request.body,
|
|
171
|
+
});
|
|
172
|
+
core_2.Logger.info(`Successfully triggered webhook for event ${event.constructor.name} for channel ${webhook.channelId} with transformer "${webhook.transformerName}"`, constants_1.loggerCtx);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
WebhookService = __decorate([
|
|
176
|
+
(0, common_1.Injectable)(),
|
|
177
|
+
__param(3, (0, common_1.Inject)(constants_1.PLUGIN_INIT_OPTIONS)),
|
|
178
|
+
__metadata("design:paramtypes", [core_2.EventBus,
|
|
179
|
+
core_2.TransactionalConnection,
|
|
180
|
+
core_1.ModuleRef, Object])
|
|
181
|
+
], WebhookService);
|
|
182
|
+
exports.WebhookService = WebhookService;
|