@slates/provider 1.0.0-rc.21 → 1.0.0-rc.23
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/dist/index.cjs +277 -138
- package/dist/index.d.cts +158 -100
- package/dist/index.d.ts +158 -100
- package/dist/index.module.js +274 -138
- package/package.json +1 -1
- package/src/action/action.ts +6 -88
- package/src/action/builder.ts +50 -46
- package/src/action/directUpload.test.ts +3 -1
- package/src/action/directUpload.ts +8 -4
- package/src/action/getFileUrlTool.ts +14 -4
- package/src/action/index.ts +1 -0
- package/src/action/trigger.ts +17 -60
- package/src/action/triggerGroup.ts +296 -0
- package/src/adapter/index.test.ts +35 -14
- package/src/auth/redact.test.ts +121 -1
- package/src/auth/redact.ts +30 -6
- package/src/module/resolveDefaultExport.test.ts +3 -3
- package/src/specification/slate.test.ts +102 -0
- package/src/specification/slate.ts +31 -3
- package/src/webhook/graph.ts +2 -2
- package/src/webhook/meta.ts +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { SlateTool } from '../action/tool';
|
|
4
|
+
import { SlateTriggerGroup } from '../action/triggerGroup';
|
|
4
5
|
import { SlateAuth } from '../auth';
|
|
5
6
|
import { SlateConfig } from '../config';
|
|
6
7
|
import { Slate } from '../specification/slate';
|
|
@@ -41,6 +42,16 @@ describe('SlateAdapterSpec', () => {
|
|
|
41
42
|
}))
|
|
42
43
|
.build();
|
|
43
44
|
|
|
45
|
+
let emailTriggerGroup = SlateTriggerGroup.create(spec, {
|
|
46
|
+
key: 'emails',
|
|
47
|
+
name: 'Emails'
|
|
48
|
+
})
|
|
49
|
+
.polling({
|
|
50
|
+
pollEvents: async () => ({ events: [] })
|
|
51
|
+
})
|
|
52
|
+
.routingMatchers(async () => [])
|
|
53
|
+
.build();
|
|
54
|
+
|
|
44
55
|
let emailReceivedTrigger = gmailAdapterSpec
|
|
45
56
|
.trigger(spec, {
|
|
46
57
|
key: 'email_received',
|
|
@@ -48,13 +59,13 @@ describe('SlateAdapterSpec', () => {
|
|
|
48
59
|
})
|
|
49
60
|
.input(z.object({ messageId: z.string() }))
|
|
50
61
|
.output(z.object({ type: z.literal('email.received') }))
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
})
|
|
62
|
+
.triggerGroup(emailTriggerGroup)
|
|
63
|
+
.matches(() => true)
|
|
64
|
+
.map(async ctx => ({
|
|
65
|
+
type: 'email.received',
|
|
66
|
+
id: ctx.input.messageId,
|
|
67
|
+
output: { type: 'email.received' as const }
|
|
68
|
+
}))
|
|
58
69
|
.build();
|
|
59
70
|
|
|
60
71
|
expect(sendEmailTool.adapter).toBe('gmail');
|
|
@@ -218,6 +229,16 @@ describe('SlateAdapterSpec', () => {
|
|
|
218
229
|
}))
|
|
219
230
|
.build();
|
|
220
231
|
|
|
232
|
+
let sendEmailTriggerGroup = SlateTriggerGroup.create(spec, {
|
|
233
|
+
key: 'sent_emails',
|
|
234
|
+
name: 'Sent Emails'
|
|
235
|
+
})
|
|
236
|
+
.polling({
|
|
237
|
+
pollEvents: async () => ({ events: [] })
|
|
238
|
+
})
|
|
239
|
+
.routingMatchers(async () => [])
|
|
240
|
+
.build();
|
|
241
|
+
|
|
221
242
|
let sendEmailTrigger = gmailAdapterSpec
|
|
222
243
|
.trigger(spec, {
|
|
223
244
|
key: 'send_email',
|
|
@@ -225,13 +246,13 @@ describe('SlateAdapterSpec', () => {
|
|
|
225
246
|
})
|
|
226
247
|
.input(z.object({}))
|
|
227
248
|
.output(z.object({ type: z.string() }))
|
|
228
|
-
.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
})
|
|
249
|
+
.triggerGroup(sendEmailTriggerGroup)
|
|
250
|
+
.matches(() => true)
|
|
251
|
+
.map(async () => ({
|
|
252
|
+
type: 'email.sent',
|
|
253
|
+
id: '1',
|
|
254
|
+
output: { type: 'email.sent' }
|
|
255
|
+
}))
|
|
235
256
|
.build();
|
|
236
257
|
|
|
237
258
|
expect(() =>
|
package/src/auth/redact.test.ts
CHANGED
|
@@ -49,6 +49,98 @@ describe('AuthConfigSecretRedactor', () => {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
describe('AuthConfigSecretRedactor.redactEmbedded', () => {
|
|
53
|
+
it('preserves the legacy placeholder format for whole-value matches', () => {
|
|
54
|
+
let redactor = new AuthConfigSecretRedactor({
|
|
55
|
+
token: 'ABC',
|
|
56
|
+
nested: { tokens: ['XYZ'] }
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(redactor.redactEmbedded({ values: ['ABC', 'XYZ', 'plain', 42, null] })).toEqual({
|
|
60
|
+
values: [
|
|
61
|
+
'$$MT$secret$authConfig$token',
|
|
62
|
+
'$$MT$secret$authConfig$nested.tokens.0',
|
|
63
|
+
'plain',
|
|
64
|
+
42,
|
|
65
|
+
null
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('terminates embedded placeholders before prefixes, suffixes, and adjacent literals', () => {
|
|
71
|
+
let redactor = new AuthConfigSecretRedactor({ token: 'ABC', token2: 'XYZ' });
|
|
72
|
+
|
|
73
|
+
expect(
|
|
74
|
+
redactor.redactEmbedded({
|
|
75
|
+
bearer: 'Bearer ABC',
|
|
76
|
+
suffix: 'ABC2',
|
|
77
|
+
multiple: 'ABC:XYZ:ABC',
|
|
78
|
+
adjacent: 'ABCXYZ'
|
|
79
|
+
})
|
|
80
|
+
).toEqual({
|
|
81
|
+
bearer: 'Bearer $$MT$secret$authConfig$token$$',
|
|
82
|
+
suffix: '$$MT$secret$authConfig$token$$2',
|
|
83
|
+
multiple:
|
|
84
|
+
'$$MT$secret$authConfig$token$$:$$MT$secret$authConfig$token2$$:$$MT$secret$authConfig$token$$',
|
|
85
|
+
adjacent: '$$MT$secret$authConfig$token$$$$MT$secret$authConfig$token2$$'
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('matches overlapping secrets longest-first', () => {
|
|
90
|
+
let redactor = new AuthConfigSecretRedactor({ token: 'ABC', longer: 'ABC2' });
|
|
91
|
+
|
|
92
|
+
expect(redactor.redactEmbedded('ABC2 ABC')).toBe(
|
|
93
|
+
'$$MT$secret$authConfig$longer$$ $$MT$secret$authConfig$token$$'
|
|
94
|
+
);
|
|
95
|
+
expect(redactor.redactEmbedded('ABC2')).toBe('$$MT$secret$authConfig$longer');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('does not scan emitted placeholders again', () => {
|
|
99
|
+
let redactor = new AuthConfigSecretRedactor({ token: 'ABC', other: 'token' });
|
|
100
|
+
|
|
101
|
+
expect(redactor.redactEmbedded('Bearer ABC token')).toBe(
|
|
102
|
+
'Bearer $$MT$secret$authConfig$token$$ $$MT$secret$authConfig$other$$'
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it.each([
|
|
107
|
+
'.',
|
|
108
|
+
'*',
|
|
109
|
+
'+',
|
|
110
|
+
'?',
|
|
111
|
+
'^',
|
|
112
|
+
'$',
|
|
113
|
+
'{',
|
|
114
|
+
'}',
|
|
115
|
+
'(',
|
|
116
|
+
')',
|
|
117
|
+
'|',
|
|
118
|
+
'[',
|
|
119
|
+
']',
|
|
120
|
+
'\\'
|
|
121
|
+
])('treats %s literally in secret values', character => {
|
|
122
|
+
let secret = `${character}secret`;
|
|
123
|
+
let redactor = new AuthConfigSecretRedactor({ token: secret });
|
|
124
|
+
|
|
125
|
+
expect(redactor.redactEmbedded(`prefix ${secret} suffix`)).toBe(
|
|
126
|
+
'prefix $$MT$secret$authConfig$token$$ suffix'
|
|
127
|
+
);
|
|
128
|
+
expect(redactor.redactEmbedded('prefix secret suffix')).toBe('prefix secret suffix');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('ignores empty secrets and leaves unrelated values unchanged', () => {
|
|
132
|
+
let redactor = new AuthConfigSecretRedactor({ empty: '', token: 'ABC' });
|
|
133
|
+
|
|
134
|
+
expect(redactor.redactEmbedded({ empty: '', plain: 'plain', token: 'ABC' })).toEqual({
|
|
135
|
+
empty: '',
|
|
136
|
+
plain: 'plain',
|
|
137
|
+
token: '$$MT$secret$authConfig$token'
|
|
138
|
+
});
|
|
139
|
+
expect(new AuthConfigSecretRedactor({ empty: '' }).redactEmbedded('plain')).toBe('plain');
|
|
140
|
+
expect(new AuthConfigSecretRedactor(undefined).redactEmbedded('plain')).toBe('plain');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
52
144
|
describe('redactUrlAttachmentSecrets', () => {
|
|
53
145
|
let authConfig = { token: '123SECRET', nested: { token2: 'also_secret' } };
|
|
54
146
|
|
|
@@ -69,12 +161,40 @@ describe('redactUrlAttachmentSecrets', () => {
|
|
|
69
161
|
|
|
70
162
|
expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual([
|
|
71
163
|
urlAttachment({
|
|
72
|
-
headers: { Authorization: 'Bearer
|
|
164
|
+
headers: { Authorization: 'Bearer $$MT$secret$authConfig$token$$' },
|
|
73
165
|
query: { key: '$$MT$secret$authConfig$nested.token2' }
|
|
74
166
|
})
|
|
75
167
|
]);
|
|
76
168
|
});
|
|
77
169
|
|
|
170
|
+
it('only changes header and query values, preserving metadata and the original attachment', () => {
|
|
171
|
+
let attachment: SlateAttachment = {
|
|
172
|
+
mimeType: 'application/pdf',
|
|
173
|
+
attachmentHash: 'file-hash',
|
|
174
|
+
content: {
|
|
175
|
+
type: 'url',
|
|
176
|
+
url: 'https://example.com/123SECRET/file.pdf',
|
|
177
|
+
headers: { Authorization: 'Bearer 123SECRET' },
|
|
178
|
+
query: { key: 'also_secret-suffix' },
|
|
179
|
+
refreshReference: { id: 'file-1' },
|
|
180
|
+
refreshAt: '2026-09-10T00:00:00Z'
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
let original = structuredClone(attachment);
|
|
184
|
+
|
|
185
|
+
expect(redactUrlAttachmentSecrets([attachment], authConfig)).toEqual([
|
|
186
|
+
{
|
|
187
|
+
...original,
|
|
188
|
+
content: {
|
|
189
|
+
...original.content,
|
|
190
|
+
headers: { Authorization: 'Bearer $$MT$secret$authConfig$token$$' },
|
|
191
|
+
query: { key: '$$MT$secret$authConfig$nested.token2$$-suffix' }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
]);
|
|
195
|
+
expect(attachment).toEqual(original);
|
|
196
|
+
});
|
|
197
|
+
|
|
78
198
|
it('leaves url attachments with no headers/query untouched', () => {
|
|
79
199
|
let attachments = [urlAttachment()];
|
|
80
200
|
expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual(attachments);
|
package/src/auth/redact.ts
CHANGED
|
@@ -8,10 +8,9 @@ import type { SlateAttachment } from '../action/attachment';
|
|
|
8
8
|
export let AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = '$$MT$secret$authConfig$';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* like the http trace redactor in '../axios/trace'.
|
|
11
|
+
* Maps auth-config string leaves to `$$MT$secret$authConfig$<path>` placeholders.
|
|
12
|
+
* `redact` preserves whole-value substitution for HTTP traces; `redactEmbedded` also
|
|
13
|
+
* replaces embedded values in attachment credentials, terminating those placeholders with `$$`.
|
|
15
14
|
*/
|
|
16
15
|
export class AuthConfigSecretRedactor {
|
|
17
16
|
private readonly secretToPlaceholder = new Map<string, string>();
|
|
@@ -26,6 +25,29 @@ export class AuthConfigSecretRedactor {
|
|
|
26
25
|
});
|
|
27
26
|
}
|
|
28
27
|
|
|
28
|
+
public redactEmbedded<T>(value: T): T {
|
|
29
|
+
if (this.secretToPlaceholder.size === 0) return value;
|
|
30
|
+
|
|
31
|
+
let pattern = new RegExp(
|
|
32
|
+
[...this.secretToPlaceholder.keys()]
|
|
33
|
+
.sort((a, b) => b.length - a.length)
|
|
34
|
+
// Escape regex metacharacters so values match literally; $& inserts the matched character.
|
|
35
|
+
.map(secret => secret.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
36
|
+
.join('|'),
|
|
37
|
+
'g'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return this.transform(value, stringValue => {
|
|
41
|
+
let exactPlaceholder = this.secretToPlaceholder.get(stringValue);
|
|
42
|
+
if (exactPlaceholder) return exactPlaceholder;
|
|
43
|
+
|
|
44
|
+
return stringValue.replace(
|
|
45
|
+
pattern,
|
|
46
|
+
secret => `${this.secretToPlaceholder.get(secret)}$$`
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
private buildSecretMap(value: unknown, path: string[] = []): void {
|
|
30
52
|
if (typeof value === 'string') {
|
|
31
53
|
// Never register "" -- it would replace every empty header/query value with a placeholder.
|
|
@@ -102,9 +124,11 @@ export let redactUrlAttachmentSecrets = (
|
|
|
102
124
|
content: {
|
|
103
125
|
...attachment.content,
|
|
104
126
|
headers: attachment.content.headers
|
|
105
|
-
? redactor.
|
|
127
|
+
? redactor.redactEmbedded(attachment.content.headers)
|
|
106
128
|
: undefined,
|
|
107
|
-
query: attachment.content.query
|
|
129
|
+
query: attachment.content.query
|
|
130
|
+
? redactor.redactEmbedded(attachment.content.query)
|
|
131
|
+
: undefined
|
|
108
132
|
}
|
|
109
133
|
};
|
|
110
134
|
});
|
|
@@ -19,9 +19,9 @@ describe('resolveDefaultExport', () => {
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
it('unwraps nested default exports from double-bundling', () => {
|
|
22
|
-
expect(
|
|
23
|
-
|
|
24
|
-
)
|
|
22
|
+
expect(resolveDefaultExport<typeof Example>({ default: { default: Example } })).toBe(
|
|
23
|
+
Example
|
|
24
|
+
);
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
it('throws when the export is not a constructor', () => {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { SlateTrigger, SlateTriggerGroup } from '../action';
|
|
4
|
+
import { SlateAuth } from '../auth';
|
|
5
|
+
import { SlateConfig } from '../config';
|
|
6
|
+
import { Slate } from './slate';
|
|
7
|
+
import { SlateSpecification } from './specification';
|
|
8
|
+
|
|
9
|
+
let createTestSpec = () => {
|
|
10
|
+
let config = SlateConfig.create(z.object({}));
|
|
11
|
+
let auth = SlateAuth.create<{}>().output(z.object({}));
|
|
12
|
+
|
|
13
|
+
return SlateSpecification.create({
|
|
14
|
+
key: 'demo',
|
|
15
|
+
name: 'Demo',
|
|
16
|
+
config,
|
|
17
|
+
auth
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let createManualWebhookTriggerGroup = (spec: ReturnType<typeof createTestSpec>, key: string) =>
|
|
22
|
+
SlateTriggerGroup.create(spec, {
|
|
23
|
+
key,
|
|
24
|
+
name: key
|
|
25
|
+
})
|
|
26
|
+
.webhook({
|
|
27
|
+
manualRegistration: {
|
|
28
|
+
userConfigSchema: z.object({}),
|
|
29
|
+
fullConfigSchema: z.object({}),
|
|
30
|
+
setup: async () => ({
|
|
31
|
+
webhookSetupDocument: '',
|
|
32
|
+
partialWebhookRegistrationPayload: {}
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
process: async () => ({ events: [] })
|
|
36
|
+
})
|
|
37
|
+
.routingMatchers(async () => [])
|
|
38
|
+
.build();
|
|
39
|
+
|
|
40
|
+
let createAutoWebhookTriggerGroup = (spec: ReturnType<typeof createTestSpec>, key: string) =>
|
|
41
|
+
SlateTriggerGroup.create(spec, {
|
|
42
|
+
key,
|
|
43
|
+
name: key
|
|
44
|
+
})
|
|
45
|
+
.webhook({
|
|
46
|
+
autoRegistration: {
|
|
47
|
+
webhookTargetList: async () => ({ targets: [], nextPageToken: null }),
|
|
48
|
+
webhookRegister: async () => ({
|
|
49
|
+
webhookRegistrationIdentifier: 'reg',
|
|
50
|
+
webhookRegistrationPayload: {}
|
|
51
|
+
}),
|
|
52
|
+
webhookUnregister: async () => {}
|
|
53
|
+
},
|
|
54
|
+
process: async () => ({ events: [] })
|
|
55
|
+
})
|
|
56
|
+
.routingMatchers(async () => [])
|
|
57
|
+
.build();
|
|
58
|
+
|
|
59
|
+
describe('Slate.create', () => {
|
|
60
|
+
it('rejects more than one manual webhook trigger group', () => {
|
|
61
|
+
let spec = createTestSpec();
|
|
62
|
+
let first = createManualWebhookTriggerGroup(spec, 'manual_one');
|
|
63
|
+
let second = createManualWebhookTriggerGroup(spec, 'manual_two');
|
|
64
|
+
|
|
65
|
+
expect(() =>
|
|
66
|
+
Slate.create({
|
|
67
|
+
spec,
|
|
68
|
+
tools: [],
|
|
69
|
+
triggers: [],
|
|
70
|
+
triggerGroups: [first, second]
|
|
71
|
+
})
|
|
72
|
+
).toThrow(
|
|
73
|
+
'Only one trigger group may use manual webhook registration, but found: manual_one, manual_two'
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('allows a single manual webhook trigger group alongside other trigger groups', () => {
|
|
78
|
+
let spec = createTestSpec();
|
|
79
|
+
let manual = createManualWebhookTriggerGroup(spec, 'manual_group');
|
|
80
|
+
let auto = createAutoWebhookTriggerGroup(spec, 'auto_group');
|
|
81
|
+
|
|
82
|
+
let manualTrigger = SlateTrigger.create(spec, {
|
|
83
|
+
key: 'manual_trigger',
|
|
84
|
+
name: 'Manual Trigger'
|
|
85
|
+
})
|
|
86
|
+
.input(z.object({}))
|
|
87
|
+
.output(z.object({ type: z.literal('demo') }))
|
|
88
|
+
.triggerGroup(manual)
|
|
89
|
+
.matches(() => true)
|
|
90
|
+
.map(async () => ({ type: 'demo', id: '1', output: { type: 'demo' as const } }))
|
|
91
|
+
.build();
|
|
92
|
+
|
|
93
|
+
let slate = Slate.create({
|
|
94
|
+
spec,
|
|
95
|
+
tools: [],
|
|
96
|
+
triggers: [manualTrigger],
|
|
97
|
+
triggerGroups: [manual, auto]
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(slate.triggerGroups.map(group => group.key)).toEqual(['manual_group', 'auto_group']);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SlateTool, SlateTrigger } from '../action';
|
|
1
|
+
import type { SlateTool, SlateTrigger, SlateTriggerGroup } from '../action';
|
|
2
2
|
import type { SlateAdapter } from '../adapter';
|
|
3
3
|
import { SlateDeclarationError } from '../error';
|
|
4
4
|
import type { SlateSpecification } from './specification';
|
|
@@ -10,7 +10,8 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
|
|
|
10
10
|
| SlateTrigger<ConfigType, AuthType, any, any>
|
|
11
11
|
| SlateTool<ConfigType, AuthType, any, any>
|
|
12
12
|
)[],
|
|
13
|
-
private readonly _adapters: SlateAdapter<ConfigType, AuthType>[]
|
|
13
|
+
private readonly _adapters: SlateAdapter<ConfigType, AuthType>[],
|
|
14
|
+
private readonly _triggerGroups: SlateTriggerGroup<ConfigType, AuthType>[]
|
|
14
15
|
) {}
|
|
15
16
|
|
|
16
17
|
static create<ConfigType extends {}, AuthType extends {}>(params: {
|
|
@@ -18,6 +19,7 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
|
|
|
18
19
|
triggers: SlateTrigger<ConfigType, AuthType, any, any>[];
|
|
19
20
|
tools: SlateTool<ConfigType, AuthType, any, any>[];
|
|
20
21
|
adapters?: SlateAdapter<ConfigType, AuthType>[];
|
|
22
|
+
triggerGroups?: SlateTriggerGroup<ConfigType, AuthType>[];
|
|
21
23
|
}) {
|
|
22
24
|
let adapters = params.adapters ?? [];
|
|
23
25
|
let actions = [...params.triggers, ...params.tools];
|
|
@@ -47,7 +49,29 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
|
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
let triggerGroups = params.triggerGroups ?? [];
|
|
53
|
+
let seenTriggerGroupKeys = new Set<string>();
|
|
54
|
+
for (let group of triggerGroups) {
|
|
55
|
+
if (seenTriggerGroupKeys.has(group.key)) {
|
|
56
|
+
throw new SlateDeclarationError(
|
|
57
|
+
`Trigger group "${group.key}" is registered more than once`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
seenTriggerGroupKeys.add(group.key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let manualWebhookTriggerGroups = triggerGroups.filter(
|
|
64
|
+
group => group.source === 'webhook' && group.webhook?.manualRegistration
|
|
65
|
+
);
|
|
66
|
+
if (manualWebhookTriggerGroups.length > 1) {
|
|
67
|
+
throw new SlateDeclarationError(
|
|
68
|
+
`Only one trigger group may use manual webhook registration, but found: ${manualWebhookTriggerGroups
|
|
69
|
+
.map(group => group.key)
|
|
70
|
+
.join(', ')}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return new Slate(params.spec, actions, adapters, triggerGroups);
|
|
51
75
|
}
|
|
52
76
|
|
|
53
77
|
get spec() {
|
|
@@ -61,4 +85,8 @@ export class Slate<ConfigType extends {}, AuthType extends {}> {
|
|
|
61
85
|
get adapters() {
|
|
62
86
|
return this._adapters;
|
|
63
87
|
}
|
|
88
|
+
|
|
89
|
+
get triggerGroups() {
|
|
90
|
+
return this._triggerGroups;
|
|
91
|
+
}
|
|
64
92
|
}
|
package/src/webhook/graph.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SlateWebhookHttpResponseInit } from '../action';
|
|
2
2
|
|
|
3
3
|
export const graphWebhookHttp = {
|
|
4
4
|
methods: ['POST'],
|
|
@@ -6,7 +6,7 @@ export const graphWebhookHttp = {
|
|
|
6
6
|
mode: 'match',
|
|
7
7
|
match: [{ hasQueryParam: 'validationToken' }]
|
|
8
8
|
}
|
|
9
|
-
}
|
|
9
|
+
};
|
|
10
10
|
|
|
11
11
|
export let getGraphWebhookValidationResponse = (
|
|
12
12
|
request: Request
|
package/src/webhook/meta.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SlateWebhookHttpResponseInit } from '../action';
|
|
2
2
|
|
|
3
3
|
export const metaWebhookHttp = {
|
|
4
4
|
methods: ['GET', 'POST'],
|
|
@@ -6,7 +6,7 @@ export const metaWebhookHttp = {
|
|
|
6
6
|
mode: 'match',
|
|
7
7
|
match: [{ method: 'GET', hasQueryParam: 'hub.mode' }]
|
|
8
8
|
}
|
|
9
|
-
}
|
|
9
|
+
};
|
|
10
10
|
|
|
11
11
|
export let getMetaWebhookVerificationResponse = (
|
|
12
12
|
request: Request,
|