@seeka-labs/cli-apps 2.2.5 → 3.2.3
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.js +46919 -35
- package/dist/index.js.map +4 -4
- package/dist/init-template/.gitlab-ci.yml +29 -8
- package/dist/init-template/.nvmrc +1 -0
- package/dist/init-template/README.md +18 -3
- package/dist/init-template/app/{server-azure-function/.eslintrc.cjs → .eslintrc.cjs} +2 -0
- package/dist/init-template/app/browser/README.md +1 -1
- package/dist/init-template/app/browser/package.json +22 -24
- package/dist/init-template/app/browser/scripts/esbuild/build-browser-plugin.mjs +14 -6
- package/dist/init-template/app/browser/src/browser.ts +6 -6
- package/dist/init-template/app/browser/src/plugin/index.ts +57 -44
- package/dist/init-template/app/browser/tsconfig.json +1 -2
- package/dist/init-template/app/lib/package.json +46 -0
- package/dist/init-template/app/lib/src/index.ts +4 -0
- package/dist/init-template/app/lib/src/models/index.ts +29 -0
- package/dist/init-template/app/lib/src/validation/index.ts +14 -0
- package/dist/init-template/app/lib/tsconfig.json +22 -0
- package/dist/init-template/app/server-azurefunc/.eslintrc.cjs +4 -0
- package/dist/init-template/app/{server-azure-function → server-azurefunc}/.funcignore +0 -3
- package/dist/init-template/app/{server-azure-function → server-azurefunc}/README.md +3 -3
- package/dist/init-template/app/{server-azure-function → server-azurefunc}/host.json +11 -0
- package/dist/init-template/app/server-azurefunc/package.json +69 -0
- package/dist/init-template/app/server-azurefunc/scripts/dev-queue-setup.js +18 -0
- package/dist/init-template/app/{server-azure-function → server-azurefunc}/scripts/ngrok.js +4 -1
- package/dist/init-template/app/server-azurefunc/src/app/api/router.ts +14 -0
- package/dist/init-template/app/server-azurefunc/src/app/api/routes/getInstallationSettings.ts +13 -0
- package/dist/init-template/app/server-azurefunc/src/app/api/routes/setInstallationSettings.ts +35 -0
- package/dist/init-template/app/server-azurefunc/src/app/jobs/index.ts +61 -0
- package/dist/init-template/app/server-azurefunc/src/app/logging/index.ts +4 -0
- package/dist/init-template/app/server-azurefunc/src/app/models/index.ts +12 -0
- package/dist/init-template/app/server-azurefunc/src/app/services/activites.ts +8 -0
- package/dist/init-template/app/{server-azure-function → server-azurefunc}/src/functions/healthCheck.ts +7 -1
- package/dist/init-template/app/server-azurefunc/src/functions/seekaAppWebhook.ts +201 -0
- package/dist/init-template/app/server-azurefunc/src/functions/trackActivityQueueHandler.ts +48 -0
- package/dist/init-template/app/server-azurefunc/src/functions/ui.ts +46 -0
- package/dist/init-template/app/{server-azure-function/local.settings.example.json → server-azurefunc/template.settings.json} +18 -10
- package/dist/init-template/app/{server-azure-function → server-azurefunc}/tsconfig.json +8 -2
- package/dist/init-template/app/ui/.env +9 -0
- package/dist/init-template/app/ui/README.md +40 -0
- package/dist/init-template/app/ui/index.html +21 -0
- package/dist/init-template/app/ui/package.json +72 -0
- package/dist/init-template/app/ui/public/favicon.ico +0 -0
- package/dist/init-template/app/ui/scripts/copy-output.mjs +30 -0
- package/dist/init-template/app/ui/src/App.tsx +72 -0
- package/dist/init-template/app/ui/src/assets/app-icon.svg +1 -0
- package/dist/init-template/app/ui/src/components/setup/steps/complete/index.tsx +32 -0
- package/dist/init-template/app/ui/src/components/setup/steps/first/index.tsx +27 -0
- package/dist/init-template/app/ui/src/components/setup/steps/index.tsx +22 -0
- package/dist/init-template/app/ui/src/components/setup/steps/second/index.tsx +29 -0
- package/dist/init-template/app/ui/src/index.tsx +45 -0
- package/dist/init-template/app/ui/src/routes/home/index.tsx +21 -0
- package/dist/init-template/app/ui/src/vite-env.d.ts +13 -0
- package/dist/init-template/app/ui/tsconfig.json +35 -0
- package/dist/init-template/app/ui/tsconfig.node.json +10 -0
- package/dist/init-template/app/ui/vite.config.mts +48 -0
- package/dist/init-template/package.json +19 -11
- package/dist/init-template/tsconfig.json +5 -6
- package/package.json +13 -4
- package/dist/init-template/app/browser/jest.config.js +0 -11
- package/dist/init-template/app/browser/src/plugin/index.test.ts +0 -6
- package/dist/init-template/app/server-azure-function/.nvmrc +0 -1
- package/dist/init-template/app/server-azure-function/package.json +0 -51
- package/dist/init-template/app/server-azure-function/src/functions/pollingExample.ts +0 -39
- package/dist/init-template/app/server-azure-function/src/functions/queueExample.ts +0 -67
- package/dist/init-template/app/server-azure-function/src/functions/seekaAppWebhook.ts +0 -236
- package/dist/init-template/app/server-azure-function/src/lib/browser/index.ts +0 -55
- package/dist/init-template/app/server-azure-function/src/lib/jobs/index.ts +0 -96
- package/dist/init-template/app/server-azure-function/src/lib/logging/index.ts +0 -93
- package/dist/init-template/app/server-azure-function/src/lib/models/index.ts +0 -7
- package/dist/init-template/app/server-azure-function/src/lib/services/index.ts +0 -41
- package/dist/init-template/app/server-azure-function/src/lib/state/redis/index.ts +0 -96
- package/dist/init-template/app/server-azure-function/src/lib/state/seeka/installations.ts +0 -65
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@seeka-app-example-name/server-azure-function",
|
|
3
|
-
"version": "2.2.1",
|
|
4
|
-
"description": "Seeka example app with hosting on Azure serverless functions",
|
|
5
|
-
"author": "Seeka <platform@seeka.co>",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"main": "dist/src/functions/*.js",
|
|
8
|
-
"private": true,
|
|
9
|
-
"engines": {
|
|
10
|
-
"node": ">=20"
|
|
11
|
-
},
|
|
12
|
-
"scripts": {
|
|
13
|
-
"lint": "eslint",
|
|
14
|
-
"build": "tsc",
|
|
15
|
-
"watch": "tsc -w",
|
|
16
|
-
"clean": "rimraf dist",
|
|
17
|
-
"prestart": "<packageManagerRunPrefix> clean && yarn build",
|
|
18
|
-
"dev": "func start --port 7072",
|
|
19
|
-
"tunnel": "node scripts/ngrok.js seeka-app-example-name-localdev",
|
|
20
|
-
"deploy": "yarn func azure functionapp publish seeka-app-example-name --no-build --javascript",
|
|
21
|
-
"dev:queue:create": "node scripts/dev-queue-setup.js sample-queue-name"
|
|
22
|
-
},
|
|
23
|
-
"dependencies": {
|
|
24
|
-
"@azure/functions": "^4.7.2",
|
|
25
|
-
"@azure/storage-queue": "^12.26.0",
|
|
26
|
-
"@datalust/winston-seq": "^2.0.0",
|
|
27
|
-
"@redis/client": "^5.1.0",
|
|
28
|
-
"@redis/json": "^5.1.0",
|
|
29
|
-
"@seeka-labs/sdk-apps-server": "2.0.7-rc.2",
|
|
30
|
-
"axios": "^1.9.0",
|
|
31
|
-
"lodash": "^4.17.21",
|
|
32
|
-
"redis": "^5.1.0",
|
|
33
|
-
"winston": "^3.17.0"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@jest/globals": "^29.7.0",
|
|
37
|
-
"@types/jest": "^29.5.14",
|
|
38
|
-
"@types/lodash": "^4.17.17",
|
|
39
|
-
"@types/lodash-es": "^4.17.12",
|
|
40
|
-
"@types/node": "^24",
|
|
41
|
-
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
|
42
|
-
"@typescript-eslint/parser": "^8.32.1",
|
|
43
|
-
"azure-functions-core-tools": "*",
|
|
44
|
-
"eslint": "^9",
|
|
45
|
-
"jest": "^29.7.0",
|
|
46
|
-
"ngrok": "^5.0.0-beta.2",
|
|
47
|
-
"rimraf": "^6.0.1",
|
|
48
|
-
"ts-jest": "^29.3.4",
|
|
49
|
-
"typescript": "^5.8.3"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import winston from 'winston';
|
|
2
|
-
|
|
3
|
-
import { app, InvocationContext, Timer } from '@azure/functions';
|
|
4
|
-
import { QueueClient } from '@azure/storage-queue';
|
|
5
|
-
|
|
6
|
-
import { jobNames, queueNames, triggerBackgroundJobWithQueue } from '../lib/jobs';
|
|
7
|
-
import { backgroundJobLogger } from '../lib/logging';
|
|
8
|
-
import { startServices } from '../lib/services';
|
|
9
|
-
import { listInstallations } from '../lib/state/seeka/installations';
|
|
10
|
-
|
|
11
|
-
// https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=python-v2%2Cisolated-process%2Cnodejs-v4&pivots=programming-language-typescript
|
|
12
|
-
app.timer('pollingExample', {
|
|
13
|
-
schedule: '0 0 * * * *', // every 1 hour
|
|
14
|
-
handler: pollingExample
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export async function pollingExample(myTimer: Timer, context: InvocationContext): Promise<void> {
|
|
18
|
-
const logger = backgroundJobLogger(jobNames.pollingExample, undefined, context);
|
|
19
|
-
|
|
20
|
-
logger.profile(`job.${jobNames.pollingExample}`)
|
|
21
|
-
logger.debug('Received request to trigger scheduled job');
|
|
22
|
-
|
|
23
|
-
await startServices(logger);
|
|
24
|
-
|
|
25
|
-
const allInstallations = await listInstallations(logger);
|
|
26
|
-
logger.verbose(`Triggering background job for ${allInstallations.length} installations`)
|
|
27
|
-
|
|
28
|
-
const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, queueNames.queueItemExampleQueueName);
|
|
29
|
-
const promises = allInstallations.map(installation => triggerBackgroundJobWithQueue(queueClient, { ...installation, causationId: context.invocationId, correlationId: context.invocationId }, logger))
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
await Promise.all(promises);
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
logger.error('Error triggering background jobs', { ex: winston.exceptions.getAllInfo(err) })
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
logger.profile(`job.${jobNames.pollingExample}`)
|
|
39
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import winston from 'winston';
|
|
2
|
-
|
|
3
|
-
import { app, InvocationContext } from '@azure/functions';
|
|
4
|
-
|
|
5
|
-
import { BackgroundJobRequestContext, deserialiseQueuePayload, queueNames, sendQueueMessageToPoisonQueue } from '../lib/jobs';
|
|
6
|
-
import { backgroundJobLogger } from '../lib/logging';
|
|
7
|
-
import { startServices } from '../lib/services';
|
|
8
|
-
import { groupBy } from 'lodash';
|
|
9
|
-
import { SeekaAppInstallState, tryGetInstallation } from '../lib/state/seeka/installations';
|
|
10
|
-
import { SeekaActivityAcceptedWebhookContent } from '@seeka-labs/sdk-apps-server';
|
|
11
|
-
|
|
12
|
-
app.storageQueue('queueExample', {
|
|
13
|
-
queueName: queueNames.queueItemExampleQueueName,
|
|
14
|
-
connection: 'AzureWebJobsStorage',
|
|
15
|
-
handler: queueExample
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export interface MyQueueItem extends BackgroundJobRequestContext {
|
|
19
|
-
items: SeekaActivityAcceptedWebhookContent[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function queueExample(queueItem: any, context: InvocationContext): Promise<void> {
|
|
23
|
-
const logger = backgroundJobLogger(queueNames.queueItemExampleQueueName, undefined, context);
|
|
24
|
-
logger.profile(`queue.${queueNames.queueItemExampleQueueName}`)
|
|
25
|
-
try {
|
|
26
|
-
// queueItem can either be a single item or an array of items
|
|
27
|
-
const payload = deserialiseQueuePayload<MyQueueItem>(queueItem, logger);
|
|
28
|
-
|
|
29
|
-
// Group by applicationInstallId
|
|
30
|
-
const grouped = groupBy(payload, e => e.applicationInstallId);
|
|
31
|
-
|
|
32
|
-
logger.verbose('Received queue batch to handle queue message', { batchSize: payload.length });
|
|
33
|
-
|
|
34
|
-
// Process each group
|
|
35
|
-
await startServices(logger);
|
|
36
|
-
for (const [applicationInstallId, items] of Object.entries(grouped)) {
|
|
37
|
-
if (items.length === 0) {
|
|
38
|
-
logger.warn('No items to process for applicationInstallId', { applicationInstallId });
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const thisLogger = backgroundJobLogger(queueNames.queueItemExampleQueueName, items[0], context);
|
|
42
|
-
try {
|
|
43
|
-
const installation = await tryGetInstallation(applicationInstallId, true, thisLogger) as SeekaAppInstallState;
|
|
44
|
-
|
|
45
|
-
// Execute sync
|
|
46
|
-
// const batchItems = items.flatMap(e => e.items || []).filter(Boolean)
|
|
47
|
-
// await executeLongRunningTask(batchItems, logger);
|
|
48
|
-
}
|
|
49
|
-
catch (err) {
|
|
50
|
-
thisLogger.error('Error handling queue item to handle queue message', { ex: winston.exceptions.getAllInfo(err) })
|
|
51
|
-
await sendQueueMessageToPoisonQueue(queueNames.queueItemExampleQueueName, {
|
|
52
|
-
...items[0],
|
|
53
|
-
causationId: items[0].causationId,
|
|
54
|
-
correlationId: context.invocationId,
|
|
55
|
-
rows: items.flatMap(e => e.items || []).filter(Boolean)
|
|
56
|
-
} as MyQueueItem, thisLogger);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
logger.error('Error handling queue item to handle queue message', { ex: winston.exceptions.getAllInfo(err) })
|
|
62
|
-
throw err; // Will retry based on host.json > extensions.queues.maxDequeueCount and then push to poison queue
|
|
63
|
-
}
|
|
64
|
-
finally {
|
|
65
|
-
logger.profile(`queue.${queueNames.queueItemExampleQueueName}`)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import winston from 'winston';
|
|
2
|
-
|
|
3
|
-
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
|
|
4
|
-
import {
|
|
5
|
-
SeekaActivityAcceptedWebhookPayload, SeekaAppInstalledWebhookPayload,
|
|
6
|
-
SeekaAppInstallSettingsUpdatedWebhookPayload, SeekaAppUninstalledWebhookPayload,
|
|
7
|
-
SeekaIdentityChangedWebhookPayload, SeekaWebhookCallType, SeekaWebhookPayload,
|
|
8
|
-
throwOnInvalidWebhookSignature
|
|
9
|
-
} from '@seeka-labs/sdk-apps-server';
|
|
10
|
-
|
|
11
|
-
import type { Headers as UndiciHeaders } from 'undici';
|
|
12
|
-
import { getSeekaBrowserPlugin } from '../lib/browser';
|
|
13
|
-
import { queueNames, triggerBackgroundJob } from '../lib/jobs';
|
|
14
|
-
|
|
15
|
-
import { webhookLogger } from '../lib/logging';
|
|
16
|
-
import { startServices } from '../lib/services';
|
|
17
|
-
import {
|
|
18
|
-
createOrUpdateInstallation, deleteInstallation, SampleAppInstallSettings, SeekaAppInstallState, tryGetInstallation
|
|
19
|
-
} from '../lib/state/seeka/installations';
|
|
20
|
-
|
|
21
|
-
import type { Logger } from 'winston';
|
|
22
|
-
import { MyQueueItem } from './queueExample';
|
|
23
|
-
app.http('seekaAppWebhook', {
|
|
24
|
-
methods: ['POST'],
|
|
25
|
-
authLevel: 'anonymous',
|
|
26
|
-
route: 'webhook/seeka/app',
|
|
27
|
-
handler: seekaAppWebhook
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
export async function seekaAppWebhook(req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
|
|
31
|
-
const bodyStr = (await req.text()) as string;
|
|
32
|
-
if (!bodyStr) {
|
|
33
|
-
return {
|
|
34
|
-
status: 400,
|
|
35
|
-
jsonBody: { error: "Body missing. Ensure body is present and either specify the Content-Length request header OR set Transfer-Encoding request header to 'chunked'" }
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const body = JSON.parse(bodyStr) as SeekaWebhookPayload;
|
|
39
|
-
|
|
40
|
-
const logger = webhookLogger(body, context);
|
|
41
|
-
logger.profile('http.seeka.webhook.app')
|
|
42
|
-
logger.verbose('Received webhook from Seeka', { body });
|
|
43
|
-
|
|
44
|
-
// Handle probe
|
|
45
|
-
if (body.type === SeekaWebhookCallType.Probe) {
|
|
46
|
-
return {
|
|
47
|
-
status: 204
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Validate webhook
|
|
52
|
-
try {
|
|
53
|
-
throwOnInvalidWebhookSignature(process.env.SEEKA_APP_SECRET as string, req.headers as UndiciHeaders, bodyStr);
|
|
54
|
-
logger.debug('Webhook signature validated', { body });
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
logger.warn('Webhook signature invalid', { body });
|
|
58
|
-
return {
|
|
59
|
-
status: 401,
|
|
60
|
-
jsonBody: { error: "Webhook call invalid" }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (body.isTest) {
|
|
65
|
-
// This is a test webhook call
|
|
66
|
-
return {
|
|
67
|
-
status: 204
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
await startServices(logger);
|
|
72
|
-
|
|
73
|
-
// Check if the webhook is for an app we have installed
|
|
74
|
-
let installation: SeekaAppInstallState | null = null;
|
|
75
|
-
if (body.type != SeekaWebhookCallType.AppInstalled) {
|
|
76
|
-
installation = await tryGetInstallation((body as SeekaAppInstalledWebhookPayload).context?.applicationInstallId as string, false, logger);
|
|
77
|
-
if (installation == null) {
|
|
78
|
-
logger.warn('Webhook call cannot be processed as the installation ID is not known by this app', { body });
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
status: 422,
|
|
82
|
-
jsonBody: { error: "App not installed" }
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Do something
|
|
88
|
-
let errorMessage: string | null = null;
|
|
89
|
-
try {
|
|
90
|
-
switch (body.type) {
|
|
91
|
-
case SeekaWebhookCallType.AppInstalled:
|
|
92
|
-
{
|
|
93
|
-
errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
case SeekaWebhookCallType.AppInstallSettingsUpdated:
|
|
97
|
-
{
|
|
98
|
-
errorMessage = await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
case SeekaWebhookCallType.AppUninstalled:
|
|
102
|
-
{
|
|
103
|
-
if (!body.isTest) {
|
|
104
|
-
const payload = body as SeekaAppUninstalledWebhookPayload;
|
|
105
|
-
await deleteInstallation(payload.context?.applicationInstallId as string, logger)
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
case SeekaWebhookCallType.ActivityAccepted:
|
|
110
|
-
{
|
|
111
|
-
const payload = body as SeekaActivityAcceptedWebhookPayload;
|
|
112
|
-
await handleSeekaActivity(payload, logger);
|
|
113
|
-
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
case SeekaWebhookCallType.IdentityChanged:
|
|
117
|
-
{
|
|
118
|
-
const payload = body as SeekaIdentityChangedWebhookPayload;
|
|
119
|
-
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
// START: component:browser
|
|
123
|
-
case SeekaWebhookCallType.BrowserSdkPlugin: {
|
|
124
|
-
const plugin = await getSeekaBrowserPlugin(installation as SeekaAppInstallState, logger);
|
|
125
|
-
|
|
126
|
-
logger.profile('http.seeka.webhook.app')
|
|
127
|
-
return {
|
|
128
|
-
status: 200,
|
|
129
|
-
body: JSON.stringify(plugin),
|
|
130
|
-
headers: {
|
|
131
|
-
'Content-Type': 'application/json'
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// END: component:browser
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
logger.error('Failed to handle webhook', { ex: winston.exceptions.getAllInfo(err) });
|
|
140
|
-
return {
|
|
141
|
-
status: 500,
|
|
142
|
-
jsonBody: { error: "Request failed" }
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
finally {
|
|
146
|
-
logger.profile('http.seeka.webhook.app')
|
|
147
|
-
logger.verbose('Seeka webhook handled');
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (errorMessage) {
|
|
151
|
-
logger.warn('Webhook call failed', { errorMessage });
|
|
152
|
-
return {
|
|
153
|
-
status: 400,
|
|
154
|
-
jsonBody: { error: { message: errorMessage } }
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
status: 204
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const validateInstallationSettings = async (installSettings: SampleAppInstallSettings, logger: Logger): Promise<string | null> => {
|
|
164
|
-
// Returning an error message string here will block the installation request or settings update request by the user installing the app
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger): Promise<string | null> => {
|
|
171
|
-
if (payload.isTest) return null;
|
|
172
|
-
|
|
173
|
-
const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
|
|
174
|
-
if (errorMessage) return errorMessage;
|
|
175
|
-
|
|
176
|
-
const installation = await createOrUpdateInstallation({
|
|
177
|
-
...payload.context,
|
|
178
|
-
installationState: {
|
|
179
|
-
grantedPermissions: payload.content?.grantedPermissions || []
|
|
180
|
-
},
|
|
181
|
-
applicationInstallId: payload.context?.applicationInstallId as string,
|
|
182
|
-
organisationBrandId: payload.context?.organisationBrandId as string,
|
|
183
|
-
organisationId: payload.context?.organisationId as string,
|
|
184
|
-
installedAt: new Date().toISOString(),
|
|
185
|
-
installationSettings: payload.content?.installationSettings || {}
|
|
186
|
-
}, logger)
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
await triggerBackgroundJob(queueNames.queueItemExampleQueueName, {
|
|
190
|
-
...payload.context,
|
|
191
|
-
causationId: payload.causationId,
|
|
192
|
-
correlationId: payload.requestId
|
|
193
|
-
}, logger)
|
|
194
|
-
}
|
|
195
|
-
catch (err) {
|
|
196
|
-
await deleteInstallation(installation.applicationInstallId, logger)
|
|
197
|
-
return 'Failed to complete install';
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger): Promise<string | null> => {
|
|
204
|
-
if (payload.isTest) return null;
|
|
205
|
-
|
|
206
|
-
const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
|
|
207
|
-
if (errorMessage) return errorMessage;
|
|
208
|
-
|
|
209
|
-
const existingInstallation = await tryGetInstallation(payload.context?.applicationInstallId as string, true, logger) as SeekaAppInstallState;
|
|
210
|
-
const installation = await createOrUpdateInstallation({
|
|
211
|
-
...existingInstallation,
|
|
212
|
-
installationState: {
|
|
213
|
-
...existingInstallation.installationState,
|
|
214
|
-
grantedPermissions: payload.content?.grantedPermissions || []
|
|
215
|
-
},
|
|
216
|
-
installationSettings: payload.content?.installationSettings || {},
|
|
217
|
-
}, logger)
|
|
218
|
-
|
|
219
|
-
await triggerBackgroundJob(queueNames.queueItemExampleQueueName, {
|
|
220
|
-
...payload.context,
|
|
221
|
-
causationId: payload.causationId,
|
|
222
|
-
correlationId: payload.requestId
|
|
223
|
-
}, logger)
|
|
224
|
-
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, logger: Logger) => {
|
|
229
|
-
// Will be handled by queueExample
|
|
230
|
-
await triggerBackgroundJob(queueNames.queueItemExampleQueueName, {
|
|
231
|
-
...activity.context,
|
|
232
|
-
causationId: activity.causationId,
|
|
233
|
-
correlationId: activity.requestId,
|
|
234
|
-
items: [activity]
|
|
235
|
-
} as MyQueueItem, logger)
|
|
236
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { SeekaBrowserSdkPluginWebhookResponse } from '@seeka-labs/sdk-apps-server';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import util from 'util';
|
|
5
|
-
import winston, { Logger } from 'winston';
|
|
6
|
-
import type { ISampleAppBrowserSdkPluginConfig } from '../models';
|
|
7
|
-
import {
|
|
8
|
-
SeekaAppInstallState
|
|
9
|
-
} from '../state/seeka/installations';
|
|
10
|
-
|
|
11
|
-
const fileExistsAsync = util.promisify(fs.exists);
|
|
12
|
-
const readFileAsync = util.promisify(fs.readFile);
|
|
13
|
-
|
|
14
|
-
export async function getSeekaBrowserPlugin(installation: SeekaAppInstallState, logger: Logger): Promise<SeekaBrowserSdkPluginWebhookResponse> {
|
|
15
|
-
logger.profile('http.seeka.browser.plugin')
|
|
16
|
-
|
|
17
|
-
let content: string | undefined;
|
|
18
|
-
let init: string | undefined;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
logger.verbose('Handling request for browser plugin content');
|
|
22
|
-
const browserPluginPath = path.resolve('./dist/src/browser/seeka-app-example-name.min.js');
|
|
23
|
-
if (await fileExistsAsync(browserPluginPath)) {
|
|
24
|
-
// Content of the script. This should not change per installation it should be the same for all installations
|
|
25
|
-
content = await readFileAsync(browserPluginPath, 'utf8');
|
|
26
|
-
|
|
27
|
-
// Configuration for the plugin. This can be different per installation.
|
|
28
|
-
// Configuration is sent to the browser so NO SENSITIVE CONFIG / SERVER SECRETS SHOULD BE SPECIFIED HERE.
|
|
29
|
-
const pluginConfig = {
|
|
30
|
-
myAppInstallSetting1: installation.installationSettings.myAppInstallSetting1,
|
|
31
|
-
myAppInstallSetting2: installation.installationSettings.myAppInstallSetting2,
|
|
32
|
-
appId: process.env.SEEKA_APP_ID,
|
|
33
|
-
appInstallId: installation.applicationInstallId,
|
|
34
|
-
appUrl: process.env.SELF_HOST_BASEURL
|
|
35
|
-
} as ISampleAppBrowserSdkPluginConfig;
|
|
36
|
-
init = `SampleAppConvergeSdkPlugin(${JSON.stringify(pluginConfig)})`;
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
logger.error('Browser plugin content not found', { path: browserPluginPath });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
logger.error('Failed to handle request for browser plugin content', { ex: winston.exceptions.getAllInfo(err) });
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
logger.profile('http.seeka.browser.plugin')
|
|
47
|
-
logger.verbose('Request for browser plugin content handled');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!content || !init) {
|
|
51
|
-
throw new Error('Failed to handle request for browser plugin content')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { content, init: init }
|
|
55
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { QueueClient } from '@azure/storage-queue';
|
|
2
|
-
import { isArray } from 'lodash';
|
|
3
|
-
import type { Logger } from 'winston';
|
|
4
|
-
import winston from 'winston';
|
|
5
|
-
|
|
6
|
-
export interface BackgroundJobRequestContext {
|
|
7
|
-
organisationId?: string;
|
|
8
|
-
organisationBrandId?: string;
|
|
9
|
-
applicationInstallId?: string;
|
|
10
|
-
causationId: string
|
|
11
|
-
correlationId: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const signatureHeaderName = 'x-signature-hmac';
|
|
15
|
-
export const apiKeyHeaderName = 'x-api-key';
|
|
16
|
-
|
|
17
|
-
export const jobNames = {
|
|
18
|
-
pollingExample: 'polling-example',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const queueNames = {
|
|
22
|
-
queueItemExampleQueueName: 'sample-queue-name'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const triggerBackgroundJob = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
26
|
-
const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, queueName);
|
|
27
|
-
return triggerBackgroundJobWithQueue(queueClient, context, logger);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const serialiseQueuePayload = (payload: unknown): string => {
|
|
31
|
-
const jsonString = JSON.stringify(payload)
|
|
32
|
-
return Buffer.from(jsonString).toString('base64')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const deserialiseQueuePayload = <TPayload>(queueItem: any, logger: Logger): TPayload[] => {
|
|
36
|
-
try {
|
|
37
|
-
if (typeof queueItem !== 'string') {
|
|
38
|
-
if (isArray(queueItem)) {
|
|
39
|
-
return queueItem
|
|
40
|
-
}
|
|
41
|
-
return [queueItem]
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
const jsonString = Buffer.from(queueItem, 'base64').toString()
|
|
45
|
-
const parsed = JSON.parse(jsonString);
|
|
46
|
-
|
|
47
|
-
if (isArray(parsed)) {
|
|
48
|
-
return parsed
|
|
49
|
-
}
|
|
50
|
-
return [parsed]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
logger.error('Failed to deserialise queue payload', { ex: winston.exceptions.getAllInfo(err), queueItem });
|
|
55
|
-
throw new Error(`Failed to deserialise queue payload`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const triggerBackgroundJobWithQueue = async (queueClient: QueueClient, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
60
|
-
const body = {
|
|
61
|
-
...context
|
|
62
|
-
}
|
|
63
|
-
const bodyStr = serialiseQueuePayload(body);
|
|
64
|
-
|
|
65
|
-
const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
|
|
66
|
-
|
|
67
|
-
if (response.errorCode) {
|
|
68
|
-
const { requestId, date, errorCode } = response;
|
|
69
|
-
logger.error("Failed to trigger background job", { body, requestId, date, errorCode })
|
|
70
|
-
throw new Error(`Failed to trigger background job: ${response.errorCode}`);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
logger.debug("Background job triggered", { body, messageId: response.messageId, context })
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const sendQueueMessageToPoisonQueue = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
78
|
-
const body = {
|
|
79
|
-
...context
|
|
80
|
-
}
|
|
81
|
-
const bodyStr = serialiseQueuePayload(body);
|
|
82
|
-
|
|
83
|
-
// https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-typescript#poison-messages
|
|
84
|
-
const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, `${queueName}-poison`);
|
|
85
|
-
|
|
86
|
-
const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
|
|
87
|
-
|
|
88
|
-
if (response.errorCode) {
|
|
89
|
-
const { requestId, date, errorCode } = response;
|
|
90
|
-
logger.error("Failed to push to poison queue", { body, requestId, date, errorCode })
|
|
91
|
-
throw new Error(`Failed to push to poison queue: ${response.errorCode}`);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
logger.verbose("Message pushed to poison queue", { body, messageId: response.messageId, context })
|
|
95
|
-
}
|
|
96
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import * as winston from 'winston';
|
|
2
|
-
|
|
3
|
-
import { InvocationContext } from '@azure/functions';
|
|
4
|
-
import { SeqTransport } from '@datalust/winston-seq';
|
|
5
|
-
|
|
6
|
-
import * as packageJson from '../../../package.json';
|
|
7
|
-
import { BackgroundJobRequestContext } from '../jobs';
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
SeekaWebhookPayload, SeekaWebhookPayloadOfSeekaAppWebhookContext
|
|
11
|
-
} from '@seeka-labs/sdk-apps-server';
|
|
12
|
-
|
|
13
|
-
const loggerTransports: winston.transport[] = [
|
|
14
|
-
new winston.transports.Console({
|
|
15
|
-
level: process.env.LOGGING_LEVEL,
|
|
16
|
-
format: winston.format.combine(
|
|
17
|
-
winston.format.errors({ stack: true }),
|
|
18
|
-
winston.format.cli(),
|
|
19
|
-
winston.format.splat(),
|
|
20
|
-
),
|
|
21
|
-
handleExceptions: true,
|
|
22
|
-
handleRejections: true,
|
|
23
|
-
}),
|
|
24
|
-
]
|
|
25
|
-
if (process.env.LOGGING_SEQ_SERVERURL) {
|
|
26
|
-
loggerTransports.push(
|
|
27
|
-
new SeqTransport({
|
|
28
|
-
level: process.env.LOGGING_LEVEL,
|
|
29
|
-
serverUrl: process.env.LOGGING_SEQ_SERVERURL,
|
|
30
|
-
apiKey: process.env.LOGGING_SEQ_APIKEY,
|
|
31
|
-
onError: ((e) => { console.error('Failed to configure Seq logging transport', e) }),
|
|
32
|
-
format: winston.format.combine(
|
|
33
|
-
winston.format.errors({ stack: true }),
|
|
34
|
-
winston.format.json(),
|
|
35
|
-
),
|
|
36
|
-
handleExceptions: true,
|
|
37
|
-
handleRejections: true,
|
|
38
|
-
})
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const logger = winston.createLogger({
|
|
43
|
-
level: process.env.LOGGING_LEVEL,
|
|
44
|
-
defaultMeta: {
|
|
45
|
-
seekaAppId: process.env.SEEKA_APP_ID,
|
|
46
|
-
Hosting_Provider: 'azure',
|
|
47
|
-
Release_Version: `v${packageJson.version}`,
|
|
48
|
-
Hosting_Region: process.env.REGION_NAME,
|
|
49
|
-
Service_Instance_Id: process.env.WEBSITE_ROLE_INSTANCE_ID,
|
|
50
|
-
AzureFunctions_FunctionName: process.env.WEBSITE_SITE_NAME,
|
|
51
|
-
Service: `app/${packageJson.name}`,
|
|
52
|
-
},
|
|
53
|
-
transports: loggerTransports,
|
|
54
|
-
exitOnError: false,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
export const childLogger = (meta: object) => logger.child(meta);
|
|
58
|
-
|
|
59
|
-
export const webhookLogger = (payload: SeekaWebhookPayload, functionContext: InvocationContext) => {
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
-
const meta: any = {
|
|
62
|
-
seekaWebhookType: payload.type,
|
|
63
|
-
seekaWebhookIsTest: payload.isTest,
|
|
64
|
-
RequestId: payload.requestId,
|
|
65
|
-
AzureFunctions_InvocationId: functionContext.invocationId,
|
|
66
|
-
CausationId: payload.causationId,
|
|
67
|
-
CorrelationId: payload.causationId,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const context = (payload as SeekaWebhookPayloadOfSeekaAppWebhookContext).context;
|
|
71
|
-
if (context) {
|
|
72
|
-
meta.seekaAppInstallId = context.applicationInstallId;
|
|
73
|
-
meta.seekaAppInstallOrganisationBrandId = context.organisationBrandId;
|
|
74
|
-
meta.seekaAppInstallOrganisationId = context.organisationId;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return childLogger(meta)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const backgroundJobLogger = (jobName: string, jobContext: BackgroundJobRequestContext | undefined, functionContext: InvocationContext) => {
|
|
81
|
-
const meta: object = {
|
|
82
|
-
jobContext,
|
|
83
|
-
jobName,
|
|
84
|
-
AzureFunctions_InvocationId: functionContext.invocationId,
|
|
85
|
-
CausationId: jobContext?.causationId,
|
|
86
|
-
CorrelationId: jobContext?.causationId,
|
|
87
|
-
seekaAppInstallId: jobContext?.applicationInstallId,
|
|
88
|
-
seekaAppInstallOrganisationBrandId: jobContext?.organisationBrandId,
|
|
89
|
-
seekaAppInstallOrganisationId: jobContext?.organisationId
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return childLogger(meta)
|
|
93
|
-
}
|