@seeka-labs/cli-apps 1.1.38 → 2.0.7-rc.1

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.
Files changed (95) hide show
  1. package/dist/index.js +31 -36
  2. package/dist/index.js.map +4 -4
  3. package/dist/init-template/.gitlab-ci.yml +47 -0
  4. package/dist/init-template/.yarnrc.yml +8 -0
  5. package/dist/init-template/README.md +7 -0
  6. package/dist/{init-templates → init-template/app}/browser/jest.config.js +11 -11
  7. package/dist/{init-templates → init-template/app}/browser/package.json +16 -12
  8. package/dist/{init-templates → init-template/app}/browser/scripts/esbuild/build-browser-plugin.mjs +110 -110
  9. package/dist/{init-templates → init-template/app}/browser/scripts/esbuild/plugins/importAsGlobals.mjs +38 -38
  10. package/dist/{init-templates → init-template/app}/browser/src/browser.ts +12 -12
  11. package/dist/{init-templates → init-template/app}/browser/src/plugin/index.test.ts +6 -6
  12. package/dist/{init-templates → init-template/app}/browser/src/plugin/index.ts +47 -49
  13. package/dist/{init-templates → init-template/app}/browser/tsconfig.json +34 -34
  14. package/dist/{init-templates/netlify-function → init-template/app/server-azure-function}/.eslintrc.cjs +2 -10
  15. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/.funcignore +12 -7
  16. package/dist/init-template/app/server-azure-function/.nvmrc +1 -0
  17. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/README.md +104 -107
  18. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/host.json +1 -10
  19. package/dist/init-template/app/server-azure-function/package.json +52 -0
  20. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/functions/seekaAppWebhook.ts +235 -236
  21. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/lib/browser/index.ts +54 -54
  22. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/lib/jobs/index.ts +1 -1
  23. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/lib/logging/index.ts +92 -92
  24. package/dist/{init-templates/azure-function/src/lib/browser → init-template/app/server-azure-function/src/lib}/models/index.ts +6 -6
  25. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/lib/state/redis/index.ts +96 -65
  26. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/lib/state/seeka/installations.ts +64 -66
  27. package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/tsconfig.json +1 -1
  28. package/dist/init-template/package.json +28 -0
  29. package/dist/init-template/tsconfig.json +25 -0
  30. package/package.json +5 -4
  31. package/dist/init-templates/aws-lambda/.env.example +0 -15
  32. package/dist/init-templates/aws-lambda/.eslintrc.cjs +0 -19
  33. package/dist/init-templates/aws-lambda/.example.gitignore +0 -49
  34. package/dist/init-templates/aws-lambda/.gitlab-ci.yml +0 -39
  35. package/dist/init-templates/aws-lambda/.nvmrc +0 -1
  36. package/dist/init-templates/aws-lambda/.vscode/extensions.json +0 -5
  37. package/dist/init-templates/aws-lambda/.vscode/launch.json +0 -20
  38. package/dist/init-templates/aws-lambda/.vscode/settings.json +0 -3
  39. package/dist/init-templates/aws-lambda/.vscode/tasks.json +0 -12
  40. package/dist/init-templates/aws-lambda/README.md +0 -77
  41. package/dist/init-templates/aws-lambda/jest.config.js +0 -5
  42. package/dist/init-templates/aws-lambda/package.json +0 -53
  43. package/dist/init-templates/aws-lambda/scripts/ngrok.js +0 -28
  44. package/dist/init-templates/aws-lambda/src/index.test.ts +0 -7
  45. package/dist/init-templates/aws-lambda/src/index.ts +0 -33
  46. package/dist/init-templates/aws-lambda/src/lib/logging/index.ts +0 -88
  47. package/dist/init-templates/aws-lambda/src/lib/services/index.ts +0 -41
  48. package/dist/init-templates/aws-lambda/src/lib/state/redis/index.ts +0 -65
  49. package/dist/init-templates/aws-lambda/src/lib/state/seeka/installations.ts +0 -67
  50. package/dist/init-templates/aws-lambda/src/routes/seekaAppWebhook.ts +0 -194
  51. package/dist/init-templates/aws-lambda/tsconfig.json +0 -31
  52. package/dist/init-templates/aws-lambda/yarn.lock +0 -4392
  53. package/dist/init-templates/azure-function/.eslintrc.cjs +0 -19
  54. package/dist/init-templates/azure-function/.example.gitignore +0 -48
  55. package/dist/init-templates/azure-function/.gitlab-ci.yml +0 -48
  56. package/dist/init-templates/azure-function/.nvmrc +0 -1
  57. package/dist/init-templates/azure-function/.vscode/extensions.json +0 -7
  58. package/dist/init-templates/azure-function/.vscode/launch.json +0 -13
  59. package/dist/init-templates/azure-function/.vscode/settings.json +0 -9
  60. package/dist/init-templates/azure-function/.vscode/tasks.json +0 -39
  61. package/dist/init-templates/azure-function/jest.config.js +0 -5
  62. package/dist/init-templates/azure-function/package.json +0 -47
  63. package/dist/init-templates/azure-function/scripts/dev-queue-setup.js +0 -30
  64. package/dist/init-templates/azure-function/src/index.test.ts +0 -7
  65. package/dist/init-templates/azure-function/yarn.lock +0 -3772
  66. package/dist/init-templates/browser/.editorconfig +0 -14
  67. package/dist/init-templates/browser/.eslintrc.cjs +0 -1
  68. package/dist/init-templates/browser/.yarnrc +0 -1
  69. package/dist/init-templates/browser/yarn.lock +0 -3045
  70. package/dist/init-templates/netlify-function/.env.example +0 -18
  71. package/dist/init-templates/netlify-function/.example.gitignore +0 -36
  72. package/dist/init-templates/netlify-function/.nvmrc +0 -1
  73. package/dist/init-templates/netlify-function/.vscode/launch.json +0 -45
  74. package/dist/init-templates/netlify-function/README.md +0 -62
  75. package/dist/init-templates/netlify-function/jest.config.js +0 -5
  76. package/dist/init-templates/netlify-function/netlify.toml +0 -7
  77. package/dist/init-templates/netlify-function/package.json +0 -39
  78. package/dist/init-templates/netlify-function/src/api/example-job-background/index.ts +0 -52
  79. package/dist/init-templates/netlify-function/src/api/polling-example-job-scheduled/index.ts +0 -46
  80. package/dist/init-templates/netlify-function/src/api/seeka-app-webhook/index.ts +0 -217
  81. package/dist/init-templates/netlify-function/src/index.test.ts +0 -7
  82. package/dist/init-templates/netlify-function/src/lib/jobs/index.ts +0 -68
  83. package/dist/init-templates/netlify-function/src/lib/logging/index.ts +0 -91
  84. package/dist/init-templates/netlify-function/src/lib/services/index.ts +0 -41
  85. package/dist/init-templates/netlify-function/src/lib/state/redis/index.ts +0 -65
  86. package/dist/init-templates/netlify-function/src/lib/state/seeka/installations.ts +0 -67
  87. package/dist/init-templates/netlify-function/tsconfig.json +0 -25
  88. package/dist/init-templates/netlify-function/yarn.lock +0 -9337
  89. /package/dist/{init-templates → init-template/app}/browser/README.md +0 -0
  90. /package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/local.settings.example.json +0 -0
  91. /package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/scripts/ngrok.js +0 -0
  92. /package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/functions/healthCheck.ts +0 -0
  93. /package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/functions/pollingExample.ts +0 -0
  94. /package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/functions/queueExample.ts +0 -0
  95. /package/dist/{init-templates/azure-function → init-template/app/server-azure-function}/src/lib/services/index.ts +0 -0
@@ -1,18 +0,0 @@
1
- SEEKA_APP_ID=
2
- SEEKA_APP_SECRET=
3
- SEEKA_DEBUG_ENABLED=true
4
- SEEKA_INGEST_URL=
5
- SEEKA_ISSUER_URL=
6
- NODE_TLS_REJECT_UNAUTHORIZED=1
7
- NETLIFY_ENABLED=false
8
- NETLIFY_SITE_NAME=
9
- REDIS_CONNECTION_USER=default
10
- REDIS_CONNECTION_PASSWORD=
11
- REDIS_CONNECTION_TLS=true
12
- REDIS_CONNECTION_HOST=
13
- REDIS_CONNECTION_PORT=
14
- LOGGING_SEQ_SERVERURL=
15
- LOGGING_SEQ_APIKEY=
16
- LOGGING_LEVEL=silly
17
- ENCRYPTION_SECRET=
18
- INBOUND_HTTP_CALL_API_KEY=
@@ -1,36 +0,0 @@
1
- # Local Netlify folder
2
- .netlify
3
- .env
4
-
5
- # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
6
-
7
- # dependencies
8
- /node_modules
9
- /.pnp
10
- .pnp.js
11
- .yarn/install-state.gz
12
-
13
- # testing
14
- /coverage
15
-
16
- # production
17
- /build
18
- /dist
19
-
20
- # misc
21
- .DS_Store
22
- *.pem
23
-
24
- # debug
25
- npm-debug.log*
26
- yarn-debug.log*
27
- yarn-error.log*
28
-
29
- # local env files
30
- .env*.local
31
-
32
- # vercel
33
- .vercel
34
-
35
- # typescript
36
- *.tsbuildinfo
@@ -1 +0,0 @@
1
- v20
@@ -1,45 +0,0 @@
1
- {
2
- "version": "0.2.0",
3
- "configurations": [
4
- {
5
- "name": "netlify dev",
6
- "type": "node",
7
- "request": "launch",
8
- "skipFiles": [
9
- "<node_internals>/**"
10
- ],
11
- "outFiles": [
12
- "${workspaceFolder}/.netlify/functions-serve/**/*.js"
13
- ],
14
- "program": "${workspaceFolder}/node_modules/.bin/netlify",
15
- "args": [
16
- "dev"
17
- ],
18
- "console": "integratedTerminal",
19
- "env": {
20
- "BROWSER": "none"
21
- },
22
- "serverReadyAction": {
23
- "pattern": "Server now ready on (https?://[\\w:.-]+)",
24
- "uriFormat": "%s",
25
- "action": "debugWithChrome"
26
- }
27
- },
28
- {
29
- "name": "netlify functions:serve",
30
- "type": "node",
31
- "request": "launch",
32
- "skipFiles": [
33
- "<node_internals>/**"
34
- ],
35
- "outFiles": [
36
- "${workspaceFolder}/.netlify/functions-serve/**/*.js"
37
- ],
38
- "program": "${workspaceFolder}/node_modules/.bin/netlify",
39
- "args": [
40
- "functions:serve"
41
- ],
42
- "console": "integratedTerminal"
43
- }
44
- ]
45
- }
@@ -1,62 +0,0 @@
1
- # Seeka app - Netlify functions
2
-
3
- ## Development
4
-
5
- - `yarn install`
6
- - `yarn dev`
7
-
8
- ### Dependencies
9
- - Python >=3.6.0 - https://www.python.org/downloads/ or `choco install python`
10
-
11
- ### Invoking functions
12
- Run `yarn dev:netlify:invoke:polling-example-job-scheduled` to trigger scheduled job which triggers multiple background jobs, 1 for each installation of the app
13
-
14
- ### Debugging / local development before deployment
15
- - This template uses [Netlify's live url debugging](https://answers.netlify.com/t/netlify-dev-with-support-for-custom-live-urls/101261)
16
- - Supports VSCode debugging - See `.vscode/launch.json`. This is only supported on Linux. If using Windows then use WSL with an Ubuntu distro for support of attaching the VS code debugger
17
-
18
- ## Logging
19
- Centralised logging handled by [Winston](https://www.npmjs.com/package/winston). Winston is installed in this template and is also used in the Seeka SDK NPM packages.
20
-
21
- [Seq](https://datalust.co/seq) Winston transport is installed in this template (optional).
22
-
23
- To configure Seq options, see `_SEQ_` environment variables.
24
-
25
- To install Seq on your development machine
26
- 1. Run `docker run --name seq -d --restart=always -e ACCEPT_EULA=Y -p 5341:80 datalust/seq:2024.2`
27
- 2. Update your `LOGGING_SEQ_SERVERURL` environment variable to `http://localhost:5341` or `http://[YOUR MACHINE NAME].local:5341` if using WSL.
28
-
29
- If creating an API key for Seq, only permission that is required is `Ingest`.
30
-
31
- ## State management
32
- Installations of your app and other state required for your app to function is stored in Redis. Another state provider can be swapped out for Redis, see `src/lib/state/seeka/installations.ts` for the file that manages the state of the installations.
33
-
34
- ### Upstash (optional)
35
- If you dont want to use Upstash for Redis then the connection strings in `.env` can be swapped with your Redis instance.
36
-
37
- > If using Upstash then create a database before following the below guide to deploying your Netlify function.
38
-
39
- > When choosing region - use US East (Ohio) - us-east-* as this is the same AWS region that the free Netlify functions run from. If not on the Netlify free plan, select the closest Upstash region that your Netlify function is hosted in to reduce latency between the function and the Redis database.
40
-
41
- > Considerations - max connection limit (1000) of Upstash connections in relation to concurrent execution count of the Netlify functions
42
-
43
- ## Deployment
44
- This project comes ready to deploy for free to Netlify with a database and queues backed by Redis hosted by Upstash.
45
-
46
- At the point of writing, Netlify and Upstash can be used to deploy your app for free.
47
- - https://upstash.com/pricing
48
- - https://www.netlify.com/pricing/
49
-
50
- ### Deployment to Netlify
51
- #### From your local machine
52
- - `yarn deploy`
53
-
54
- #### Using Netlify continuous delivery
55
- See the [Netlify docs](https://docs.netlify.com/functions/deploy/?fn-language=ts)
56
-
57
- ## Considerations when using this template
58
- Each call to the netlify function will create a new connection to the database/Redis as calls to the Netlify function in quick succession will not share a single "warm" function host which is supported by Azure Functions and AWS Lambda.
59
-
60
- ## References
61
- - https://docs.netlify.com/cli/get-started/
62
- - https://docs.netlify.com/functions/get-started/?fn-language=ts
@@ -1,5 +0,0 @@
1
- /** @type {import('ts-jest').JestConfigWithTsJest} */
2
- module.exports = {
3
- preset: 'ts-jest',
4
- testEnvironment: 'node',
5
- };
@@ -1,7 +0,0 @@
1
- [functions]
2
- directory = "src/api"
3
- node_bundler = "esbuild"
4
-
5
- [dev]
6
- command = "yarn dev"
7
- port = 8888
@@ -1,39 +0,0 @@
1
- {
2
- "name": "seeka-app-example-name",
3
- "version": "0.0.1",
4
- "description": "Seeka example app for hosting on Netlify functions",
5
- "author": "Seeka <platform@seeka.co>",
6
- "license": "MIT",
7
- "type": "module",
8
- "private": true,
9
- "engines": {
10
- "node": ">=20"
11
- },
12
- "scripts": {
13
- "dev:invoke:example-scheduled-job": "netlify functions:invoke --name polling-example-job-scheduled --port 8888 --identity",
14
- "lint": "eslint",
15
- "test": "yarn jest",
16
- "dev": "netlify dev --live=localdev --port 8888 --no-open",
17
- "build": "netlify functions:build --functions=dist --src=src/api",
18
- "deploy": "netlify deploy --build --skip-functions-cache --prod"
19
- },
20
- "dependencies": {
21
- "@datalust/winston-seq": "^2.0.0",
22
- "@netlify/functions": "^3.0.0",
23
- "@seeka-labs/sdk-apps-server": "^1.1.26",
24
- "lodash-es": "^4.17.21",
25
- "redis": "^4.7.0"
26
- },
27
- "devDependencies": {
28
- "@jest/globals": "^29.7.0",
29
- "@types/lodash-es": "^4.17.12",
30
- "@types/node": "^20",
31
- "@typescript-eslint/eslint-plugin": "^8.22.0",
32
- "@typescript-eslint/parser": "^8.22.0",
33
- "eslint": "^9",
34
- "jest": "^29.7.0",
35
- "ts-jest": "^29.2.5",
36
- "netlify-cli": "^18.0.3",
37
- "typescript": "^5.7.3"
38
- }
39
- }
@@ -1,52 +0,0 @@
1
- import winston, { Logger } from 'winston';
2
-
3
- import {
4
- apiKeyHeaderName, BackgroundJobRequestContext, jobNames, signatureHeaderName, validateApiKey,
5
- validateSignature
6
- } from '@/lib/jobs';
7
- import { backgroundJobLogger } from '@/lib/logging';
8
- import { startServices, stopServices } from '@/lib/services';
9
-
10
- import type { Config, Context } from "@netlify/functions"
11
- export default async (req: Request, context: Context) => {
12
- if (req.method != "POST") {
13
- return new Response("Method not allowed", { status: 405 });
14
- }
15
-
16
- const bodyStr = await req.text();
17
- const body = JSON.parse(bodyStr) as BackgroundJobRequestContext;
18
- const logger = backgroundJobLogger(jobNames.exampleBackgroundJob, body, context);
19
- logger.profile(`job.${jobNames.exampleBackgroundJob}`)
20
-
21
- await startServices(logger);
22
-
23
- if (process.env.SEEKA_DEBUG_ENABLED === 'true') {
24
- logger.debug('Received request to trigger background job', { body, headers: req.headers });
25
- }
26
- else {
27
- logger.verbose('Received request to trigger background job', { body });
28
- }
29
-
30
- try {
31
- // Verify request
32
- validateApiKey(req.headers.get(apiKeyHeaderName) as string, logger);
33
- validateSignature(req.headers.get(signatureHeaderName) as string, bodyStr, logger);
34
-
35
- // Execute
36
- await doWork(body, logger);
37
-
38
- // Close db connections
39
- await stopServices(logger);
40
- }
41
- catch (err) {
42
- logger.error('Error executing background job', { ex: winston.exceptions.getAllInfo(err) })
43
- }
44
-
45
- logger.profile(`job.${jobNames.exampleBackgroundJob}`)
46
-
47
- return new Response(undefined, { status: 202 })
48
- }
49
-
50
- const doWork = async (context: BackgroundJobRequestContext, logger: Logger) => {
51
- logger.debug('Ran background job')
52
- }
@@ -1,46 +0,0 @@
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
- }
@@ -1,217 +0,0 @@
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
- }
@@ -1,7 +0,0 @@
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
- });
@@ -1,68 +0,0 @@
1
- import * as crypto from 'crypto';
2
- import { trimEnd } from 'lodash-es';
3
-
4
- import type { Logger } from 'winston';
5
- export interface BackgroundJobRequestContext {
6
- organisationId?: string;
7
- organisationBrandId?: string;
8
- applicationInstallId?: string;
9
- }
10
-
11
- export const signatureHeaderName = 'x-signature-hmac';
12
- export const apiKeyHeaderName = 'x-api-key';
13
-
14
- export const jobNames = {
15
- exampleBackgroundJob: 'example-job-background',
16
- examplePollingScheduledJob: 'polling-example-job-scheduled',
17
- }
18
-
19
- export const validateApiKey = (apiKey: string, logger: Logger) => {
20
- if (!apiKey || apiKey != process.env.INBOUND_HTTP_CALL_API_KEY) {
21
- logger.error('Failed to validate API key');
22
- throw new Error('Invalid API key');
23
- }
24
-
25
- logger.verbose('Validated API key')
26
- }
27
-
28
- export const validateSignature = (signature: string, payload: string, logger: Logger) => {
29
- const digest = createSignature(payload)
30
-
31
- if (!signature || signature != digest) {
32
- logger.error('Invalid webhook signature');
33
- throw new Error('Invalid signature');
34
- }
35
-
36
- logger.verbose('Validated signature');
37
- }
38
-
39
- export const triggerBackgroundJob = async (jobName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
40
- const body = {
41
- ...context
42
- }
43
- const bodyStr = JSON.stringify(body);
44
-
45
- const response = await fetch(`${trimEnd(process.env.URL, '/')}/.netlify/functions/${jobName}`, {
46
- method: 'POST',
47
- body: bodyStr,
48
- headers: {
49
- [apiKeyHeaderName]: process.env.INBOUND_HTTP_CALL_API_KEY as string,
50
- [signatureHeaderName]: createSignature(bodyStr),
51
- 'Content-Type': 'application/json',
52
- },
53
- })
54
-
55
- if (!response.ok) {
56
- const { status, statusText, url, type, headers } = response;
57
- logger.error("Failed to trigger background job", { status, statusText, body: await response.text(), url, type, headers })
58
- throw new Error(`Failed to trigger background job: ${response.statusText}`);
59
- }
60
- }
61
-
62
- export const createSignature = (payload: string) => {
63
- const hmac = crypto.createHmac('sha256', process.env.ENCRYPTION_SECRET as string);
64
- hmac.update(payload);
65
- const digest = hmac.digest('hex');
66
-
67
- return digest;
68
- }