@seeka-labs/cli-apps 2.2.6 → 3.2.5

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 (72) hide show
  1. package/dist/index.js +46922 -35
  2. package/dist/index.js.map +4 -4
  3. package/dist/init-template/.gitlab-ci.yml +29 -8
  4. package/dist/init-template/.nvmrc +1 -0
  5. package/dist/init-template/README.md +18 -3
  6. package/dist/init-template/app/{server-azure-function/.eslintrc.cjs → .eslintrc.cjs} +2 -0
  7. package/dist/init-template/app/browser/README.md +1 -1
  8. package/dist/init-template/app/browser/package.json +22 -24
  9. package/dist/init-template/app/browser/scripts/esbuild/build-browser-plugin.mjs +14 -6
  10. package/dist/init-template/app/browser/src/browser.ts +6 -6
  11. package/dist/init-template/app/browser/src/plugin/index.ts +57 -44
  12. package/dist/init-template/app/browser/tsconfig.json +1 -2
  13. package/dist/init-template/app/lib/package.json +46 -0
  14. package/dist/init-template/app/lib/src/index.ts +4 -0
  15. package/dist/init-template/app/lib/src/models/index.ts +29 -0
  16. package/dist/init-template/app/lib/src/validation/index.ts +14 -0
  17. package/dist/init-template/app/lib/tsconfig.json +22 -0
  18. package/dist/init-template/app/server-azurefunc/.eslintrc.cjs +4 -0
  19. package/dist/init-template/app/{server-azure-function → server-azurefunc}/.funcignore +0 -3
  20. package/dist/init-template/app/{server-azure-function → server-azurefunc}/README.md +3 -3
  21. package/dist/init-template/app/{server-azure-function → server-azurefunc}/host.json +11 -0
  22. package/dist/init-template/app/{server-azure-function/local.settings.example.json → server-azurefunc/local.settings.template.json} +18 -10
  23. package/dist/init-template/app/server-azurefunc/package.json +69 -0
  24. package/dist/init-template/app/server-azurefunc/scripts/dev-queue-setup.js +18 -0
  25. package/dist/init-template/app/{server-azure-function → server-azurefunc}/scripts/ngrok.js +4 -1
  26. package/dist/init-template/app/server-azurefunc/src/app/api/router.ts +14 -0
  27. package/dist/init-template/app/server-azurefunc/src/app/api/routes/getInstallationSettings.ts +13 -0
  28. package/dist/init-template/app/server-azurefunc/src/app/api/routes/setInstallationSettings.ts +35 -0
  29. package/dist/init-template/app/server-azurefunc/src/app/jobs/index.ts +61 -0
  30. package/dist/init-template/app/server-azurefunc/src/app/logging/index.ts +4 -0
  31. package/dist/init-template/app/server-azurefunc/src/app/models/index.ts +12 -0
  32. package/dist/init-template/app/server-azurefunc/src/app/services/activites.ts +8 -0
  33. package/dist/init-template/app/{server-azure-function → server-azurefunc}/src/functions/healthCheck.ts +7 -1
  34. package/dist/init-template/app/server-azurefunc/src/functions/seekaAppWebhook.ts +201 -0
  35. package/dist/init-template/app/server-azurefunc/src/functions/trackActivityQueueHandler.ts +48 -0
  36. package/dist/init-template/app/server-azurefunc/src/functions/ui.ts +46 -0
  37. package/dist/init-template/app/{server-azure-function → server-azurefunc}/tsconfig.json +8 -2
  38. package/dist/init-template/app/ui/README.md +40 -0
  39. package/dist/init-template/app/ui/index.html +21 -0
  40. package/dist/init-template/app/ui/package.json +72 -0
  41. package/dist/init-template/app/ui/public/favicon.ico +0 -0
  42. package/dist/init-template/app/ui/scripts/copy-output.mjs +30 -0
  43. package/dist/init-template/app/ui/src/App.tsx +72 -0
  44. package/dist/init-template/app/ui/src/assets/app-icon.svg +1 -0
  45. package/dist/init-template/app/ui/src/components/setup/steps/complete/index.tsx +32 -0
  46. package/dist/init-template/app/ui/src/components/setup/steps/first/index.tsx +27 -0
  47. package/dist/init-template/app/ui/src/components/setup/steps/index.tsx +22 -0
  48. package/dist/init-template/app/ui/src/components/setup/steps/second/index.tsx +29 -0
  49. package/dist/init-template/app/ui/src/index.tsx +45 -0
  50. package/dist/init-template/app/ui/src/routes/home/index.tsx +21 -0
  51. package/dist/init-template/app/ui/src/vite-env.d.ts +13 -0
  52. package/dist/init-template/app/ui/tsconfig.json +35 -0
  53. package/dist/init-template/app/ui/tsconfig.node.json +10 -0
  54. package/dist/init-template/app/ui/vite.config.mts +48 -0
  55. package/dist/init-template/package.json +19 -11
  56. package/dist/init-template/tsconfig.json +5 -6
  57. package/package.json +13 -4
  58. package/README.md +0 -1
  59. package/dist/init-template/app/browser/jest.config.js +0 -11
  60. package/dist/init-template/app/browser/src/plugin/index.test.ts +0 -6
  61. package/dist/init-template/app/server-azure-function/.nvmrc +0 -1
  62. package/dist/init-template/app/server-azure-function/package.json +0 -51
  63. package/dist/init-template/app/server-azure-function/src/functions/pollingExample.ts +0 -39
  64. package/dist/init-template/app/server-azure-function/src/functions/queueExample.ts +0 -67
  65. package/dist/init-template/app/server-azure-function/src/functions/seekaAppWebhook.ts +0 -236
  66. package/dist/init-template/app/server-azure-function/src/lib/browser/index.ts +0 -55
  67. package/dist/init-template/app/server-azure-function/src/lib/jobs/index.ts +0 -96
  68. package/dist/init-template/app/server-azure-function/src/lib/logging/index.ts +0 -93
  69. package/dist/init-template/app/server-azure-function/src/lib/models/index.ts +0 -7
  70. package/dist/init-template/app/server-azure-function/src/lib/services/index.ts +0 -41
  71. package/dist/init-template/app/server-azure-function/src/lib/state/redis/index.ts +0 -96
  72. package/dist/init-template/app/server-azure-function/src/lib/state/seeka/installations.ts +0 -65
@@ -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
+ })();
@@ -2,6 +2,9 @@
2
2
  // eslint-disable-next-line @typescript-eslint/no-var-requires
3
3
  const ngrok = require('ngrok');
4
4
  (async function () {
5
+ await ngrok.disconnect(process.argv[2]); // stops all
6
+ await ngrok.kill(); // kills ngrok process
7
+
5
8
  const url = await ngrok.connect({
6
9
  proto: 'http',
7
10
  web_addr: 'localhost:4040',
@@ -23,6 +26,6 @@ const ngrok = require('ngrok');
23
26
  console.info(`Use this URL in your Seeka app configuration for testing`)
24
27
  console.log('')
25
28
  console.log('------------------------------------------')
26
- console.log('')
29
+ console.log('http://127.0.0.1:4040/inspect/http')
27
30
  console.log('')
28
31
  })();
@@ -0,0 +1,14 @@
1
+ import { ExampleAppAppInstallState } from "@example-org-name/example-app-name-lib";
2
+ import {getInstallationSettings} from "./routes/getInstallationSettings";
3
+ import {setInstallationSettings} from "./routes/setInstallationSettings";
4
+ import { SeekaAppInstallContext } from "@seeka-labs/sdk-apps-core";
5
+ import { Logger } from "winston";
6
+ import { AppUiHttpRequestResponse, HttpMethod } from "@seeka-labs/sdk-apps-server-host";
7
+ import {HttpRequest, InvocationContext} from "@azure/functions";
8
+
9
+ export const apiRouteHandler = async (req: HttpRequest, context: InvocationContext, requestedPath: string, requestedMethod: HttpMethod, installation: SeekaAppInstallContext<ExampleAppAppInstallState>, logger: Logger ): Promise<AppUiHttpRequestResponse | null> => {
10
+ if (requestedPath === '/api/settings' && requestedMethod === 'GET') return await getInstallationSettings(installation, req, context, logger);
11
+ if (requestedPath === '/api/settings' && requestedMethod === 'POST') return await setInstallationSettings(installation, req, context, logger);
12
+
13
+ return null;
14
+ }
@@ -0,0 +1,13 @@
1
+ import { HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
2
+ import { ExampleAppAppInstallContext, ExampleAppAppInstallSettings } from "@example-org-name/example-app-name-lib";
3
+ import { AppUiHttpRequestResponse } from "@seeka-labs/sdk-apps-server-host";
4
+ import { Logger } from "winston";
5
+
6
+ export const getInstallationSettings = async (installation: ExampleAppAppInstallContext, req: HttpRequest, context: InvocationContext, logger: Logger): Promise<AppUiHttpRequestResponse> => {
7
+ // Important to not expose any sensitive data to the client via this endpoint
8
+ // Be selective, no spread operator here, only the fields you want to expose
9
+ return {
10
+ status: 200,
11
+ jsonBody: installation.installationState?.installationSettings as ExampleAppAppInstallSettings
12
+ }
13
+ }
@@ -0,0 +1,35 @@
1
+ import { HttpRequest, InvocationContext } from "@azure/functions";
2
+ import { Logger } from "winston";
3
+
4
+ import { ExampleAppAppInstallContext, ExampleAppAppInstallSettings, validateInstallationSettings } from "@example-org-name/example-app-name-lib";
5
+ import { AppUiHttpRequestResponse, createOrUpdateInstallation } from "@seeka-labs/sdk-apps-server-host";
6
+
7
+ export const setInstallationSettings = async (installation: ExampleAppAppInstallContext, req: HttpRequest, context: InvocationContext, logger: Logger): Promise<AppUiHttpRequestResponse> => {
8
+ // Validate
9
+ const newSettings = (await req.json()) as ExampleAppAppInstallSettings | null;
10
+ if (!newSettings) {
11
+ return {
12
+ status: 400,
13
+ jsonBody: { error: 'Settings are required' }
14
+ };
15
+ }
16
+
17
+ const error = await validateInstallationSettings(newSettings, logger);
18
+ if (error) return {
19
+ status: 400,
20
+ jsonBody: { error }
21
+ }
22
+
23
+ installation.installationState = {
24
+ ...installation.installationState,
25
+ installationSettings: {
26
+ ...installation.installationState.installationSettings,
27
+ ...newSettings
28
+ }
29
+ };
30
+ await createOrUpdateInstallation(installation, logger);
31
+
32
+ return {
33
+ status: 200
34
+ };
35
+ }
@@ -0,0 +1,61 @@
1
+ import { QueueClient, QueueSendMessageOptions } from '@azure/storage-queue';
2
+ import type { Logger } from 'winston';
3
+ import {BackgroundJobRequestContext} from "@seeka-labs/sdk-apps-server-host";
4
+
5
+ export const queueNames = {
6
+ trackActivity: 'activity-track-queue',
7
+ }
8
+
9
+ export const triggerBackgroundJob = async <TPayload>(queueName: string, context: BackgroundJobRequestContext<TPayload>, logger: Logger, queueSendOptions?: QueueSendMessageOptions): Promise<void> => {
10
+ const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, queueName);
11
+ return await triggerBackgroundJobWithQueue(queueClient, context, logger, queueSendOptions);
12
+ }
13
+
14
+ const serialiseQueuePayload = (payload: unknown): string => {
15
+ const jsonString = JSON.stringify(payload)
16
+ return Buffer.from(jsonString).toString('base64')
17
+ }
18
+
19
+ export const deserialiseQueuePayload = <TPayload>(payload: string): TPayload => {
20
+ const jsonString = Buffer.from(payload, 'base64').toString()
21
+ return JSON.parse(jsonString) as TPayload;
22
+ }
23
+
24
+ export const triggerBackgroundJobWithQueue = async <TPayload>(queueClient: QueueClient, context: BackgroundJobRequestContext<TPayload>, logger: Logger, queueSendOptions?: QueueSendMessageOptions): Promise<void> => {
25
+ const body = {
26
+ ...context
27
+ }
28
+ const bodyStr = serialiseQueuePayload(body);
29
+
30
+ const response = await queueClient.sendMessage(bodyStr, queueSendOptions);
31
+
32
+ if (response.errorCode) {
33
+ const { requestId, date, errorCode } = response;
34
+ logger.error("Failed to trigger background job", { body, requestId, date, errorCode })
35
+ throw new Error(`Failed to trigger background job: ${response.errorCode}`);
36
+ }
37
+ else {
38
+ logger.info("Background job triggered for " + queueClient.name, { body, messageId: response.messageId, context })
39
+ }
40
+ }
41
+
42
+ export const sendQueueMessageToPoisonQueue = async <TPayload>(queueName: string, context: BackgroundJobRequestContext<TPayload>, logger: Logger): Promise<void> => {
43
+ const body = {
44
+ ...context
45
+ }
46
+ const bodyStr = serialiseQueuePayload(body);
47
+
48
+ // 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
49
+ const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, `${queueName}-poison`);
50
+
51
+ const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
52
+
53
+ if (response.errorCode) {
54
+ const { requestId, date, errorCode } = response;
55
+ logger.error("Failed to push to poison queue", { body, requestId, date, errorCode })
56
+ throw new Error(`Failed to push to poison queue: ${response.errorCode}`);
57
+ }
58
+ else {
59
+ logger.verbose("Message pushed to poison queue", { body, messageId: response.messageId, context })
60
+ }
61
+ }
@@ -0,0 +1,4 @@
1
+ import * as packageJson from '../../../package.json';
2
+ import {registerSeekaAppOpenTelemetry} from "@seeka-labs/sdk-apps-server-telemetry-logging";
3
+
4
+ registerSeekaAppOpenTelemetry(packageJson.name, packageJson.version, 'azure', 'azure_function', process.env);
@@ -0,0 +1,12 @@
1
+ 
2
+ import {SeekaActivityAcceptedWebhookContent} from "@seeka-labs/sdk-apps-server";
3
+ import { BackgroundJobRequestContext } from "@seeka-labs/sdk-apps-server-host";
4
+
5
+ export type TrackExampleAppActivityQueueItem = BackgroundJobRequestContext<TrackSeekaExampleAppActivityPayload> & {
6
+
7
+ }
8
+
9
+ export type TrackSeekaExampleAppActivityPayload = {
10
+ seekaActivity: SeekaActivityAcceptedWebhookContent
11
+ }
12
+
@@ -0,0 +1,8 @@
1
+ 
2
+ import {Logger} from "winston";
3
+ import {TrackSeekaExampleAppActivityPayload} from "../models";
4
+ import {SeekaAppInstallContext} from "@seeka-labs/sdk-apps-core";
5
+
6
+ export const sendActivityToAnotherSoftware = async (payload: TrackSeekaExampleAppActivityPayload, installation: SeekaAppInstallContext<any>, causationId: string, correlationId: string, logger: Logger): Promise<void> => {
7
+ // Anything
8
+ }
@@ -1,13 +1,19 @@
1
1
  import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
2
+ import { appLogger } from "@seeka-labs/sdk-apps-server-host";
2
3
 
3
4
  app.http('healthCheck', {
4
5
  methods: ['GET', 'HEAD'],
5
6
  authLevel: 'anonymous',
6
- route: 'health',
7
+ route: 'api/health',
7
8
  handler: healthCheck
8
9
  });
9
10
 
10
11
  export async function healthCheck(req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
12
+ // await otelPing('healthcheck.ping', { route: 'api/health' });
13
+
14
+ // logger.verbose('Health check request received', { status: res.status, url: req.url });
15
+ appLogger.verbose('Health check', { url: req.url });
16
+
11
17
  return {
12
18
  status: 200
13
19
  }
@@ -0,0 +1,201 @@
1
+ import * as winston from 'winston';
2
+
3
+ import {app, HttpRequest, HttpResponseInit, InvocationContext} from '@azure/functions';
4
+ import {
5
+ SeekaActivityAcceptedWebhookPayload, SeekaAppInstalledWebhookPayload,
6
+ SeekaAppUninstalledWebhookPayload,
7
+ SeekaIdentityChangedWebhookPayload, SeekaWebhookCallType, SeekaWebhookPayload,
8
+ throwOnInvalidWebhookSignature
9
+ } from '@seeka-labs/sdk-apps-server';
10
+
11
+ import {tryGetInstallation,
12
+ createOrUpdateInstallation, deleteInstallation, getSeekaBrowserPlugin, startServices,
13
+ webhookLogger,
14
+ childLogger,
15
+ } from '@seeka-labs/sdk-apps-server-host';
16
+
17
+ import { validateInstallationSettings, ExampleAppAppBrowserSdkPluginConfig, ExampleAppAppInstallContext} from '@example-org-name/example-app-name-lib'
18
+
19
+ import type {Logger} from 'winston';
20
+ import {queueNames, triggerBackgroundJob} from '../app/jobs';
21
+ import {TrackExampleAppActivityQueueItem} from "../app/models";
22
+
23
+ app.http('seekaAppWebhook', {
24
+ methods: ['POST'],
25
+ authLevel: 'anonymous',
26
+ route: 'api/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
+ let 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 Headers, bodyStr);
54
+ logger.debug('Webhook signature validated', {body});
55
+ } catch {
56
+ logger.warn('Webhook signature invalid', {body});
57
+ return {
58
+ status: 401,
59
+ jsonBody: {error: "Webhook call invalid"}
60
+ }
61
+ }
62
+
63
+ if (body.isTest) {
64
+ // This is a test webhook call
65
+ return {
66
+ status: 204
67
+ }
68
+ }
69
+
70
+ await startServices(logger);
71
+
72
+ // Check if the webhook is for an app we have installed
73
+ let installation: ExampleAppAppInstallContext | null = null;
74
+ if (body.type != SeekaWebhookCallType.AppInstalled) {
75
+ installation = await tryGetInstallation((body as SeekaAppInstalledWebhookPayload).context?.applicationInstallId as string, false, logger);
76
+ if (installation == null) {
77
+ logger.warn('Webhook call cannot be processed as the installation ID is not known by this app', {body});
78
+
79
+ return {
80
+ status: 422,
81
+ jsonBody: {error: "App not installed"}
82
+ }
83
+ }
84
+ }
85
+
86
+ logger = childLogger(null, installation, logger, context);
87
+
88
+ // Do something
89
+ let errorMessage: string | null = null;
90
+ try {
91
+ switch (body.type) {
92
+ case SeekaWebhookCallType.AppInstalled: {
93
+ errorMessage = await onInstallation(body as SeekaAppInstalledWebhookPayload, logger);
94
+ break;
95
+ }
96
+ case SeekaWebhookCallType.AppUninstalled: {
97
+ if (!body.isTest) {
98
+ const payload = body as SeekaAppUninstalledWebhookPayload;
99
+ await deleteInstallation(payload.context?.applicationInstallId as string, logger)
100
+ }
101
+ break;
102
+ }
103
+ case SeekaWebhookCallType.ActivityAccepted: {
104
+ const payload = body as SeekaActivityAcceptedWebhookPayload;
105
+ await handleSeekaActivity(payload, installation, context, logger);
106
+
107
+ break;
108
+ }
109
+ case SeekaWebhookCallType.IdentityChanged: {
110
+ const payload = body as SeekaIdentityChangedWebhookPayload;
111
+
112
+ logger.debug('Identity changed webhook received', {personId: payload.content.personId });
113
+
114
+ break;
115
+ }
116
+
117
+ case SeekaWebhookCallType.BrowserSdkPlugin: {
118
+ // Configuration object passed to the Seeka browser plugin
119
+ // Under no circumstances should internal or private api keys be exposed to the client/browser via the below object
120
+ const pluginConfig = {
121
+ appId: process.env.SEEKA_APP_ID,
122
+ appInstallId: installation.applicationInstallId,
123
+ appUrl: process.env.SELF_HOST_BASEURL,
124
+ installationSettings: {
125
+ browserPluginAppSetting1: installation.installationState?.installationSettings?.browserPluginAppSetting1
126
+ }
127
+ } as ExampleAppAppBrowserSdkPluginConfig;
128
+ const plugin = await getSeekaBrowserPlugin<ExampleAppAppBrowserSdkPluginConfig>('ExampleAppAppConvergeSdkPlugin', pluginConfig, logger);
129
+
130
+ logger.profile('http.seeka.webhook.app')
131
+ return {
132
+ status: 200,
133
+ body: JSON.stringify(plugin),
134
+ headers: {
135
+ 'Content-Type': 'application/json'
136
+ }
137
+ }
138
+ }
139
+ }
140
+ } catch (err) {
141
+ logger.error('Failed to handle webhook', {ex: winston.exceptions.getAllInfo(err)});
142
+ return {
143
+ status: 500,
144
+ jsonBody: {error: "Request failed"}
145
+ }
146
+ } finally {
147
+ logger.profile('http.seeka.webhook.app')
148
+ logger.verbose('Seeka webhook handled');
149
+ }
150
+
151
+ if (errorMessage) {
152
+ logger.warn('Webhook call failed', {errorMessage});
153
+ return {
154
+ status: 400,
155
+ jsonBody: {error: {message: errorMessage}}
156
+ }
157
+ }
158
+
159
+ return {
160
+ status: 204
161
+ }
162
+ }
163
+
164
+ const onInstallation = async (payload: SeekaAppInstalledWebhookPayload, logger: Logger): Promise<string | null> => {
165
+ if (payload.isTest) return null;
166
+
167
+ const errorMessage = await validateInstallationSettings(payload.content?.installationSettings || {}, logger);
168
+ if (errorMessage) return errorMessage;
169
+
170
+ const installation = await createOrUpdateInstallation({
171
+ ...payload.context,
172
+ installationState: {
173
+ grantedPermissions: payload.content?.grantedPermissions || [],
174
+ installationSettings: payload.content?.installationSettings || {}
175
+ },
176
+ applicationInstallId: payload.context?.applicationInstallId as string,
177
+ organisationBrandId: payload.context?.organisationBrandId as string,
178
+ organisationId: payload.context?.organisationId as string,
179
+ installedAt: new Date().toISOString(),
180
+ }, logger)
181
+
182
+ return null;
183
+ }
184
+
185
+ const handleSeekaActivity = async (activity: SeekaActivityAcceptedWebhookPayload, installation: ExampleAppAppInstallContext, context: InvocationContext, logger: Logger) => {
186
+ logger = childLogger({
187
+ 'Activity_ActivityIdentifier': activity?.content?.activity?.activityId,
188
+ 'Tracking_Source_Url': activity?.content?.source?.loc,
189
+ 'Activity_Name': activity?.content?.activity?.activityNameCustom || activity?.content?.activity?.activityName?.toString()
190
+ }, installation, logger, context);
191
+
192
+ await triggerBackgroundJob(queueNames.trackActivity, {
193
+ payload: activity.content,
194
+ organisationBrandId: installation.organisationBrandId,
195
+ organisationId: installation.organisationId,
196
+ applicationInstallId: installation.applicationInstallId,
197
+ causationId: context.invocationId,
198
+ correlationId: context.invocationId
199
+ } as TrackExampleAppActivityQueueItem, logger)
200
+ }
201
+
@@ -0,0 +1,48 @@
1
+ import * as winston from 'winston';
2
+
3
+ import {app, InvocationContext} from '@azure/functions';
4
+
5
+ import {deserialiseQueuePayload, queueNames, sendQueueMessageToPoisonQueue} from '../app/jobs';
6
+ import {sendActivityToAnotherSoftware} from "../app/services/activites";
7
+ import {TrackExampleAppActivityQueueItem} from "../app/models";
8
+ import {backgroundJobLogger, childLogger, startServices, tryGetInstallation} from "@seeka-labs/sdk-apps-server-host";
9
+
10
+ app.storageQueue('trackActivityQueueHandler', {
11
+ queueName: queueNames.trackActivity,
12
+ connection: 'AzureWebJobsStorage',
13
+ handler: trackActivityQueueHandler
14
+ });
15
+
16
+ export async function trackActivityQueueHandler(queueItem: unknown, context: InvocationContext): Promise<void> {
17
+ const body = typeof queueItem === 'string' ? deserialiseQueuePayload<TrackExampleAppActivityQueueItem>(queueItem) : queueItem as TrackExampleAppActivityQueueItem;
18
+ let logger = backgroundJobLogger(queueNames.trackActivity, body, context);
19
+ logger.profile(`queue.${queueNames.trackActivity}`)
20
+
21
+ await startServices(logger);
22
+
23
+ const applicationInstallId = body.applicationInstallId as string || (body.payload as any)?.applicationInstallId as string; // Support legacy payload / previous bug
24
+
25
+ const installation = await tryGetInstallation(applicationInstallId as string, true, logger);
26
+
27
+ logger = childLogger({
28
+ 'Activity_ActivityIdentifier': body?.payload?.seekaActivity?.activity?.activityId,
29
+ 'Tracking_Source_Url': body?.payload?.seekaActivity?.source?.loc,
30
+ 'Activity_Name': body?.payload?.seekaActivity?.activity?.activityNameCustom || body?.payload?.seekaActivity?.activity?.activityName?.toString()
31
+ }, installation, logger, context)
32
+
33
+ logger.info('Received request to track activity for ExampleApp', {body});
34
+
35
+ try {
36
+ await sendActivityToAnotherSoftware(body.payload, installation, context.invocationId, context.invocationId, logger);
37
+ } catch (err) {
38
+ const ex: any = winston.exceptions.getAllInfo(err);
39
+ logger.error('Error handling track ExampleApp activity queue item', {ex});
40
+
41
+ await sendQueueMessageToPoisonQueue(queueNames.trackActivity, {
42
+ ...body,
43
+ correlationId: context.invocationId,
44
+ } as TrackExampleAppActivityQueueItem, logger);
45
+ }
46
+
47
+ logger.profile(`queue.${queueNames.trackActivity}`)
48
+ }
@@ -0,0 +1,46 @@
1
+ import {app, HttpRequest, HttpResponseInit, InvocationContext} from '@azure/functions';
2
+ import { ExampleAppAppInstallState, ExampleAppAppUiClientInitState } from "@example-org-name/example-app-name-lib";
3
+ import { SeekaAppInstallContext } from "@seeka-labs/sdk-apps-core";
4
+
5
+ import { generateAppUiHttpRequestResponse, HttpMethod} from '@seeka-labs/sdk-apps-server-host';
6
+ import {Logger} from "winston";
7
+ import {apiRouteHandler} from "../app/api/router";
8
+
9
+ // Define your route and handler for the /ui path
10
+ app.http('ui', {
11
+ methods: ['GET', 'HEAD', 'POST', 'DELETE'],
12
+ authLevel: 'anonymous',
13
+ route: 'app/{*route}', // Wildcard route under /ui
14
+ handler: ui
15
+ });
16
+
17
+ // Function to serve the React app under /app
18
+ export async function ui(req: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
19
+ const createBrowserInitState = async (installation: SeekaAppInstallContext<ExampleAppAppInstallState>, logger: Logger ): Promise<ExampleAppAppUiClientInitState> => {
20
+ // Under no circumstances should internal or private api keys be exposed to the client/browser via the below object
21
+ // Below state will be passed in plain text to the UI app, important to be selective (no spread operators on installation state)
22
+ // and not expose any internal or private api keys to the client/browser
23
+ return {
24
+ exampleInstallSetting1: installation?.installationState?.installationSettings?.exampleInstallSetting1
25
+ }
26
+ }
27
+
28
+ const res = await generateAppUiHttpRequestResponse(
29
+ req.url,
30
+ req.method as HttpMethod,
31
+ req.headers as Headers,
32
+ context,
33
+ createBrowserInitState,
34
+ async(...args) => {
35
+ return await apiRouteHandler(req, context, ...args)
36
+ }
37
+ )
38
+
39
+ return {
40
+ status: res.status,
41
+ headers: res.headers as HeadersInit,
42
+ cookies: res.cookies,
43
+ body: res.body as HttpResponseInit['body'],
44
+ jsonBody: res.jsonBody
45
+ };
46
+ }
@@ -10,9 +10,15 @@
10
10
  "module": "CommonJS",
11
11
  "target": "ES6",
12
12
  "resolveJsonModule": true,
13
- "isolatedModules": false,
13
+ "allowSyntheticDefaultImports": true,
14
+ "moduleResolution": "node",
15
+ "isolatedModules": false
14
16
  },
15
17
  "include": [
16
- "src/functions/*.ts"
18
+ "src/**/*.ts",
19
+ "src/definitions.d.ts"
20
+ ],
21
+ "exclude": [
22
+ "node_modules"
17
23
  ]
18
24
  }
@@ -0,0 +1,40 @@
1
+ # Material UI - Create React App example in TypeScript
2
+
3
+ ## How to use
4
+
5
+ Download the example [or clone the repo](https://github.com/mui/material-ui):
6
+
7
+ <!-- #default-branch-switch -->
8
+
9
+ ```bash
10
+ curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/material-ui-cra-ts
11
+ cd material-ui-cra-ts
12
+ ```
13
+
14
+ Install it and run:
15
+
16
+ ```bash
17
+ npm install
18
+ npm start
19
+ ```
20
+
21
+ or:
22
+
23
+ <!-- #default-branch-switch -->
24
+
25
+ [![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/sandbox/github/mui/material-ui/tree/master/examples/material-ui-cra-ts)
26
+
27
+ [![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/material-ui-cra-ts)
28
+
29
+ ## The idea behind the example
30
+
31
+ This example demonstrates how you can use Material UI with [Create React App](https://github.com/facebookincubator/create-react-app) in [TypeScript](https://github.com/Microsoft/TypeScript).
32
+ It includes `@mui/material` and its peer dependencies, including [Emotion](https://emotion.sh/docs/introduction), the default style engine in Material UI v6.
33
+ If you prefer, you can [use styled-components instead](https://mui.com/material-ui/integrations/interoperability/#styled-components).
34
+
35
+ ## What's next?
36
+
37
+ <!-- #default-branch-switch -->
38
+
39
+ You now have a working example project.
40
+ You can head back to the documentation and continue by browsing the [templates](https://mui.com/material-ui/getting-started/templates/) section.
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8"/>
6
+ <link rel="icon" href="/favicon.ico"/>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
8
+ <title>Seeka ExampleApp</title>
9
+ <!-- Fonts to support Material Design -->
10
+ <link rel="preconnect" href="https://fonts.googleapis.com"/>
11
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
12
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"/>
13
+ <script vite-ignore src="%VITE_BASE_URL%config.js?c=%VITE_RELEASE_TAG%"></script>
14
+ <script type="module" src="/src/index.tsx"></script>
15
+ </head>
16
+ <body>
17
+ <noscript>You need to enable JavaScript to run this app.</noscript>
18
+ <div id="root"></div>
19
+ </body>
20
+
21
+ </html>