@pinelab/vendure-plugin-webhook 1.5.2 → 1.6.0
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/CHANGELOG.md +6 -0
- package/dist/api/api-extension.js +2 -0
- package/dist/api/webhook.entity.d.ts +5 -0
- package/dist/api/webhook.entity.js +9 -0
- package/dist/api/webhook.resolver.d.ts +2 -2
- package/dist/api/webhook.resolver.js +15 -1
- package/dist/api/webhook.service.js +13 -4
- package/dist/generated/graphql-types.d.ts +4 -0
- package/dist/generated/graphql-types.js +2 -0
- package/dist/ui/graphql-types.ts +6 -2
- package/dist/ui/queries.ts +2 -0
- package/dist/ui/webhook.component.html +10 -2
- package/dist/ui/webhook.component.ts +13 -8
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# 1.6.0 (2026-01-20)
|
|
2
|
+
|
|
3
|
+
- Allow creation of channel-agnostic webhooks that trigger for all channels.
|
|
4
|
+
- Channel-agnostic webhooks are available to users with the "CreateChannel" permission to prevent leaking data of other channels.
|
|
5
|
+
- BREAKING: requires a DB migration to add the new `channelAgnostic` column.
|
|
6
|
+
|
|
1
7
|
# 1.5.2 (2025-11-13)
|
|
2
8
|
|
|
3
9
|
- Documentation update
|
|
@@ -10,6 +10,7 @@ exports.adminSchema = (0, graphql_tag_1.default) `
|
|
|
10
10
|
event: String!
|
|
11
11
|
url: String!
|
|
12
12
|
transformerName: String
|
|
13
|
+
channelAgnostic: Boolean
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
type Webhook {
|
|
@@ -17,6 +18,7 @@ exports.adminSchema = (0, graphql_tag_1.default) `
|
|
|
17
18
|
event: String!
|
|
18
19
|
url: String!
|
|
19
20
|
requestTransformer: WebhookRequestTransformer
|
|
21
|
+
channelAgnostic: Boolean!
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
type WebhookRequestTransformer {
|
|
@@ -9,4 +9,9 @@ export declare class Webhook extends VendureEntity {
|
|
|
9
9
|
url: string;
|
|
10
10
|
event: string;
|
|
11
11
|
transformerName?: string;
|
|
12
|
+
/**
|
|
13
|
+
* When set to true, this webhook is available for all channels.
|
|
14
|
+
* Otherwise, it is only available for the channel specified in the `channelId` column.
|
|
15
|
+
*/
|
|
16
|
+
channelAgnostic: boolean;
|
|
12
17
|
}
|
|
@@ -19,6 +19,11 @@ const core_1 = require("@vendure/core");
|
|
|
19
19
|
let Webhook = class Webhook extends core_1.VendureEntity {
|
|
20
20
|
constructor(input) {
|
|
21
21
|
super(input);
|
|
22
|
+
/**
|
|
23
|
+
* When set to true, this webhook is available for all channels.
|
|
24
|
+
* Otherwise, it is only available for the channel specified in the `channelId` column.
|
|
25
|
+
*/
|
|
26
|
+
this.channelAgnostic = false;
|
|
22
27
|
}
|
|
23
28
|
};
|
|
24
29
|
exports.Webhook = Webhook;
|
|
@@ -38,6 +43,10 @@ __decorate([
|
|
|
38
43
|
(0, typeorm_1.Column)({ nullable: true }),
|
|
39
44
|
__metadata("design:type", String)
|
|
40
45
|
], Webhook.prototype, "transformerName", void 0);
|
|
46
|
+
__decorate([
|
|
47
|
+
(0, typeorm_1.Column)({ default: false }),
|
|
48
|
+
__metadata("design:type", Boolean)
|
|
49
|
+
], Webhook.prototype, "channelAgnostic", void 0);
|
|
41
50
|
exports.Webhook = Webhook = __decorate([
|
|
42
51
|
(0, typeorm_1.Entity)(),
|
|
43
52
|
__metadata("design:paramtypes", [Object])
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PermissionDefinition, RequestContext } from '@vendure/core';
|
|
2
|
-
import { WebhookService } from './webhook.service';
|
|
3
2
|
import { Webhook, WebhookInput, WebhookRequestTransformer } from '../generated/graphql-types';
|
|
4
|
-
import { Webhook as WebhookEntity } from './webhook.entity';
|
|
5
3
|
import { RequestTransformer } from './request-transformer';
|
|
4
|
+
import { Webhook as WebhookEntity } from './webhook.entity';
|
|
5
|
+
import { WebhookService } from './webhook.service';
|
|
6
6
|
export declare const webhookPermission: PermissionDefinition;
|
|
7
7
|
/**
|
|
8
8
|
* Graphql resolvers for retrieving and updating webhook for channel
|
|
@@ -16,8 +16,8 @@ exports.WebhookRequestTransformerResolver = exports.WebhookResolver = exports.we
|
|
|
16
16
|
exports.mapToGraphqlTransformer = mapToGraphqlTransformer;
|
|
17
17
|
const graphql_1 = require("@nestjs/graphql");
|
|
18
18
|
const core_1 = require("@vendure/core");
|
|
19
|
-
const webhook_service_1 = require("./webhook.service");
|
|
20
19
|
const webhook_entity_1 = require("./webhook.entity");
|
|
20
|
+
const webhook_service_1 = require("./webhook.service");
|
|
21
21
|
// Permission needs to be defined first
|
|
22
22
|
exports.webhookPermission = new core_1.PermissionDefinition({
|
|
23
23
|
name: 'SetWebhook',
|
|
@@ -31,6 +31,20 @@ let WebhookResolver = class WebhookResolver {
|
|
|
31
31
|
this.webhookService = webhookService;
|
|
32
32
|
}
|
|
33
33
|
async setWebhooks(ctx, webhooks) {
|
|
34
|
+
const currentWebhooks = await this.webhookService.getAllWebhooks(ctx);
|
|
35
|
+
const newWebhooks = webhooks.filter((webhook) => !currentWebhooks.some((w) => w.channelAgnostic &&
|
|
36
|
+
w.event === webhook.event &&
|
|
37
|
+
w.url === webhook.url));
|
|
38
|
+
// Check if any of the new webhooks are being set as channel-agnostic
|
|
39
|
+
const hasChannelAgnosticWebhook = newWebhooks.some((webhook) => webhook.channelAgnostic === true);
|
|
40
|
+
if (hasChannelAgnosticWebhook) {
|
|
41
|
+
// Check if the current user has the "CreateChannel" permission
|
|
42
|
+
const channelPermissions = ctx.session?.user?.channelPermissions || [];
|
|
43
|
+
const hasPermission = channelPermissions.some((perm) => perm.permissions?.includes(core_1.Permission.CreateChannel));
|
|
44
|
+
if (!hasPermission) {
|
|
45
|
+
throw new core_1.ForbiddenError();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
34
48
|
return this.webhookService.saveWebhooks(ctx, webhooks);
|
|
35
49
|
}
|
|
36
50
|
async webhooks(ctx) {
|
|
@@ -82,9 +82,12 @@ let WebhookService = class WebhookService {
|
|
|
82
82
|
* Get all configured webhooks for current channel
|
|
83
83
|
*/
|
|
84
84
|
async getAllWebhooks(ctx) {
|
|
85
|
-
return this.connection
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
return this.connection.getRepository(ctx, webhook_entity_1.Webhook).find({
|
|
86
|
+
where: [
|
|
87
|
+
// Get channel-specific webhooks only
|
|
88
|
+
{ channelId: String(ctx.channelId) },
|
|
89
|
+
],
|
|
90
|
+
});
|
|
88
91
|
}
|
|
89
92
|
/**
|
|
90
93
|
* Get configured webhooks for given Event
|
|
@@ -92,7 +95,12 @@ let WebhookService = class WebhookService {
|
|
|
92
95
|
async getWebhooksForEvent(event) {
|
|
93
96
|
const eventName = event.constructor.name;
|
|
94
97
|
return this.connection.getRepository(event.ctx, webhook_entity_1.Webhook).find({
|
|
95
|
-
where:
|
|
98
|
+
where: [
|
|
99
|
+
// Get channel-specific webhooks
|
|
100
|
+
{ channelId: String(event.ctx.channelId), event: eventName },
|
|
101
|
+
// Or, get channel-agnostic webhooks
|
|
102
|
+
{ channelAgnostic: true, event: eventName },
|
|
103
|
+
],
|
|
96
104
|
});
|
|
97
105
|
}
|
|
98
106
|
/**
|
|
@@ -109,6 +117,7 @@ let WebhookService = class WebhookService {
|
|
|
109
117
|
url: input.url,
|
|
110
118
|
event: input.event,
|
|
111
119
|
transformerName: input.transformerName ?? undefined,
|
|
120
|
+
channelAgnostic: input.channelAgnostic ?? false,
|
|
112
121
|
}));
|
|
113
122
|
await repository.save(webhooks);
|
|
114
123
|
return this.getAllWebhooks(ctx);
|
|
@@ -41,12 +41,14 @@ export type Query = {
|
|
|
41
41
|
};
|
|
42
42
|
export type Webhook = {
|
|
43
43
|
__typename?: 'Webhook';
|
|
44
|
+
channelAgnostic: Scalars['Boolean'];
|
|
44
45
|
event: Scalars['String'];
|
|
45
46
|
id: Scalars['ID'];
|
|
46
47
|
requestTransformer?: Maybe<WebhookRequestTransformer>;
|
|
47
48
|
url: Scalars['String'];
|
|
48
49
|
};
|
|
49
50
|
export type WebhookInput = {
|
|
51
|
+
channelAgnostic?: InputMaybe<Scalars['Boolean']>;
|
|
50
52
|
event: Scalars['String'];
|
|
51
53
|
transformerName?: InputMaybe<Scalars['String']>;
|
|
52
54
|
url: Scalars['String'];
|
|
@@ -66,6 +68,7 @@ export type SetWebhooksMutation = {
|
|
|
66
68
|
id: number | string;
|
|
67
69
|
event: string;
|
|
68
70
|
url: string;
|
|
71
|
+
channelAgnostic: boolean;
|
|
69
72
|
requestTransformer?: {
|
|
70
73
|
__typename?: 'WebhookRequestTransformer';
|
|
71
74
|
name: string;
|
|
@@ -83,6 +86,7 @@ export type WebhooksQuery = {
|
|
|
83
86
|
id: number | string;
|
|
84
87
|
event: string;
|
|
85
88
|
url: string;
|
|
89
|
+
channelAgnostic: boolean;
|
|
86
90
|
requestTransformer?: {
|
|
87
91
|
__typename?: 'WebhookRequestTransformer';
|
|
88
92
|
name: string;
|
|
@@ -15,6 +15,7 @@ exports.SetWebhooks = (0, graphql_tag_1.default) `
|
|
|
15
15
|
supportedEvents
|
|
16
16
|
}
|
|
17
17
|
url
|
|
18
|
+
channelAgnostic
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
`;
|
|
@@ -28,6 +29,7 @@ exports.Webhooks = (0, graphql_tag_1.default) `
|
|
|
28
29
|
supportedEvents
|
|
29
30
|
}
|
|
30
31
|
url
|
|
32
|
+
channelAgnostic
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
`;
|
package/dist/ui/graphql-types.ts
CHANGED
|
@@ -39,6 +39,7 @@ export type Query = {
|
|
|
39
39
|
|
|
40
40
|
export type Webhook = {
|
|
41
41
|
__typename?: 'Webhook';
|
|
42
|
+
channelAgnostic: Scalars['Boolean'];
|
|
42
43
|
event: Scalars['String'];
|
|
43
44
|
id: Scalars['ID'];
|
|
44
45
|
requestTransformer?: Maybe<WebhookRequestTransformer>;
|
|
@@ -46,6 +47,7 @@ export type Webhook = {
|
|
|
46
47
|
};
|
|
47
48
|
|
|
48
49
|
export type WebhookInput = {
|
|
50
|
+
channelAgnostic?: InputMaybe<Scalars['Boolean']>;
|
|
49
51
|
event: Scalars['String'];
|
|
50
52
|
transformerName?: InputMaybe<Scalars['String']>;
|
|
51
53
|
url: Scalars['String'];
|
|
@@ -62,12 +64,12 @@ export type SetWebhooksMutationVariables = Exact<{
|
|
|
62
64
|
}>;
|
|
63
65
|
|
|
64
66
|
|
|
65
|
-
export type SetWebhooksMutation = { __typename?: 'Mutation', setWebhooks: Array<{ __typename?: 'Webhook', id: number | string, event: string, url: string, requestTransformer?: { __typename?: 'WebhookRequestTransformer', name: string, supportedEvents: Array<string> } | null }> };
|
|
67
|
+
export type SetWebhooksMutation = { __typename?: 'Mutation', setWebhooks: Array<{ __typename?: 'Webhook', id: number | string, event: string, url: string, channelAgnostic: boolean, requestTransformer?: { __typename?: 'WebhookRequestTransformer', name: string, supportedEvents: Array<string> } | null }> };
|
|
66
68
|
|
|
67
69
|
export type WebhooksQueryVariables = Exact<{ [key: string]: never; }>;
|
|
68
70
|
|
|
69
71
|
|
|
70
|
-
export type WebhooksQuery = { __typename?: 'Query', webhooks: Array<{ __typename?: 'Webhook', id: number | string, event: string, url: string, requestTransformer?: { __typename?: 'WebhookRequestTransformer', name: string, supportedEvents: Array<string> } | null }> };
|
|
72
|
+
export type WebhooksQuery = { __typename?: 'Query', webhooks: Array<{ __typename?: 'Webhook', id: number | string, event: string, url: string, channelAgnostic: boolean, requestTransformer?: { __typename?: 'WebhookRequestTransformer', name: string, supportedEvents: Array<string> } | null }> };
|
|
71
73
|
|
|
72
74
|
export type AvailableWebhookEventsQueryVariables = Exact<{ [key: string]: never; }>;
|
|
73
75
|
|
|
@@ -90,6 +92,7 @@ export const SetWebhooks = gql`
|
|
|
90
92
|
supportedEvents
|
|
91
93
|
}
|
|
92
94
|
url
|
|
95
|
+
channelAgnostic
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
98
|
`;
|
|
@@ -103,6 +106,7 @@ export const Webhooks = gql`
|
|
|
103
106
|
supportedEvents
|
|
104
107
|
}
|
|
105
108
|
url
|
|
109
|
+
channelAgnostic
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
`;
|
package/dist/ui/queries.ts
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
<select clrSelect [(ngModel)]="eventName">
|
|
40
40
|
<option value="{{ undefined }}">--select---</option>
|
|
41
41
|
<option
|
|
42
|
-
*ngFor="let event of
|
|
42
|
+
*ngFor="let event of filteredWebhookEvents"
|
|
43
43
|
[value]="event"
|
|
44
44
|
>
|
|
45
45
|
{{ event }}
|
|
@@ -55,6 +55,10 @@
|
|
|
55
55
|
[(ngModel)]="url"
|
|
56
56
|
/>
|
|
57
57
|
</li>
|
|
58
|
+
<li class="list-group-item" *vdrIfPermissions="['CreateChannel']">
|
|
59
|
+
<h4 class="card-title">Trigger for all channels</h4>
|
|
60
|
+
<input type="checkbox" [(ngModel)]="channelAgnostic" />
|
|
61
|
+
</li>
|
|
58
62
|
</ul>
|
|
59
63
|
</div>
|
|
60
64
|
</div>
|
|
@@ -83,6 +87,7 @@
|
|
|
83
87
|
<vdr-dt-column>Event</vdr-dt-column>
|
|
84
88
|
<vdr-dt-column>Request Transformer</vdr-dt-column>
|
|
85
89
|
<vdr-dt-column>URL</vdr-dt-column>
|
|
90
|
+
<vdr-dt-column>Trigger for all channels</vdr-dt-column>
|
|
86
91
|
<vdr-dt-column></vdr-dt-column>
|
|
87
92
|
|
|
88
93
|
<ng-template let-webhook="item">
|
|
@@ -95,6 +100,9 @@
|
|
|
95
100
|
<td class="left align-middle">
|
|
96
101
|
{{ webhook.url }}
|
|
97
102
|
</td>
|
|
103
|
+
<td class="left align-middle">
|
|
104
|
+
{{ webhook.channelAgnostic ? 'Yes' : 'No' }}
|
|
105
|
+
</td>
|
|
98
106
|
<td>
|
|
99
107
|
<vdr-dropdown>
|
|
100
108
|
<button type="button" class="btn btn-link btn-sm" vdrDropdownTrigger>
|
|
@@ -105,7 +113,7 @@
|
|
|
105
113
|
<button
|
|
106
114
|
type="button"
|
|
107
115
|
class="delete-button"
|
|
108
|
-
(click)="
|
|
116
|
+
(click)="deleteWebhook(webhook.id)"
|
|
109
117
|
vdrDropdownItem
|
|
110
118
|
>
|
|
111
119
|
<clr-icon shape="trash" class="is-danger"></clr-icon>
|
|
@@ -25,9 +25,10 @@ export class WebhookComponent implements OnInit {
|
|
|
25
25
|
eventName: string | undefined;
|
|
26
26
|
requestTransformerName: string | undefined;
|
|
27
27
|
url: string | undefined;
|
|
28
|
+
channelAgnostic: boolean = false;
|
|
28
29
|
showMessage = false;
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
availableWebhookEvents: string[] = [];
|
|
31
|
+
filteredWebhookEvents: string[] = [];
|
|
31
32
|
avaiabelWebhookRequestTransformers: WebhookRequestTransformer[] = [];
|
|
32
33
|
|
|
33
34
|
constructor(
|
|
@@ -41,7 +42,7 @@ export class WebhookComponent implements OnInit {
|
|
|
41
42
|
this.dataService
|
|
42
43
|
.query(getAvailableWebhookEventsQuery)
|
|
43
44
|
.single$.subscribe((e: any) => {
|
|
44
|
-
this.
|
|
45
|
+
this.filteredWebhookEvents = this.availableWebhookEvents =
|
|
45
46
|
e.availableWebhookEvents;
|
|
46
47
|
});
|
|
47
48
|
this.dataService.query(getWebhooksQuery).single$.subscribe((s: any) => {
|
|
@@ -69,6 +70,7 @@ export class WebhookComponent implements OnInit {
|
|
|
69
70
|
this.eventName = '';
|
|
70
71
|
this.requestTransformerName = '';
|
|
71
72
|
this.url = '';
|
|
73
|
+
this.channelAgnostic = false;
|
|
72
74
|
this.changeDetector.detectChanges();
|
|
73
75
|
}
|
|
74
76
|
|
|
@@ -87,12 +89,14 @@ export class WebhookComponent implements OnInit {
|
|
|
87
89
|
event: w.event,
|
|
88
90
|
transformerName: w.requestTransformer?.name,
|
|
89
91
|
url: w.url,
|
|
92
|
+
channelAgnostic: w.channelAgnostic,
|
|
90
93
|
};
|
|
91
94
|
}),
|
|
92
95
|
{
|
|
93
96
|
event: this.eventName,
|
|
94
97
|
transformerName: this.requestTransformerName,
|
|
95
98
|
url: this.url,
|
|
99
|
+
channelAgnostic: this.channelAgnostic,
|
|
96
100
|
},
|
|
97
101
|
],
|
|
98
102
|
})
|
|
@@ -107,7 +111,7 @@ export class WebhookComponent implements OnInit {
|
|
|
107
111
|
}
|
|
108
112
|
}
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
deleteWebhook(id: number) {
|
|
111
115
|
this.webhooks = this.webhooks.filter((w: Webhook) => w.id != id);
|
|
112
116
|
this.changeDetector.detectChanges();
|
|
113
117
|
this.dataService
|
|
@@ -118,6 +122,7 @@ export class WebhookComponent implements OnInit {
|
|
|
118
122
|
event: w.event,
|
|
119
123
|
transformerName: w.requestTransformer?.name,
|
|
120
124
|
url: w.url,
|
|
125
|
+
channelAgnostic: w.channelAgnostic,
|
|
121
126
|
};
|
|
122
127
|
}),
|
|
123
128
|
],
|
|
@@ -130,19 +135,19 @@ export class WebhookComponent implements OnInit {
|
|
|
130
135
|
|
|
131
136
|
requestTransformerSelected(setRequestTransformerName?: string) {
|
|
132
137
|
if (setRequestTransformerName) {
|
|
133
|
-
this.
|
|
138
|
+
this.filteredWebhookEvents =
|
|
134
139
|
this.avaiabelWebhookRequestTransformers.find(
|
|
135
140
|
(v) => v.name === this.requestTransformerName
|
|
136
141
|
)?.supportedEvents ?? [];
|
|
137
142
|
} else if (this.requestTransformerName) {
|
|
138
|
-
this.
|
|
143
|
+
this.filteredWebhookEvents =
|
|
139
144
|
this.avaiabelWebhookRequestTransformers.find(
|
|
140
145
|
(v) => v.name === this.requestTransformerName
|
|
141
146
|
)?.supportedEvents ?? [];
|
|
142
147
|
} else {
|
|
143
|
-
this.
|
|
148
|
+
this.filteredWebhookEvents = this.availableWebhookEvents;
|
|
144
149
|
}
|
|
145
|
-
this.eventName = this.
|
|
150
|
+
this.eventName = this.filteredWebhookEvents.find(
|
|
146
151
|
(v) => v === this.eventName
|
|
147
152
|
);
|
|
148
153
|
this.changeDetector.detectChanges();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pinelab/vendure-plugin-webhook",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Call webhooks based on configured events from Vendure",
|
|
5
5
|
"author": "Martijn van de Brug <martijn@pinelab.studio>",
|
|
6
6
|
"homepage": "https://plugins.pinelab.studio/",
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"lint": "echo 'No linting configured'"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "9249d84c769819b980f28dd39197d3e5fa4539cc"
|
|
27
27
|
}
|