@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
|
@@ -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,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,
|
|
42
|
+
cookies: res.cookies,
|
|
43
|
+
body: res.body as HttpResponseInit['body'],
|
|
44
|
+
jsonBody: res.jsonBody
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -7,20 +7,28 @@
|
|
|
7
7
|
"FUNCTIONS_WORKER_RUNTIME": "node",
|
|
8
8
|
"FUNCTIONS_EXTENSION_VERSION": "~4",
|
|
9
9
|
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
|
|
10
|
-
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
|
11
|
-
"SEEKA_APP_ID": "",
|
|
12
|
-
"SEEKA_APP_SECRET": "",
|
|
13
10
|
"SEEKA_DEBUG_ENABLED": "true",
|
|
11
|
+
"NODE_TLS_REJECT_UNAUTHORIZED": "0",
|
|
12
|
+
"REDIS_CONNECTION_USER": "default",
|
|
13
|
+
"OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "",
|
|
14
|
+
"OTEL_EXPORTER_OTLP_LOGS_HEADERS": "",
|
|
14
15
|
"SEEKA_INGEST_URL": "",
|
|
15
16
|
"SEEKA_ISSUER_URL": "",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
17
|
+
"SEEKA_APP_ID": "",
|
|
18
|
+
"SEEKA_APP_SECRET": "",
|
|
19
|
+
"REDIS_CONNECTION_HOST": "",
|
|
18
20
|
"REDIS_CONNECTION_PASSWORD": "",
|
|
21
|
+
"AzureWebJobsStorage": "",
|
|
22
|
+
"SEEKA_TELEMETRY_URL": "",
|
|
23
|
+
"REDIS_CONNECTION_PORT": "6379",
|
|
19
24
|
"REDIS_CONNECTION_TLS": "true",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
+
"OTEL_LOG_LEVEL": "debug",
|
|
26
|
+
"LOGGING_LEVEL": "silly",
|
|
27
|
+
"SELF_HOST_BASEURL": "",
|
|
28
|
+
"APP_UI_BASE_PATH": "",
|
|
29
|
+
"OTEL_TRACES_EXPORTER": "none",
|
|
30
|
+
"OTEL_METRICS_EXPORTER": "none",
|
|
31
|
+
"OTEL_LOGS_EXPORTER": "none",
|
|
32
|
+
"OTEL_PROPAGATORS": "none"
|
|
25
33
|
}
|
|
26
34
|
}
|
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
"module": "CommonJS",
|
|
11
11
|
"target": "ES6",
|
|
12
12
|
"resolveJsonModule": true,
|
|
13
|
-
"
|
|
13
|
+
"allowSyntheticDefaultImports": true,
|
|
14
|
+
"moduleResolution": "node",
|
|
15
|
+
"isolatedModules": false
|
|
14
16
|
},
|
|
15
17
|
"include": [
|
|
16
|
-
"src
|
|
18
|
+
"src/**/*.ts",
|
|
19
|
+
"src/definitions.d.ts"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules"
|
|
17
23
|
]
|
|
18
24
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
PORT=3000
|
|
2
|
+
EXTEND_ESLINT=TRUE
|
|
3
|
+
REACT_APP_RELEASE_TAG=dev
|
|
4
|
+
VITE_BASE_URL=/app/
|
|
5
|
+
VITE_URLS_SEEKA_API_CORE= https://api-localdev-env0-seeka.au.ngrok.io
|
|
6
|
+
VITE_URLS_SEEKA_APP_CORE_ORIGIN=https://localhost:4200
|
|
7
|
+
|
|
8
|
+
PROD_VITE_URLS_SEEKA_API_CORE=https://api.seeka.services
|
|
9
|
+
PROD_VITE_URLS_SEEKA_APP_CORE_ORIGIN=https://seeka.app
|
|
@@ -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
|
+
[](https://codesandbox.io/p/sandbox/github/mui/material-ui/tree/master/examples/material-ui-cra-ts)
|
|
26
|
+
|
|
27
|
+
[](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>
|