@seeka-labs/cli-apps 1.1.23 → 1.1.25
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/LICENSE +19 -19
- package/dist/index.js +10 -10
- package/dist/index.js.map +4 -4
- package/dist/init-templates/aws-lambda/.example.gitignore +48 -48
- package/dist/init-templates/aws-lambda/.nvmrc +1 -1
- package/dist/init-templates/aws-lambda/README.md +76 -76
- package/dist/init-templates/aws-lambda/jest.config.js +4 -4
- package/dist/init-templates/aws-lambda/package.json +51 -54
- package/dist/init-templates/aws-lambda/src/index.test.ts +6 -6
- package/dist/init-templates/aws-lambda/src/lib/logging/index.ts +87 -87
- package/dist/init-templates/aws-lambda/src/lib/state/redis/index.ts +64 -64
- package/dist/init-templates/aws-lambda/src/lib/state/seeka/installations.ts +66 -66
- package/dist/init-templates/aws-lambda/src/routes/seekaAppWebhook.ts +193 -193
- package/dist/init-templates/azure-function/.example.gitignore +47 -47
- package/dist/init-templates/azure-function/README.md +107 -107
- package/dist/init-templates/azure-function/jest.config.js +4 -4
- package/dist/init-templates/azure-function/package.json +45 -48
- package/dist/init-templates/azure-function/scripts/dev-queue-setup.js +29 -29
- package/dist/init-templates/azure-function/src/functions/healthCheck.ts +13 -13
- package/dist/init-templates/azure-function/src/functions/pollingExample.ts +39 -39
- package/dist/init-templates/azure-function/src/functions/queueExample.ts +66 -66
- package/dist/init-templates/azure-function/src/functions/seekaAppWebhook.ts +236 -236
- package/dist/init-templates/azure-function/src/index.test.ts +6 -6
- package/dist/init-templates/azure-function/src/lib/browser/index.ts +54 -54
- package/dist/init-templates/azure-function/src/lib/browser/models/index.ts +6 -6
- package/dist/init-templates/azure-function/src/lib/jobs/index.ts +95 -95
- package/dist/init-templates/azure-function/src/lib/logging/index.ts +92 -92
- package/dist/init-templates/azure-function/src/lib/state/redis/index.ts +64 -64
- package/dist/init-templates/azure-function/src/lib/state/seeka/installations.ts +66 -66
- package/dist/init-templates/browser/.editorconfig +14 -14
- package/dist/init-templates/browser/.eslintrc.cjs +1 -1
- package/dist/init-templates/browser/.yarnrc +1 -1
- package/dist/init-templates/browser/jest.config.js +11 -11
- package/dist/init-templates/browser/package.json +3 -3
- package/dist/init-templates/browser/scripts/esbuild/build-browser-plugin.mjs +110 -110
- package/dist/init-templates/browser/scripts/esbuild/plugins/importAsGlobals.mjs +38 -38
- package/dist/init-templates/browser/src/browser.ts +12 -12
- package/dist/init-templates/browser/src/plugin/index.test.ts +6 -6
- package/dist/init-templates/browser/src/plugin/index.ts +49 -49
- package/dist/init-templates/browser/tsconfig.json +34 -34
- package/dist/init-templates/netlify-function/.env.example +17 -17
- package/dist/init-templates/netlify-function/.example.gitignore +36 -36
- package/dist/init-templates/netlify-function/.nvmrc +1 -1
- package/dist/init-templates/netlify-function/.vscode/launch.json +44 -44
- package/dist/init-templates/netlify-function/README.md +61 -61
- package/dist/init-templates/netlify-function/jest.config.js +4 -4
- package/dist/init-templates/netlify-function/netlify.toml +6 -6
- package/dist/init-templates/netlify-function/package.json +11 -14
- package/dist/init-templates/netlify-function/src/api/example-job-background/index.ts +51 -51
- package/dist/init-templates/netlify-function/src/api/polling-example-job-scheduled/index.ts +45 -45
- package/dist/init-templates/netlify-function/src/api/seeka-app-webhook/index.ts +216 -216
- package/dist/init-templates/netlify-function/src/index.test.ts +6 -6
- package/dist/init-templates/netlify-function/src/lib/jobs/index.ts +67 -67
- package/dist/init-templates/netlify-function/src/lib/logging/index.ts +90 -90
- package/dist/init-templates/netlify-function/src/lib/state/redis/index.ts +64 -64
- package/dist/init-templates/netlify-function/src/lib/state/seeka/installations.ts +66 -66
- package/package.json +7 -7
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import winston from 'winston';
|
|
2
|
-
|
|
3
|
-
import { jobNames, triggerBackgroundJob } from '@/lib/jobs';
|
|
4
|
-
import { backgroundJobLogger } from '@/lib/logging';
|
|
5
|
-
import { startServices, stopServices } from '@/lib/services';
|
|
6
|
-
import { listInstallations } from '@/lib/state/seeka/installations';
|
|
7
|
-
|
|
8
|
-
import type { Config, Context } from "@netlify/functions"
|
|
9
|
-
export const config: Config = {
|
|
10
|
-
schedule: "@hourly"
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default async (req: Request, context: Context) => {
|
|
14
|
-
const { next_run } = (await req.json()) as { next_run: string }
|
|
15
|
-
|
|
16
|
-
const logger = backgroundJobLogger(jobNames.examplePollingScheduledJob, undefined, context);
|
|
17
|
-
|
|
18
|
-
logger.profile(`job.${jobNames.examplePollingScheduledJob}`)
|
|
19
|
-
if (process.env.SEEKA_DEBUG_ENABLED === 'true') {
|
|
20
|
-
logger.debug('Received request to trigger scheduled job', { headers: req.headers, next_run });
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
logger.verbose('Received request to trigger scheduled job', { next_run });
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
await startServices(logger);
|
|
27
|
-
|
|
28
|
-
const allInstallations = await listInstallations(logger);
|
|
29
|
-
logger.verbose(`Triggering background job for ${allInstallations.length} installations`, { allInstallations })
|
|
30
|
-
|
|
31
|
-
// Close db connections
|
|
32
|
-
await stopServices(logger);
|
|
33
|
-
|
|
34
|
-
const promises = allInstallations.map(installation => triggerBackgroundJob(jobNames.exampleBackgroundJob, installation, logger))
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
await Promise.all(promises);
|
|
38
|
-
}
|
|
39
|
-
catch (err) {
|
|
40
|
-
logger.error('Error triggering background jobs', { ex: winston.exceptions.getAllInfo(err) })
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
logger.profile(`job.${jobNames.examplePollingScheduledJob}`)
|
|
44
|
-
|
|
45
|
-
return new Response(undefined, { status: 202 })
|
|
1
|
+
import winston from 'winston';
|
|
2
|
+
|
|
3
|
+
import { jobNames, triggerBackgroundJob } from '@/lib/jobs';
|
|
4
|
+
import { backgroundJobLogger } from '@/lib/logging';
|
|
5
|
+
import { startServices, stopServices } from '@/lib/services';
|
|
6
|
+
import { listInstallations } from '@/lib/state/seeka/installations';
|
|
7
|
+
|
|
8
|
+
import type { Config, Context } from "@netlify/functions"
|
|
9
|
+
export const config: Config = {
|
|
10
|
+
schedule: "@hourly"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default async (req: Request, context: Context) => {
|
|
14
|
+
const { next_run } = (await req.json()) as { next_run: string }
|
|
15
|
+
|
|
16
|
+
const logger = backgroundJobLogger(jobNames.examplePollingScheduledJob, undefined, context);
|
|
17
|
+
|
|
18
|
+
logger.profile(`job.${jobNames.examplePollingScheduledJob}`)
|
|
19
|
+
if (process.env.SEEKA_DEBUG_ENABLED === 'true') {
|
|
20
|
+
logger.debug('Received request to trigger scheduled job', { headers: req.headers, next_run });
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
logger.verbose('Received request to trigger scheduled job', { next_run });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await startServices(logger);
|
|
27
|
+
|
|
28
|
+
const allInstallations = await listInstallations(logger);
|
|
29
|
+
logger.verbose(`Triggering background job for ${allInstallations.length} installations`, { allInstallations })
|
|
30
|
+
|
|
31
|
+
// Close db connections
|
|
32
|
+
await stopServices(logger);
|
|
33
|
+
|
|
34
|
+
const promises = allInstallations.map(installation => triggerBackgroundJob(jobNames.exampleBackgroundJob, installation, logger))
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await Promise.all(promises);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
logger.error('Error triggering background jobs', { ex: winston.exceptions.getAllInfo(err) })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
logger.profile(`job.${jobNames.examplePollingScheduledJob}`)
|
|
44
|
+
|
|
45
|
+
return new Response(undefined, { status: 202 })
|
|
46
46
|
}
|
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
|
|
2
|
-
import type { Logger } from 'winston';
|
|
3
|
-
|
|
4
|
-
import winston from 'winston';
|
|
5
|
-
|
|
6
|
-
import { jobNames, triggerBackgroundJob } from '@/lib/jobs';
|
|
7
|
-
import { webhookLogger } from '@/lib/logging';
|
|
8
|
-
import { startServices, stopServices } from '@/lib/services';
|
|
9
|
-
import {
|
|
10
|
-
createOrUpdateInstallation, deleteInstallation, SampleAppInstallSettings, SeekaAppInstallState, tryGetInstallation
|
|
11
|
-
} from '@/lib/state/seeka/installations';
|
|
12
|
-
import {
|
|
13
|
-
PersonIdentifiers, SeekaActivityAcceptedWebhookPayload, SeekaAppInstalledWebhookPayload,
|
|
14
|
-
SeekaAppInstallSettingsUpdatedWebhookPayload, SeekaAppUninstalledWebhookPayload,
|
|
15
|
-
SeekaIdentityChangedWebhookPayload, SeekaWebhookCallType, SeekaWebhookPayload,
|
|
16
|
-
throwOnInvalidWebhookSignature
|
|
17
|
-
} from '@seeka-labs/sdk-apps-server';
|
|
18
|
-
|
|
19
|
-
import type { Config, Context } from "@netlify/functions"
|
|
20
|
-
export const config: Config = {
|
|
21
|
-
path: "/api/webhook/seeka/app",
|
|
22
|
-
method: "POST"
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export default async (req: Request, context: Context) => {
|
|
26
|
-
const bodyStr = (await req.text()) as string;
|
|
27
|
-
const body = JSON.parse(bodyStr) as SeekaWebhookPayload;
|
|
28
|
-
|
|
29
|
-
const logger = webhookLogger(body, context);
|
|
30
|
-
logger.profile('http.seeka.webhook.app')
|
|
31
|
-
logger.verbose('Received webhook from Seeka', { body });
|
|
32
|
-
|
|
33
|
-
// Handle probe
|
|
34
|
-
if (body.type === SeekaWebhookCallType.Probe) {
|
|
35
|
-
return new Response(undefined, { status: 204 })
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Validate webhook
|
|
39
|
-
try {
|
|
40
|
-
throwOnInvalidWebhookSignature(process.env.SEEKA_APP_SECRET as string, req.headers, bodyStr);
|
|
41
|
-
logger.debug('Webhook signature validated', { body });
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
logger.warn('Webhook signature invalid', { body });
|
|
45
|
-
return new Response(JSON.stringify({ error: "Webhook call invalid" }), { status: 401 })
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (body.isTest) {
|
|
49
|
-
// This is a test webhook call
|
|
50
|
-
return new Response(undefined, { status: 204 })
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
await startServices(logger);
|
|
54
|
-
|
|
55
|
-
// Check if the webhook is for an app we have installed
|
|
56
|
-
let installation: SeekaAppInstallState | null = null;
|
|
57
|
-
if (body.type != SeekaWebhookCallType.AppInstalled) {
|
|
58
|
-
installation = await tryGetInstallation((body as SeekaAppInstalledWebhookPayload).context?.applicationInstallId as string, false, logger);
|
|
59
|
-
if (installation == null) {
|
|
60
|
-
logger.warn('Webhook call cannot be processed as the installation ID is not known by this app', { body });
|
|
61
|
-
|
|
62
|
-
// Close db connections
|
|
63
|
-
await stopServices(logger);
|
|
64
|
-
|
|
65
|
-
return new Response(JSON.stringify({ error: "App not installed" }), { status: 422 })
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Do something
|
|
70
|
-
let errorMessage: string | null = null;
|
|
71
|
-
try {
|
|
72
|
-
switch (body.type) {
|
|
73
|
-
case SeekaWebhookCallType.AppInstalled:
|
|
74
|
-
{
|
|
75
|
-
errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
case SeekaWebhookCallType.AppInstallSettingsUpdated:
|
|
79
|
-
{
|
|
80
|
-
errorMessage = await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
case SeekaWebhookCallType.AppUninstalled:
|
|
84
|
-
{
|
|
85
|
-
if (!body.isTest) {
|
|
86
|
-
const payload = body as SeekaAppUninstalledWebhookPayload;
|
|
87
|
-
await deleteInstallation(payload.context?.applicationInstallId as string, logger) // TODO: remove cast
|
|
88
|
-
}
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case SeekaWebhookCallType.ActivityAccepted:
|
|
92
|
-
{
|
|
93
|
-
const payload = body as SeekaActivityAcceptedWebhookPayload;
|
|
94
|
-
await handleSeekaActivity(payload, logger);
|
|
95
|
-
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
case SeekaWebhookCallType.IdentityChanged:
|
|
99
|
-
{
|
|
100
|
-
const payload = body as SeekaIdentityChangedWebhookPayload;
|
|
101
|
-
logger.debug('Identity changed', { payload });
|
|
102
|
-
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
logger.error('Failed to handle webhook', { ex: winston.exceptions.getAllInfo(err) });
|
|
109
|
-
return new Response(JSON.stringify({ error: "Request failed" }), { status: 500 })
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
// Close db connections
|
|
113
|
-
await stopServices(logger);
|
|
114
|
-
logger.profile('http.seeka.webhook.app')
|
|
115
|
-
logger.verbose('Seeka webhook handled');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (errorMessage) {
|
|
119
|
-
logger.warn('Webhook call failed', { errorMessage });
|
|
120
|
-
return Response.json({ error: { message: errorMessage } }, { status: 400 });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return new Response(undefined, { status: 204 })
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const validateInstallationSettings = async (installSettings: SampleAppInstallSettings, logger: Logger): Promise<string | null> => {
|
|
127
|
-
// Returning an error message string here will block the installation request or settings update request by the user installing the app
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger): Promise<string | null> => {
|
|
134
|
-
if (payload.isTest) return null;
|
|
135
|
-
|
|
136
|
-
const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
|
|
137
|
-
if (errorMessage) return errorMessage;
|
|
138
|
-
|
|
139
|
-
const installation = await createOrUpdateInstallation({
|
|
140
|
-
...payload.context,
|
|
141
|
-
installationState: {
|
|
142
|
-
grantedPermissions: payload.content?.grantedPermissions || []
|
|
143
|
-
},
|
|
144
|
-
applicationInstallId: payload.context?.applicationInstallId as string,
|
|
145
|
-
organisationBrandId: payload.context?.organisationBrandId as string,
|
|
146
|
-
organisationId: payload.context?.organisationId as string,
|
|
147
|
-
installedAt: new Date().toISOString(),
|
|
148
|
-
installationSettings: payload.content?.installationSettings || {}
|
|
149
|
-
}, logger)
|
|
150
|
-
|
|
151
|
-
// Trigger a sync for the installation
|
|
152
|
-
try {
|
|
153
|
-
await triggerBackgroundJob(jobNames.exampleBackgroundJob, {
|
|
154
|
-
...payload.context
|
|
155
|
-
}, logger)
|
|
156
|
-
}
|
|
157
|
-
catch (err) {
|
|
158
|
-
await deleteInstallation(installation.applicationInstallId, logger)
|
|
159
|
-
return 'Failed to complete install';
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger): Promise<string | null> => {
|
|
166
|
-
if (payload.isTest) return null;
|
|
167
|
-
|
|
168
|
-
const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
|
|
169
|
-
if (errorMessage) return errorMessage;
|
|
170
|
-
|
|
171
|
-
const existingInstallation = await tryGetInstallation(payload.context?.applicationInstallId as string, true, logger) as SeekaAppInstallState;
|
|
172
|
-
|
|
173
|
-
// Update settings
|
|
174
|
-
const installation = await createOrUpdateInstallation({
|
|
175
|
-
...payload.context,
|
|
176
|
-
...existingInstallation,
|
|
177
|
-
installationState: {
|
|
178
|
-
...existingInstallation.installationState,
|
|
179
|
-
grantedPermissions: payload.content?.grantedPermissions || []
|
|
180
|
-
},
|
|
181
|
-
installationSettings: payload.content?.installationSettings || {}
|
|
182
|
-
} as any, logger) // TODO: remove any
|
|
183
|
-
|
|
184
|
-
logger.info('Settings updated')
|
|
185
|
-
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, logger: Logger) => {
|
|
190
|
-
// const context = activity.context as SeekaAppWebhookContext;
|
|
191
|
-
// const helper = SeekaAppHelper.create(process.env['SEEKA_APP_SECRET'] as string, {
|
|
192
|
-
// organisationId: context.organisationId as string,
|
|
193
|
-
// applicationInstallId: context.applicationInstallId as string,
|
|
194
|
-
// applicationId: process.env['SEEKA_APP_ID'] as string,
|
|
195
|
-
// }, { name, version }, logger);
|
|
196
|
-
|
|
197
|
-
// // Append a first name to the identity
|
|
198
|
-
// await helper.api.mergeIdentity({
|
|
199
|
-
// seekaPId: activity.content?.personId,
|
|
200
|
-
// firstName: [
|
|
201
|
-
// 'firstname_' + new Date().getTime()
|
|
202
|
-
// ]
|
|
203
|
-
// }, {
|
|
204
|
-
// method: 'toremove',
|
|
205
|
-
// origin: TrackingEventSourceOriginType.Server
|
|
206
|
-
// })
|
|
207
|
-
|
|
208
|
-
// // Fire off a tracking event
|
|
209
|
-
// await helper.api.trackActivity({
|
|
210
|
-
// activityName: TrackingActivityNames.Custom,
|
|
211
|
-
// activityNameCustom: 'seeka-app-activity-accepted',
|
|
212
|
-
// activityId: 'act' + new Date().getTime(),
|
|
213
|
-
// }, activity.content?.personId as string, {
|
|
214
|
-
// method: 'toremove',
|
|
215
|
-
// origin: TrackingEventSourceOriginType.Server
|
|
216
|
-
// })
|
|
1
|
+
|
|
2
|
+
import type { Logger } from 'winston';
|
|
3
|
+
|
|
4
|
+
import winston from 'winston';
|
|
5
|
+
|
|
6
|
+
import { jobNames, triggerBackgroundJob } from '@/lib/jobs';
|
|
7
|
+
import { webhookLogger } from '@/lib/logging';
|
|
8
|
+
import { startServices, stopServices } from '@/lib/services';
|
|
9
|
+
import {
|
|
10
|
+
createOrUpdateInstallation, deleteInstallation, SampleAppInstallSettings, SeekaAppInstallState, tryGetInstallation
|
|
11
|
+
} from '@/lib/state/seeka/installations';
|
|
12
|
+
import {
|
|
13
|
+
PersonIdentifiers, SeekaActivityAcceptedWebhookPayload, SeekaAppInstalledWebhookPayload,
|
|
14
|
+
SeekaAppInstallSettingsUpdatedWebhookPayload, SeekaAppUninstalledWebhookPayload,
|
|
15
|
+
SeekaIdentityChangedWebhookPayload, SeekaWebhookCallType, SeekaWebhookPayload,
|
|
16
|
+
throwOnInvalidWebhookSignature
|
|
17
|
+
} from '@seeka-labs/sdk-apps-server';
|
|
18
|
+
|
|
19
|
+
import type { Config, Context } from "@netlify/functions"
|
|
20
|
+
export const config: Config = {
|
|
21
|
+
path: "/api/webhook/seeka/app",
|
|
22
|
+
method: "POST"
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default async (req: Request, context: Context) => {
|
|
26
|
+
const bodyStr = (await req.text()) as string;
|
|
27
|
+
const body = JSON.parse(bodyStr) as SeekaWebhookPayload;
|
|
28
|
+
|
|
29
|
+
const logger = webhookLogger(body, context);
|
|
30
|
+
logger.profile('http.seeka.webhook.app')
|
|
31
|
+
logger.verbose('Received webhook from Seeka', { body });
|
|
32
|
+
|
|
33
|
+
// Handle probe
|
|
34
|
+
if (body.type === SeekaWebhookCallType.Probe) {
|
|
35
|
+
return new Response(undefined, { status: 204 })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate webhook
|
|
39
|
+
try {
|
|
40
|
+
throwOnInvalidWebhookSignature(process.env.SEEKA_APP_SECRET as string, req.headers, bodyStr);
|
|
41
|
+
logger.debug('Webhook signature validated', { body });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
logger.warn('Webhook signature invalid', { body });
|
|
45
|
+
return new Response(JSON.stringify({ error: "Webhook call invalid" }), { status: 401 })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (body.isTest) {
|
|
49
|
+
// This is a test webhook call
|
|
50
|
+
return new Response(undefined, { status: 204 })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await startServices(logger);
|
|
54
|
+
|
|
55
|
+
// Check if the webhook is for an app we have installed
|
|
56
|
+
let installation: SeekaAppInstallState | null = null;
|
|
57
|
+
if (body.type != SeekaWebhookCallType.AppInstalled) {
|
|
58
|
+
installation = await tryGetInstallation((body as SeekaAppInstalledWebhookPayload).context?.applicationInstallId as string, false, logger);
|
|
59
|
+
if (installation == null) {
|
|
60
|
+
logger.warn('Webhook call cannot be processed as the installation ID is not known by this app', { body });
|
|
61
|
+
|
|
62
|
+
// Close db connections
|
|
63
|
+
await stopServices(logger);
|
|
64
|
+
|
|
65
|
+
return new Response(JSON.stringify({ error: "App not installed" }), { status: 422 })
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Do something
|
|
70
|
+
let errorMessage: string | null = null;
|
|
71
|
+
try {
|
|
72
|
+
switch (body.type) {
|
|
73
|
+
case SeekaWebhookCallType.AppInstalled:
|
|
74
|
+
{
|
|
75
|
+
errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case SeekaWebhookCallType.AppInstallSettingsUpdated:
|
|
79
|
+
{
|
|
80
|
+
errorMessage = await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case SeekaWebhookCallType.AppUninstalled:
|
|
84
|
+
{
|
|
85
|
+
if (!body.isTest) {
|
|
86
|
+
const payload = body as SeekaAppUninstalledWebhookPayload;
|
|
87
|
+
await deleteInstallation(payload.context?.applicationInstallId as string, logger) // TODO: remove cast
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case SeekaWebhookCallType.ActivityAccepted:
|
|
92
|
+
{
|
|
93
|
+
const payload = body as SeekaActivityAcceptedWebhookPayload;
|
|
94
|
+
await handleSeekaActivity(payload, logger);
|
|
95
|
+
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case SeekaWebhookCallType.IdentityChanged:
|
|
99
|
+
{
|
|
100
|
+
const payload = body as SeekaIdentityChangedWebhookPayload;
|
|
101
|
+
logger.debug('Identity changed', { payload });
|
|
102
|
+
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
logger.error('Failed to handle webhook', { ex: winston.exceptions.getAllInfo(err) });
|
|
109
|
+
return new Response(JSON.stringify({ error: "Request failed" }), { status: 500 })
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
// Close db connections
|
|
113
|
+
await stopServices(logger);
|
|
114
|
+
logger.profile('http.seeka.webhook.app')
|
|
115
|
+
logger.verbose('Seeka webhook handled');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (errorMessage) {
|
|
119
|
+
logger.warn('Webhook call failed', { errorMessage });
|
|
120
|
+
return Response.json({ error: { message: errorMessage } }, { status: 400 });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return new Response(undefined, { status: 204 })
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const validateInstallationSettings = async (installSettings: SampleAppInstallSettings, logger: Logger): Promise<string | null> => {
|
|
127
|
+
// Returning an error message string here will block the installation request or settings update request by the user installing the app
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger): Promise<string | null> => {
|
|
134
|
+
if (payload.isTest) return null;
|
|
135
|
+
|
|
136
|
+
const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
|
|
137
|
+
if (errorMessage) return errorMessage;
|
|
138
|
+
|
|
139
|
+
const installation = await createOrUpdateInstallation({
|
|
140
|
+
...payload.context,
|
|
141
|
+
installationState: {
|
|
142
|
+
grantedPermissions: payload.content?.grantedPermissions || []
|
|
143
|
+
},
|
|
144
|
+
applicationInstallId: payload.context?.applicationInstallId as string,
|
|
145
|
+
organisationBrandId: payload.context?.organisationBrandId as string,
|
|
146
|
+
organisationId: payload.context?.organisationId as string,
|
|
147
|
+
installedAt: new Date().toISOString(),
|
|
148
|
+
installationSettings: payload.content?.installationSettings || {}
|
|
149
|
+
}, logger)
|
|
150
|
+
|
|
151
|
+
// Trigger a sync for the installation
|
|
152
|
+
try {
|
|
153
|
+
await triggerBackgroundJob(jobNames.exampleBackgroundJob, {
|
|
154
|
+
...payload.context
|
|
155
|
+
}, logger)
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
await deleteInstallation(installation.applicationInstallId, logger)
|
|
159
|
+
return 'Failed to complete install';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger): Promise<string | null> => {
|
|
166
|
+
if (payload.isTest) return null;
|
|
167
|
+
|
|
168
|
+
const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
|
|
169
|
+
if (errorMessage) return errorMessage;
|
|
170
|
+
|
|
171
|
+
const existingInstallation = await tryGetInstallation(payload.context?.applicationInstallId as string, true, logger) as SeekaAppInstallState;
|
|
172
|
+
|
|
173
|
+
// Update settings
|
|
174
|
+
const installation = await createOrUpdateInstallation({
|
|
175
|
+
...payload.context,
|
|
176
|
+
...existingInstallation,
|
|
177
|
+
installationState: {
|
|
178
|
+
...existingInstallation.installationState,
|
|
179
|
+
grantedPermissions: payload.content?.grantedPermissions || []
|
|
180
|
+
},
|
|
181
|
+
installationSettings: payload.content?.installationSettings || {}
|
|
182
|
+
} as any, logger) // TODO: remove any
|
|
183
|
+
|
|
184
|
+
logger.info('Settings updated')
|
|
185
|
+
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, logger: Logger) => {
|
|
190
|
+
// const context = activity.context as SeekaAppWebhookContext;
|
|
191
|
+
// const helper = SeekaAppHelper.create(process.env['SEEKA_APP_SECRET'] as string, {
|
|
192
|
+
// organisationId: context.organisationId as string,
|
|
193
|
+
// applicationInstallId: context.applicationInstallId as string,
|
|
194
|
+
// applicationId: process.env['SEEKA_APP_ID'] as string,
|
|
195
|
+
// }, { name, version }, logger);
|
|
196
|
+
|
|
197
|
+
// // Append a first name to the identity
|
|
198
|
+
// await helper.api.mergeIdentity({
|
|
199
|
+
// seekaPId: activity.content?.personId,
|
|
200
|
+
// firstName: [
|
|
201
|
+
// 'firstname_' + new Date().getTime()
|
|
202
|
+
// ]
|
|
203
|
+
// }, {
|
|
204
|
+
// method: 'toremove',
|
|
205
|
+
// origin: TrackingEventSourceOriginType.Server
|
|
206
|
+
// })
|
|
207
|
+
|
|
208
|
+
// // Fire off a tracking event
|
|
209
|
+
// await helper.api.trackActivity({
|
|
210
|
+
// activityName: TrackingActivityNames.Custom,
|
|
211
|
+
// activityNameCustom: 'seeka-app-activity-accepted',
|
|
212
|
+
// activityId: 'act' + new Date().getTime(),
|
|
213
|
+
// }, activity.content?.personId as string, {
|
|
214
|
+
// method: 'toremove',
|
|
215
|
+
// origin: TrackingEventSourceOriginType.Server
|
|
216
|
+
// })
|
|
217
217
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { describe, expect, test } from '@jest/globals';
|
|
2
|
-
|
|
3
|
-
describe('test example module', () => {
|
|
4
|
-
test('should be false', () => {
|
|
5
|
-
expect(false).toBeFalsy();
|
|
6
|
-
});
|
|
1
|
+
import { describe, expect, test } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
describe('test example module', () => {
|
|
4
|
+
test('should be false', () => {
|
|
5
|
+
expect(false).toBeFalsy();
|
|
6
|
+
});
|
|
7
7
|
});
|