1 | import { createHash } from 'node:crypto'; |
2 | import { ApifyClient } from 'apify-client@^2.19.0'; |
3 |
|
4 | type ApifyApiKey = { |
5 | api_key: string; |
6 | }; |
7 |
|
8 | type Apify = { |
9 | token: string; |
10 | }; |
11 |
|
12 | type WebhookEventTypes = |
13 | | 'ACTOR.RUN.SUCCEEDED' |
14 | | 'ACTOR.RUN.FAILED' |
15 | | 'ACTOR.RUN.TIMED_OUT' |
16 | | 'ACTOR.RUN.ABORTED' |
17 |
|
18 | type ApifyWebhookConfig = { |
19 | url: string; |
20 | token: string; |
21 | }; |
22 |
|
23 | export type DynSelect_taskId = string; |
24 | export async function taskId(api_key?: ApifyApiKey, oauth_token?: Apify) { |
25 | if (!api_key?.api_key && !oauth_token?.token) { |
26 | return [{ value: '', label: 'Missing Apify API key or OAuth token' }]; |
27 | } |
28 |
|
29 | try { |
30 | const client = createClient(api_key, oauth_token); |
31 |
|
32 | const data = await client.tasks().list(); |
33 | const items = data?.items ?? []; |
34 |
|
35 | return items.map((task: any) => ({ |
36 | value: task.id, |
37 | label: task.title || task.name || task.id, |
38 | })); |
39 | } catch (error: any) { |
40 | return [ |
41 | { value: '', label: `Failed to load tasks: ${error.message || error}` }, |
42 | ]; |
43 | } |
44 | } |
45 |
|
46 | function prettifyEvent(e: string) { |
47 | return e.replace(/^ACTOR\.RUN\./, ''); |
48 | } |
49 |
|
50 | function generateIdempotencyKey( |
51 | id: string, |
52 | eventTypes: WebhookEventTypes[], |
53 | requestUrl: string |
54 | ): string { |
55 | const sortedEventTypes = [...eventTypes].sort(); |
56 | const url = new URL(requestUrl); |
57 | const pathname = url.pathname; |
58 | const hash = createHash('sha256'); |
59 | hash.update(`${id}:${sortedEventTypes.join(',')}:${pathname}`); |
60 | return hash.digest('hex'); |
61 | } |
62 |
|
63 | const createClient = (api_key?: ApifyApiKey, oauth_token?: Apify): ApifyClient => { |
64 | const token = oauth_token?.token ?? api_key?.api_key; |
65 | if (!token) { |
66 | throw new Error('Missing Apify API key or OAuth token'); |
67 | } |
68 |
|
69 | return new ApifyClient({ |
70 | token: token, |
71 | requestInterceptors: [ |
72 | (request) => { |
73 | if (!request.headers) { |
74 | request.headers = {}; |
75 | } |
76 | request.headers['x-apify-integration-platform'] = 'windmill'; |
77 | return request; |
78 | }, |
79 | ], |
80 | }); |
81 | }; |
82 |
|
83 | export async function main( |
84 | apifyWebhookConfig: ApifyWebhookConfig, |
85 | taskId: DynSelect_taskId, |
86 | eventTypes: WebhookEventTypes[], |
87 | api_key?: ApifyApiKey, |
88 | oauth_token?: Apify, |
89 | ) { |
90 | if (!taskId) { |
91 | return { error: 'Task ID is required' }; |
92 | } |
93 |
|
94 | const client = createClient(api_key, oauth_token); |
95 |
|
96 | const idempotencyKey = generateIdempotencyKey( |
97 | taskId, |
98 | eventTypes, |
99 | apifyWebhookConfig.url |
100 | ); |
101 | const headersTemplate = JSON.stringify({ |
102 | Authorization: `Bearer ${apifyWebhookConfig.token}`, |
103 | }); |
104 |
|
105 | |
106 | const task = await client.task(taskId).get(); |
107 | const taskName = task && (task.title || task.name); |
108 | const webhookEvents = eventTypes |
109 | .map((event) => prettifyEvent(event)) |
110 | .join(', '); |
111 |
|
112 | try { |
113 | const response = await client.webhooks().create({ |
114 | requestUrl: apifyWebhookConfig.url, |
115 | eventTypes: eventTypes, |
116 | condition: { |
117 | actorTaskId: taskId, |
118 | }, |
119 | idempotencyKey, |
120 | headersTemplate, |
121 | shouldInterpolateStrings: true, |
122 | description: `${taskName}: ${webhookEvents}`, |
123 | title: "Windmill integration" |
124 | }); |
125 |
|
126 | return response; |
127 | } catch (e: any) { |
128 | return { error: `Failed to create a webhook. Reason: ${e.message}` }; |
129 | } |
130 | } |
131 |
|