@seeka-labs/cli-apps 1.1.6 → 1.1.8

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.
@@ -5,6 +5,7 @@ module.exports = {
5
5
  plugins: ['@typescript-eslint'],
6
6
  root: true,
7
7
  rules: {
8
- "@typescript-eslint/no-unused-vars": "warn"
8
+ "@typescript-eslint/no-unused-vars": "warn",
9
+ "@typescript-eslint/no-explicit-any": "off"
9
10
  }
10
11
  };
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@datalust/winston-seq": "^2.0.0",
26
- "@seeka-labs/sdk-apps-server": "^1.0.4",
26
+ "@seeka-labs/sdk-apps-server": "^1.1.7",
27
27
  "axios": "^1.6.7",
28
28
  "express": "^4.18.2",
29
29
  "lodash-es": "^4.17.21",
@@ -1,6 +1,7 @@
1
1
  import { createClient } from 'redis';
2
2
 
3
3
  import { getLogger } from '../../logging';
4
+ import winston from 'winston';
4
5
 
5
6
  const redisProtocol = process.env.REDIS_CONNECTION_TLS === 'true' ? 'rediss://' : 'redis://';
6
7
  const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${process.env.REDIS_CONNECTION_PASSWORD}@${process.env.REDIS_CONNECTION_HOST}:${process.env.REDIS_CONNECTION_PORT}`;
@@ -8,7 +9,7 @@ const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${proces
8
9
  const redisClient = createClient({
9
10
  url: redisConn
10
11
  })
11
- .on('error', err => getLogger().error('Redis Client ', { error: err }));
12
+ .on('error', (err: any) => getLogger().error('Redis Client ', { ex: winston.exceptions.getAllInfo(err) }));
12
13
 
13
14
  export const connect = async () => {
14
15
  await redisClient.connect();
@@ -14,7 +14,7 @@ import {
14
14
  import { webhookLogger } from '../lib/logging';
15
15
  import { startServices } from '../lib/services';
16
16
  import {
17
- createOrUpdateInstallation, deleteInstallation, SeekaAppInstallState, tryGetInstallation
17
+ createOrUpdateInstallation, deleteInstallation, SampleAppInstallSettings, SeekaAppInstallState, tryGetInstallation
18
18
  } from '../lib/state/seeka/installations';
19
19
 
20
20
  export async function seekaAppWebhook(req: Request, res: Response, context: Context): Promise<void> {
@@ -64,15 +64,16 @@ export async function seekaAppWebhook(req: Request, res: Response, context: Cont
64
64
 
65
65
  // Do something
66
66
  try {
67
+ let errorMessage: string | null = null;
67
68
  switch (body.type) {
68
69
  case SeekaWebhookCallType.AppInstalled:
69
70
  {
70
- await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
71
+ errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
71
72
  break;
72
73
  }
73
74
  case SeekaWebhookCallType.AppInstallSettingsUpdated:
74
75
  {
75
- await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
76
+ errorMessage = await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
76
77
  break;
77
78
  }
78
79
  case SeekaWebhookCallType.AppUninstalled:
@@ -97,7 +98,13 @@ export async function seekaAppWebhook(req: Request, res: Response, context: Cont
97
98
  }
98
99
  }
99
100
 
100
- res.status(204).send();
101
+ if (errorMessage) {
102
+ logger.warn('Webhook call failed', { errorMessage });
103
+ res.status(400).json({ error: { message: errorMessage } }).send();
104
+ }
105
+ else {
106
+ res.status(204).send();
107
+ }
101
108
  }
102
109
  catch (err) {
103
110
  logger.error('Failed to handle webhook', { ex: winston.exceptions.getAllInfo(err) });
@@ -109,8 +116,18 @@ export async function seekaAppWebhook(req: Request, res: Response, context: Cont
109
116
  }
110
117
  }
111
118
 
112
- const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger) => {
113
- if (payload.isTest) return;
119
+ const validateInstallationSettings = async (installSettings: SampleAppInstallSettings, logger: Logger): Promise<string | null> => {
120
+ // Returning an error message string here will block the installation request or settings update request by the user installing the app
121
+
122
+
123
+ return null;
124
+ }
125
+
126
+ const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger): Promise<string | null> => {
127
+ if (payload.isTest) return null;
128
+
129
+ const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
130
+ if (errorMessage) return errorMessage;
114
131
 
115
132
  const installation = await createOrUpdateInstallation({
116
133
  ...payload.context,
@@ -123,10 +140,15 @@ const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger:
123
140
  installedAt: new Date().toISOString(),
124
141
  installationSettings: payload.content?.installationSettings || {}
125
142
  }, logger)
143
+
144
+ return null;
126
145
  }
127
146
 
128
- const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger) => {
129
- if (payload.isTest) return;
147
+ const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger): Promise<string | null> => {
148
+ if (payload.isTest) return null;
149
+
150
+ const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
151
+ if (errorMessage) return errorMessage;
130
152
 
131
153
  const existingInstallation = await tryGetInstallation(payload.context?.applicationInstallId as string, true, logger) as SeekaAppInstallState;
132
154
  const installation = await createOrUpdateInstallation({
@@ -137,6 +159,8 @@ const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpda
137
159
  },
138
160
  installationSettings: payload.content?.installationSettings || {},
139
161
  }, logger)
162
+
163
+ return null;
140
164
  }
141
165
 
142
166
  const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, logger: Logger) => {
@@ -5,6 +5,7 @@ module.exports = {
5
5
  plugins: ['@typescript-eslint'],
6
6
  root: true,
7
7
  rules: {
8
- "@typescript-eslint/no-unused-vars": "warn"
8
+ "@typescript-eslint/no-unused-vars": "warn",
9
+ "@typescript-eslint/no-explicit-any": "off"
9
10
  }
10
11
  };
@@ -17,13 +17,14 @@
17
17
  "prestart": "<packageManagerRunPrefix> clean && <packageManagerRunPrefix> build",
18
18
  "dev": "func start --port 7072",
19
19
  "tunnel": "node scripts/ngrok.js seeka-app-example-name-localdev",
20
- "deploy": "<packageManagerRunPrefix> clean && <packageManagerRunPrefix> build && func azure functionapp publish seeka-app-example-name --no-build --javascript"
20
+ "deploy": "<packageManagerRunPrefix> clean && <packageManagerRunPrefix> build && func azure functionapp publish seeka-app-example-name --no-build --javascript",
21
+ "dev:queue:create": "node scripts/dev-queue-setup.js sample-queue-name"
21
22
  },
22
23
  "dependencies": {
23
24
  "@azure/functions": "^4.1.0",
24
25
  "@azure/storage-queue": "^12.16.0",
25
26
  "@datalust/winston-seq": "^2.0.0",
26
- "@seeka-labs/sdk-apps-server": "^1.0.4",
27
+ "@seeka-labs/sdk-apps-server": "^1.1.7",
27
28
  "axios": "^1.6.7",
28
29
  "lodash-es": "^4.17.21",
29
30
  "openid-client": "^5.6.4",
@@ -0,0 +1,18 @@
1
+ /* eslint-disable @typescript-eslint/no-var-requires */
2
+ /* eslint-disable no-undef */
3
+ const { QueueClient } = require("@azure/storage-queue");
4
+
5
+ (async function () {
6
+ // https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cqueue-storage#azure-sdks
7
+ // Dev / emulator / azurite
8
+ var client = new QueueClient(
9
+ `UseDevelopmentStorage=true`, process.argv[2]
10
+ );
11
+ const res = await client.createIfNotExists();
12
+ if (res.succeeded) {
13
+ console.log("Queue created");
14
+ }
15
+ else {
16
+ console.log("Queue already exists");
17
+ }
18
+ })();
@@ -13,7 +13,8 @@ app.storageQueue('queueExample', {
13
13
  });
14
14
 
15
15
  export async function queueExample(queueItem: string, context: InvocationContext): Promise<void> {
16
- const body = deserialiseQueuePayload<BackgroundJobRequestContext>(queueItem);
16
+ const body = typeof queueItem === 'string' ? deserialiseQueuePayload<BackgroundJobRequestContext>(queueItem) : queueItem as BackgroundJobRequestContext;
17
+
17
18
  const logger = backgroundJobLogger(queueNames.queueItemExampleQueueName, body, context);
18
19
  logger.profile(`queue.${queueNames.queueItemExampleQueueName}`)
19
20
 
@@ -16,7 +16,7 @@ import { getSeekaBrowserPlugin } from '../lib/browser'
16
16
  import { webhookLogger } from '../lib/logging';
17
17
  import { startServices } from '../lib/services';
18
18
  import {
19
- createOrUpdateInstallation, deleteInstallation, SeekaAppInstallState, tryGetInstallation
19
+ createOrUpdateInstallation, deleteInstallation, SampleAppInstallSettings, SeekaAppInstallState, tryGetInstallation
20
20
  } from '../lib/state/seeka/installations';
21
21
 
22
22
  import type { Logger } from 'winston';
@@ -85,16 +85,17 @@ export async function seekaAppWebhook(req: HttpRequest, context: InvocationConte
85
85
  }
86
86
 
87
87
  // Do something
88
+ let errorMessage: string | null = null;
88
89
  try {
89
90
  switch (body.type) {
90
91
  case SeekaWebhookCallType.AppInstalled:
91
92
  {
92
- await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
93
+ errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
93
94
  break;
94
95
  }
95
96
  case SeekaWebhookCallType.AppInstallSettingsUpdated:
96
97
  {
97
- await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
98
+ errorMessage = await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
98
99
  break;
99
100
  }
100
101
  case SeekaWebhookCallType.AppUninstalled:
@@ -146,13 +147,31 @@ export async function seekaAppWebhook(req: HttpRequest, context: InvocationConte
146
147
  logger.verbose('Seeka webhook handled');
147
148
  }
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
+
149
158
  return {
150
159
  status: 204
151
160
  }
152
161
  }
153
162
 
154
- const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger) => {
155
- if (payload.isTest) return;
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;
156
175
 
157
176
  const installation = await createOrUpdateInstallation({
158
177
  ...payload.context,
@@ -171,10 +190,15 @@ const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger:
171
190
  causationId: payload.causationId,
172
191
  correlationId: payload.requestId
173
192
  }, logger)
193
+
194
+ return null;
174
195
  }
175
196
 
176
- const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger) => {
177
- if (payload.isTest) return;
197
+ const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger): Promise<string | null> => {
198
+ if (payload.isTest) return null;
199
+
200
+ const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
201
+ if (errorMessage) return errorMessage;
178
202
 
179
203
  const existingInstallation = await tryGetInstallation(payload.context?.applicationInstallId as string, true, logger) as SeekaAppInstallState;
180
204
  const installation = await createOrUpdateInstallation({
@@ -191,6 +215,8 @@ const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpda
191
215
  causationId: payload.causationId,
192
216
  correlationId: payload.requestId
193
217
  }, logger)
218
+
219
+ return null;
194
220
  }
195
221
 
196
222
  const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, logger: Logger) => {
@@ -1,6 +1,7 @@
1
1
  import { createClient } from 'redis';
2
2
 
3
3
  import { logger } from '../../logging';
4
+ import winston from 'winston';
4
5
 
5
6
  const redisProtocol = process.env.REDIS_CONNECTION_TLS === 'true' ? 'rediss://' : 'redis://';
6
7
  const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${process.env.REDIS_CONNECTION_PASSWORD}@${process.env.REDIS_CONNECTION_HOST}:${process.env.REDIS_CONNECTION_PORT}`;
@@ -8,7 +9,7 @@ const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${proces
8
9
  const redisClient = createClient({
9
10
  url: redisConn
10
11
  })
11
- .on('error', err => logger.error('Redis Client ', { error: err }));
12
+ .on('error', (err: any) => logger.error('Redis Client ', { ex: winston.exceptions.getAllInfo(err) }));
12
13
 
13
14
  export const connect = async () => {
14
15
  await redisClient.connect();
@@ -4,4 +4,8 @@ module.exports = {
4
4
  parser: '@typescript-eslint/parser',
5
5
  plugins: ['@typescript-eslint'],
6
6
  root: true,
7
+ rules: {
8
+ "@typescript-eslint/no-unused-vars": "warn",
9
+ "@typescript-eslint/no-explicit-any": "off"
10
+ }
7
11
  };
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "@datalust/winston-seq": "^2.0.0",
21
21
  "@netlify/functions": "^2.5.1",
22
- "@seeka-labs/sdk-apps-server": "^1.0.4",
22
+ "@seeka-labs/sdk-apps-server": "^1.1.7",
23
23
  "axios": "^1.6.7",
24
24
  "lodash-es": "^4.17.21",
25
25
  "openid-client": "^5.6.4",
@@ -7,7 +7,7 @@ import { jobNames, triggerBackgroundJob } from '@/lib/jobs';
7
7
  import { webhookLogger } from '@/lib/logging';
8
8
  import { startServices, stopServices } from '@/lib/services';
9
9
  import {
10
- createOrUpdateInstallation, deleteInstallation, SeekaAppInstallState, tryGetInstallation
10
+ createOrUpdateInstallation, deleteInstallation, SampleAppInstallSettings, SeekaAppInstallState, tryGetInstallation
11
11
  } from '@/lib/state/seeka/installations';
12
12
  import {
13
13
  PersonIdentifiers, SeekaActivityAcceptedWebhookPayload, SeekaAppInstalledWebhookPayload,
@@ -67,16 +67,17 @@ export default async (req: Request, context: Context) => {
67
67
  }
68
68
 
69
69
  // Do something
70
+ let errorMessage: string | null = null;
70
71
  try {
71
72
  switch (body.type) {
72
73
  case SeekaWebhookCallType.AppInstalled:
73
74
  {
74
- await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
75
+ errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
75
76
  break;
76
77
  }
77
78
  case SeekaWebhookCallType.AppInstallSettingsUpdated:
78
79
  {
79
- await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
80
+ errorMessage = await onInstallationSettingsUpdate(body as SeekaAppInstallSettingsUpdatedWebhookPayload, logger);
80
81
  break;
81
82
  }
82
83
  case SeekaWebhookCallType.AppUninstalled:
@@ -114,29 +115,52 @@ export default async (req: Request, context: Context) => {
114
115
  logger.verbose('Seeka webhook handled');
115
116
  }
116
117
 
118
+ if (errorMessage) {
119
+ logger.warn('Webhook call failed', { errorMessage });
120
+ return Response.json({ error: { message: errorMessage } }, { status: 400 });
121
+ }
122
+
117
123
  return new Response(undefined, { status: 204 })
118
124
  }
119
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;
120
135
 
121
- const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger) => {
122
- if (payload.isTest) return;
136
+ const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
137
+ if (errorMessage) return errorMessage;
123
138
 
124
139
  const installation = await createOrUpdateInstallation({
125
140
  ...payload.context,
126
141
  installationState: {
127
142
  grantedPermissions: payload.content?.grantedPermissions || []
128
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(),
129
148
  installationSettings: payload.content?.installationSettings || {}
130
- } as any, logger) // TODO: remove any
149
+ }, logger)
131
150
 
132
151
  // Trigger a sync for the installation
133
152
  await triggerBackgroundJob(jobNames.exampleBackgroundJob, {
134
153
  ...payload.context
135
154
  }, logger)
155
+
156
+ return null;
136
157
  }
137
158
 
138
- const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger) => {
139
- if (payload.isTest) return;
159
+ const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpdatedWebhookPayload, logger: Logger): Promise<string | null> => {
160
+ if (payload.isTest) return null;
161
+
162
+ const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
163
+ if (errorMessage) return errorMessage;
140
164
 
141
165
  const existingInstallation = await tryGetInstallation(payload.context?.applicationInstallId as string, true, logger) as SeekaAppInstallState;
142
166
 
@@ -152,6 +176,8 @@ const onInstallationSettingsUpdate = async (payload: SeekaAppInstallSettingsUpda
152
176
  } as any, logger) // TODO: remove any
153
177
 
154
178
  logger.info('Settings updated')
179
+
180
+ return null;
155
181
  }
156
182
 
157
183
  const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, logger: Logger) => {
@@ -1,6 +1,7 @@
1
1
  import { createClient } from 'redis';
2
2
 
3
3
  import { logger } from '@/lib/logging';
4
+ import winston from 'winston';
4
5
 
5
6
  const redisProtocol = process.env.REDIS_CONNECTION_TLS === 'true' ? 'rediss://' : 'redis://';
6
7
  const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${process.env.REDIS_CONNECTION_PASSWORD}@${process.env.REDIS_CONNECTION_HOST}:${process.env.REDIS_CONNECTION_PORT}`;
@@ -8,7 +9,7 @@ const redisConn = `${redisProtocol}${process.env.REDIS_CONNECTION_USER}:${proces
8
9
  const redisClient = createClient({
9
10
  url: redisConn
10
11
  })
11
- .on('error', err => logger.error('Redis Client ', { error: err }));
12
+ .on('error', (err: any) => logger.error('Redis Client ', { ex: winston.exceptions.getAllInfo(err) }));
12
13
 
13
14
  export const connect = async () => {
14
15
  await redisClient.connect();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seeka-labs/cli-apps",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Seeka - Apps CLI",
5
5
  "author": "SEEKA <platform@seeka.co>",
6
6
  "license": "MIT",