@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 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
- .getRepository(ctx, webhook_entity_1.Webhook)
87
- .find({ where: { channelId: String(ctx.channelId) } });
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: { channelId: String(event.ctx.channelId), event: eventName },
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
  `;
@@ -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
  `;
@@ -10,6 +10,7 @@ export const setWebhooksMutation = gql`
10
10
  supportedEvents
11
11
  }
12
12
  url
13
+ channelAgnostic
13
14
  }
14
15
  }
15
16
  `;
@@ -24,6 +25,7 @@ export const getWebhooksQuery = gql`
24
25
  supportedEvents
25
26
  }
26
27
  url
28
+ channelAgnostic
27
29
  }
28
30
  }
29
31
  `;
@@ -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 filteredWeebhookEvents"
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)="deleteWeebhook(webhook.id)"
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
- availableWeebhookEvents: string[] = [];
30
- filteredWeebhookEvents: string[] = [];
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.filteredWeebhookEvents = this.availableWeebhookEvents =
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
- deleteWeebhook(id: number) {
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.filteredWeebhookEvents =
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.filteredWeebhookEvents =
143
+ this.filteredWebhookEvents =
139
144
  this.avaiabelWebhookRequestTransformers.find(
140
145
  (v) => v.name === this.requestTransformerName
141
146
  )?.supportedEvents ?? [];
142
147
  } else {
143
- this.filteredWeebhookEvents = this.availableWeebhookEvents;
148
+ this.filteredWebhookEvents = this.availableWebhookEvents;
144
149
  }
145
- this.eventName = this.filteredWeebhookEvents.find(
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.5.2",
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": "ba4e499e13892b029fac2aa29082c03ec8032249"
26
+ "gitHead": "9249d84c769819b980f28dd39197d3e5fa4539cc"
27
27
  }