@seeka-labs/cli-apps 1.1.24 → 1.1.25
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/LICENSE +19 -19
- package/dist/index.js +10 -10
- package/dist/index.js.map +4 -4
- package/dist/init-templates/aws-lambda/.example.gitignore +48 -48
- package/dist/init-templates/aws-lambda/.nvmrc +1 -1
- package/dist/init-templates/aws-lambda/README.md +76 -76
- package/dist/init-templates/aws-lambda/jest.config.js +4 -4
- package/dist/init-templates/aws-lambda/package.json +51 -54
- package/dist/init-templates/aws-lambda/src/index.test.ts +6 -6
- package/dist/init-templates/aws-lambda/src/lib/logging/index.ts +87 -87
- package/dist/init-templates/aws-lambda/src/lib/state/redis/index.ts +64 -64
- package/dist/init-templates/aws-lambda/src/lib/state/seeka/installations.ts +66 -66
- package/dist/init-templates/aws-lambda/src/routes/seekaAppWebhook.ts +193 -193
- package/dist/init-templates/azure-function/.example.gitignore +47 -47
- package/dist/init-templates/azure-function/README.md +107 -107
- package/dist/init-templates/azure-function/jest.config.js +4 -4
- package/dist/init-templates/azure-function/package.json +45 -48
- package/dist/init-templates/azure-function/scripts/dev-queue-setup.js +29 -29
- package/dist/init-templates/azure-function/src/functions/healthCheck.ts +13 -13
- package/dist/init-templates/azure-function/src/functions/pollingExample.ts +39 -39
- package/dist/init-templates/azure-function/src/functions/queueExample.ts +66 -66
- package/dist/init-templates/azure-function/src/functions/seekaAppWebhook.ts +236 -236
- package/dist/init-templates/azure-function/src/index.test.ts +6 -6
- package/dist/init-templates/azure-function/src/lib/browser/index.ts +54 -54
- package/dist/init-templates/azure-function/src/lib/browser/models/index.ts +6 -6
- package/dist/init-templates/azure-function/src/lib/jobs/index.ts +95 -95
- package/dist/init-templates/azure-function/src/lib/logging/index.ts +92 -92
- package/dist/init-templates/azure-function/src/lib/state/redis/index.ts +64 -64
- package/dist/init-templates/azure-function/src/lib/state/seeka/installations.ts +66 -66
- package/dist/init-templates/browser/.editorconfig +14 -14
- package/dist/init-templates/browser/.eslintrc.cjs +1 -1
- package/dist/init-templates/browser/.yarnrc +1 -1
- package/dist/init-templates/browser/jest.config.js +11 -11
- package/dist/init-templates/browser/package.json +3 -3
- package/dist/init-templates/browser/scripts/esbuild/build-browser-plugin.mjs +110 -110
- package/dist/init-templates/browser/scripts/esbuild/plugins/importAsGlobals.mjs +38 -38
- package/dist/init-templates/browser/src/browser.ts +12 -12
- package/dist/init-templates/browser/src/plugin/index.test.ts +6 -6
- package/dist/init-templates/browser/src/plugin/index.ts +49 -49
- package/dist/init-templates/browser/tsconfig.json +34 -34
- package/dist/init-templates/netlify-function/.env.example +17 -17
- package/dist/init-templates/netlify-function/.example.gitignore +36 -36
- package/dist/init-templates/netlify-function/.nvmrc +1 -1
- package/dist/init-templates/netlify-function/.vscode/launch.json +44 -44
- package/dist/init-templates/netlify-function/README.md +61 -61
- package/dist/init-templates/netlify-function/jest.config.js +4 -4
- package/dist/init-templates/netlify-function/netlify.toml +6 -6
- package/dist/init-templates/netlify-function/package.json +11 -14
- package/dist/init-templates/netlify-function/src/api/example-job-background/index.ts +51 -51
- package/dist/init-templates/netlify-function/src/api/polling-example-job-scheduled/index.ts +45 -45
- package/dist/init-templates/netlify-function/src/api/seeka-app-webhook/index.ts +216 -216
- package/dist/init-templates/netlify-function/src/index.test.ts +6 -6
- package/dist/init-templates/netlify-function/src/lib/jobs/index.ts +67 -67
- package/dist/init-templates/netlify-function/src/lib/logging/index.ts +90 -90
- package/dist/init-templates/netlify-function/src/lib/state/redis/index.ts +64 -64
- package/dist/init-templates/netlify-function/src/lib/state/seeka/installations.ts +66 -66
- package/package.json +7 -7
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import winston, { Logger } from 'winston';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import util from 'util';
|
|
5
|
-
import { SeekaBrowserSdkPluginWebhookResponse } from '@seeka-labs/sdk-apps-server'
|
|
6
|
-
import {
|
|
7
|
-
SeekaAppInstallState
|
|
8
|
-
} from '../state/seeka/installations';
|
|
9
|
-
import type { ISampleAppBrowserSdkPluginConfig } from './models';
|
|
10
|
-
|
|
11
|
-
const fileExistsAsync = util.promisify(fs.exists);
|
|
12
|
-
const readFileAsync = util.promisify(fs.readFile);
|
|
13
|
-
|
|
14
|
-
export async function getSeekaBrowserPlugin(installation: SeekaAppInstallState, logger: Logger): Promise<SeekaBrowserSdkPluginWebhookResponse> {
|
|
15
|
-
logger.profile('http.seeka.browser.plugin')
|
|
16
|
-
|
|
17
|
-
let content: string | undefined;
|
|
18
|
-
let init: string | undefined;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
logger.verbose('Handling request for browser plugin content');
|
|
22
|
-
const browserPluginPath = path.resolve('./dist/src/browser/seeka-app-example-name.min.js');
|
|
23
|
-
if (await fileExistsAsync(browserPluginPath)) {
|
|
24
|
-
// Content of the script. This should not change per installation it should be the same for all installations
|
|
25
|
-
content = await readFileAsync(browserPluginPath, 'utf8');
|
|
26
|
-
|
|
27
|
-
// Configuration for the plugin. This can be different per installation.
|
|
28
|
-
// Configuration is sent to the browser so NO SENSITIVE CONFIG / SERVER SECRETS SHOULD BE SPECIFIED HERE.
|
|
29
|
-
const pluginConfig = {
|
|
30
|
-
myAppInstallSetting1: installation.installationSettings.myAppInstallSetting1,
|
|
31
|
-
myAppInstallSetting2: installation.installationSettings.myAppInstallSetting2,
|
|
32
|
-
appId: process.env.SEEKA_APP_ID,
|
|
33
|
-
appInstallId: installation.applicationInstallId,
|
|
34
|
-
appUrl: process.env.SELF_HOST_BASEURL
|
|
35
|
-
} as ISampleAppBrowserSdkPluginConfig;
|
|
36
|
-
init = `SampleAppConvergeSdkPlugin(${JSON.stringify(pluginConfig)})`;
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
logger.error('Browser plugin content not found', { path: browserPluginPath });
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
logger.error('Failed to handle request for browser plugin content', { ex: winston.exceptions.getAllInfo(err) });
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
logger.profile('http.seeka.browser.plugin')
|
|
47
|
-
logger.verbose('Request for browser plugin content handled');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!content || !init) {
|
|
51
|
-
throw new Error('Failed to handle request for browser plugin content')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { content, init: init }
|
|
1
|
+
import winston, { Logger } from 'winston';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import util from 'util';
|
|
5
|
+
import { SeekaBrowserSdkPluginWebhookResponse } from '@seeka-labs/sdk-apps-server'
|
|
6
|
+
import {
|
|
7
|
+
SeekaAppInstallState
|
|
8
|
+
} from '../state/seeka/installations';
|
|
9
|
+
import type { ISampleAppBrowserSdkPluginConfig } from './models';
|
|
10
|
+
|
|
11
|
+
const fileExistsAsync = util.promisify(fs.exists);
|
|
12
|
+
const readFileAsync = util.promisify(fs.readFile);
|
|
13
|
+
|
|
14
|
+
export async function getSeekaBrowserPlugin(installation: SeekaAppInstallState, logger: Logger): Promise<SeekaBrowserSdkPluginWebhookResponse> {
|
|
15
|
+
logger.profile('http.seeka.browser.plugin')
|
|
16
|
+
|
|
17
|
+
let content: string | undefined;
|
|
18
|
+
let init: string | undefined;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
logger.verbose('Handling request for browser plugin content');
|
|
22
|
+
const browserPluginPath = path.resolve('./dist/src/browser/seeka-app-example-name.min.js');
|
|
23
|
+
if (await fileExistsAsync(browserPluginPath)) {
|
|
24
|
+
// Content of the script. This should not change per installation it should be the same for all installations
|
|
25
|
+
content = await readFileAsync(browserPluginPath, 'utf8');
|
|
26
|
+
|
|
27
|
+
// Configuration for the plugin. This can be different per installation.
|
|
28
|
+
// Configuration is sent to the browser so NO SENSITIVE CONFIG / SERVER SECRETS SHOULD BE SPECIFIED HERE.
|
|
29
|
+
const pluginConfig = {
|
|
30
|
+
myAppInstallSetting1: installation.installationSettings.myAppInstallSetting1,
|
|
31
|
+
myAppInstallSetting2: installation.installationSettings.myAppInstallSetting2,
|
|
32
|
+
appId: process.env.SEEKA_APP_ID,
|
|
33
|
+
appInstallId: installation.applicationInstallId,
|
|
34
|
+
appUrl: process.env.SELF_HOST_BASEURL
|
|
35
|
+
} as ISampleAppBrowserSdkPluginConfig;
|
|
36
|
+
init = `SampleAppConvergeSdkPlugin(${JSON.stringify(pluginConfig)})`;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
logger.error('Browser plugin content not found', { path: browserPluginPath });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
logger.error('Failed to handle request for browser plugin content', { ex: winston.exceptions.getAllInfo(err) });
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
logger.profile('http.seeka.browser.plugin')
|
|
47
|
+
logger.verbose('Request for browser plugin content handled');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!content || !init) {
|
|
51
|
+
throw new Error('Failed to handle request for browser plugin content')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { content, init: init }
|
|
55
55
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export interface ISampleAppBrowserSdkPluginConfig {
|
|
2
|
-
myAppInstallSetting1: string | number | undefined;
|
|
3
|
-
myAppInstallSetting2: string | number | undefined;
|
|
4
|
-
appId: string
|
|
5
|
-
appInstallId: string
|
|
6
|
-
appUrl: string
|
|
1
|
+
export interface ISampleAppBrowserSdkPluginConfig {
|
|
2
|
+
myAppInstallSetting1: string | number | undefined;
|
|
3
|
+
myAppInstallSetting2: string | number | undefined;
|
|
4
|
+
appId: string
|
|
5
|
+
appInstallId: string
|
|
6
|
+
appUrl: string
|
|
7
7
|
}
|
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
import type { Logger } from 'winston';
|
|
2
|
-
import { QueueClient } from '@azure/storage-queue';
|
|
3
|
-
import { isArray } from 'lodash';
|
|
4
|
-
import winston from 'winston';
|
|
5
|
-
|
|
6
|
-
export interface BackgroundJobRequestContext {
|
|
7
|
-
organisationId?: string;
|
|
8
|
-
organisationBrandId?: string;
|
|
9
|
-
applicationInstallId?: string;
|
|
10
|
-
causationId: string
|
|
11
|
-
correlationId: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const signatureHeaderName = 'x-signature-hmac';
|
|
15
|
-
export const apiKeyHeaderName = 'x-api-key';
|
|
16
|
-
|
|
17
|
-
export const jobNames = {
|
|
18
|
-
pollingExample: 'polling-example',
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const queueNames = {
|
|
22
|
-
queueItemExampleQueueName: 'sample-queue-name'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const triggerBackgroundJob = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
26
|
-
const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, queueName);
|
|
27
|
-
return triggerBackgroundJobWithQueue(queueClient, context, logger);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const serialiseQueuePayload = (payload: unknown): string => {
|
|
31
|
-
const jsonString = JSON.stringify(payload)
|
|
32
|
-
return Buffer.from(jsonString).toString('base64')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const deserialiseQueuePayload = <TPayload>(queueItem: any, logger: Logger): TPayload[] => {
|
|
36
|
-
try {
|
|
37
|
-
if (typeof queueItem !== 'string') {
|
|
38
|
-
if (isArray(queueItem)) {
|
|
39
|
-
return queueItem
|
|
40
|
-
}
|
|
41
|
-
return [queueItem]
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
const jsonString = Buffer.from(queueItem, 'base64').toString()
|
|
45
|
-
const parsed = JSON.parse(jsonString);
|
|
46
|
-
|
|
47
|
-
if (isArray(parsed)) {
|
|
48
|
-
return parsed
|
|
49
|
-
}
|
|
50
|
-
return [parsed]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
catch (err) {
|
|
54
|
-
logger.error('Failed to deserialise queue payload', { ex: winston.exceptions.getAllInfo(err), queueItem });
|
|
55
|
-
throw new Error(`Failed to deserialise queue payload`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const triggerBackgroundJobWithQueue = async (queueClient: QueueClient, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
60
|
-
const body = {
|
|
61
|
-
...context
|
|
62
|
-
}
|
|
63
|
-
const bodyStr = serialiseQueuePayload(body);
|
|
64
|
-
|
|
65
|
-
const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
|
|
66
|
-
|
|
67
|
-
if (response.errorCode) {
|
|
68
|
-
const { requestId, date, errorCode } = response;
|
|
69
|
-
logger.error("Failed to trigger background job", { body, requestId, date, errorCode })
|
|
70
|
-
throw new Error(`Failed to trigger background job: ${response.errorCode}`);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
logger.debug("Background job triggered", { body, messageId: response.messageId, context })
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export const sendQueueMessageToPoisonQueue = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
78
|
-
const body = {
|
|
79
|
-
...context
|
|
80
|
-
}
|
|
81
|
-
const bodyStr = serialiseQueuePayload(body);
|
|
82
|
-
|
|
83
|
-
// https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-typescript#poison-messages
|
|
84
|
-
const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, `${queueName}-poison`);
|
|
85
|
-
|
|
86
|
-
const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
|
|
87
|
-
|
|
88
|
-
if (response.errorCode) {
|
|
89
|
-
const { requestId, date, errorCode } = response;
|
|
90
|
-
logger.error("Failed to push to poison queue", { body, requestId, date, errorCode })
|
|
91
|
-
throw new Error(`Failed to push to poison queue: ${response.errorCode}`);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
logger.verbose("Message pushed to poison queue", { body, messageId: response.messageId, context })
|
|
95
|
-
}
|
|
1
|
+
import type { Logger } from 'winston';
|
|
2
|
+
import { QueueClient } from '@azure/storage-queue';
|
|
3
|
+
import { isArray } from 'lodash';
|
|
4
|
+
import winston from 'winston';
|
|
5
|
+
|
|
6
|
+
export interface BackgroundJobRequestContext {
|
|
7
|
+
organisationId?: string;
|
|
8
|
+
organisationBrandId?: string;
|
|
9
|
+
applicationInstallId?: string;
|
|
10
|
+
causationId: string
|
|
11
|
+
correlationId: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const signatureHeaderName = 'x-signature-hmac';
|
|
15
|
+
export const apiKeyHeaderName = 'x-api-key';
|
|
16
|
+
|
|
17
|
+
export const jobNames = {
|
|
18
|
+
pollingExample: 'polling-example',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const queueNames = {
|
|
22
|
+
queueItemExampleQueueName: 'sample-queue-name'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const triggerBackgroundJob = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
26
|
+
const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, queueName);
|
|
27
|
+
return triggerBackgroundJobWithQueue(queueClient, context, logger);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const serialiseQueuePayload = (payload: unknown): string => {
|
|
31
|
+
const jsonString = JSON.stringify(payload)
|
|
32
|
+
return Buffer.from(jsonString).toString('base64')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const deserialiseQueuePayload = <TPayload>(queueItem: any, logger: Logger): TPayload[] => {
|
|
36
|
+
try {
|
|
37
|
+
if (typeof queueItem !== 'string') {
|
|
38
|
+
if (isArray(queueItem)) {
|
|
39
|
+
return queueItem
|
|
40
|
+
}
|
|
41
|
+
return [queueItem]
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const jsonString = Buffer.from(queueItem, 'base64').toString()
|
|
45
|
+
const parsed = JSON.parse(jsonString);
|
|
46
|
+
|
|
47
|
+
if (isArray(parsed)) {
|
|
48
|
+
return parsed
|
|
49
|
+
}
|
|
50
|
+
return [parsed]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logger.error('Failed to deserialise queue payload', { ex: winston.exceptions.getAllInfo(err), queueItem });
|
|
55
|
+
throw new Error(`Failed to deserialise queue payload`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const triggerBackgroundJobWithQueue = async (queueClient: QueueClient, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
60
|
+
const body = {
|
|
61
|
+
...context
|
|
62
|
+
}
|
|
63
|
+
const bodyStr = serialiseQueuePayload(body);
|
|
64
|
+
|
|
65
|
+
const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
|
|
66
|
+
|
|
67
|
+
if (response.errorCode) {
|
|
68
|
+
const { requestId, date, errorCode } = response;
|
|
69
|
+
logger.error("Failed to trigger background job", { body, requestId, date, errorCode })
|
|
70
|
+
throw new Error(`Failed to trigger background job: ${response.errorCode}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
logger.debug("Background job triggered", { body, messageId: response.messageId, context })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const sendQueueMessageToPoisonQueue = async (queueName: string, context: BackgroundJobRequestContext, logger: Logger): Promise<void> => {
|
|
78
|
+
const body = {
|
|
79
|
+
...context
|
|
80
|
+
}
|
|
81
|
+
const bodyStr = serialiseQueuePayload(body);
|
|
82
|
+
|
|
83
|
+
// https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=python-v2%2Cisolated-process%2Cnodejs-v4%2Cextensionv5&pivots=programming-language-typescript#poison-messages
|
|
84
|
+
const queueClient = new QueueClient(process.env.AzureWebJobsStorage as string, `${queueName}-poison`);
|
|
85
|
+
|
|
86
|
+
const response = await queueClient.sendMessage(bodyStr, { messageTimeToLive: -1 });
|
|
87
|
+
|
|
88
|
+
if (response.errorCode) {
|
|
89
|
+
const { requestId, date, errorCode } = response;
|
|
90
|
+
logger.error("Failed to push to poison queue", { body, requestId, date, errorCode })
|
|
91
|
+
throw new Error(`Failed to push to poison queue: ${response.errorCode}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
logger.verbose("Message pushed to poison queue", { body, messageId: response.messageId, context })
|
|
95
|
+
}
|
|
96
96
|
}
|
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
import * as winston from 'winston';
|
|
2
|
-
|
|
3
|
-
import { InvocationContext } from '@azure/functions';
|
|
4
|
-
import { SeqTransport } from '@datalust/winston-seq';
|
|
5
|
-
|
|
6
|
-
import * as packageJson from '../../../package.json';
|
|
7
|
-
import { BackgroundJobRequestContext } from '../jobs';
|
|
8
|
-
|
|
9
|
-
import type {
|
|
10
|
-
SeekaWebhookPayload, SeekaWebhookPayloadOfSeekaAppWebhookContext
|
|
11
|
-
} from '@seeka-labs/sdk-apps-server';
|
|
12
|
-
|
|
13
|
-
const loggerTransports: winston.transport[] = [
|
|
14
|
-
new winston.transports.Console({
|
|
15
|
-
level: process.env.LOGGING_LEVEL,
|
|
16
|
-
format: winston.format.combine(
|
|
17
|
-
winston.format.errors({ stack: true }),
|
|
18
|
-
winston.format.cli(),
|
|
19
|
-
winston.format.splat(),
|
|
20
|
-
),
|
|
21
|
-
handleExceptions: true,
|
|
22
|
-
handleRejections: true,
|
|
23
|
-
}),
|
|
24
|
-
]
|
|
25
|
-
if (process.env.LOGGING_SEQ_SERVERURL) {
|
|
26
|
-
loggerTransports.push(
|
|
27
|
-
new SeqTransport({
|
|
28
|
-
level: process.env.LOGGING_LEVEL,
|
|
29
|
-
serverUrl: process.env.LOGGING_SEQ_SERVERURL,
|
|
30
|
-
apiKey: process.env.LOGGING_SEQ_APIKEY,
|
|
31
|
-
onError: ((e) => { console.error('Failed to configure Seq logging transport', e) }),
|
|
32
|
-
format: winston.format.combine(
|
|
33
|
-
winston.format.errors({ stack: true }),
|
|
34
|
-
winston.format.json(),
|
|
35
|
-
),
|
|
36
|
-
handleExceptions: true,
|
|
37
|
-
handleRejections: true,
|
|
38
|
-
})
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const logger = winston.createLogger({
|
|
43
|
-
level: process.env.LOGGING_LEVEL,
|
|
44
|
-
defaultMeta: {
|
|
45
|
-
seekaAppId: process.env.SEEKA_APP_ID,
|
|
46
|
-
Hosting_Provider: 'azure',
|
|
47
|
-
Release_Version: `v${packageJson.version}`,
|
|
48
|
-
Hosting_Region: process.env.REGION_NAME,
|
|
49
|
-
Service_Instance_Id: process.env.WEBSITE_ROLE_INSTANCE_ID,
|
|
50
|
-
AzureFunctions_FunctionName: process.env.WEBSITE_SITE_NAME,
|
|
51
|
-
Service: `app/${packageJson.name}`,
|
|
52
|
-
},
|
|
53
|
-
transports: loggerTransports,
|
|
54
|
-
exitOnError: false,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
export const childLogger = (meta: object) => logger.child(meta);
|
|
58
|
-
|
|
59
|
-
export const webhookLogger = (payload: SeekaWebhookPayload, functionContext: InvocationContext) => {
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
-
const meta: any = {
|
|
62
|
-
seekaWebhookType: payload.type,
|
|
63
|
-
seekaWebhookIsTest: payload.isTest,
|
|
64
|
-
RequestId: payload.requestId,
|
|
65
|
-
AzureFunctions_InvocationId: functionContext.invocationId,
|
|
66
|
-
CausationId: payload.causationId,
|
|
67
|
-
CorrelationId: payload.causationId,
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const context = (payload as SeekaWebhookPayloadOfSeekaAppWebhookContext).context;
|
|
71
|
-
if (context) {
|
|
72
|
-
meta.seekaAppInstallId = context.applicationInstallId;
|
|
73
|
-
meta.seekaAppInstallOrganisationBrandId = context.organisationBrandId;
|
|
74
|
-
meta.seekaAppInstallOrganisationId = context.organisationId;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return childLogger(meta)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export const backgroundJobLogger = (jobName: string, jobContext: BackgroundJobRequestContext | undefined, functionContext: InvocationContext) => {
|
|
81
|
-
const meta: object = {
|
|
82
|
-
jobContext,
|
|
83
|
-
jobName,
|
|
84
|
-
AzureFunctions_InvocationId: functionContext.invocationId,
|
|
85
|
-
CausationId: jobContext?.causationId,
|
|
86
|
-
CorrelationId: jobContext?.causationId,
|
|
87
|
-
seekaAppInstallId: jobContext?.applicationInstallId,
|
|
88
|
-
seekaAppInstallOrganisationBrandId: jobContext?.organisationBrandId,
|
|
89
|
-
seekaAppInstallOrganisationId: jobContext?.organisationId
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return childLogger(meta)
|
|
1
|
+
import * as winston from 'winston';
|
|
2
|
+
|
|
3
|
+
import { InvocationContext } from '@azure/functions';
|
|
4
|
+
import { SeqTransport } from '@datalust/winston-seq';
|
|
5
|
+
|
|
6
|
+
import * as packageJson from '../../../package.json';
|
|
7
|
+
import { BackgroundJobRequestContext } from '../jobs';
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
SeekaWebhookPayload, SeekaWebhookPayloadOfSeekaAppWebhookContext
|
|
11
|
+
} from '@seeka-labs/sdk-apps-server';
|
|
12
|
+
|
|
13
|
+
const loggerTransports: winston.transport[] = [
|
|
14
|
+
new winston.transports.Console({
|
|
15
|
+
level: process.env.LOGGING_LEVEL,
|
|
16
|
+
format: winston.format.combine(
|
|
17
|
+
winston.format.errors({ stack: true }),
|
|
18
|
+
winston.format.cli(),
|
|
19
|
+
winston.format.splat(),
|
|
20
|
+
),
|
|
21
|
+
handleExceptions: true,
|
|
22
|
+
handleRejections: true,
|
|
23
|
+
}),
|
|
24
|
+
]
|
|
25
|
+
if (process.env.LOGGING_SEQ_SERVERURL) {
|
|
26
|
+
loggerTransports.push(
|
|
27
|
+
new SeqTransport({
|
|
28
|
+
level: process.env.LOGGING_LEVEL,
|
|
29
|
+
serverUrl: process.env.LOGGING_SEQ_SERVERURL,
|
|
30
|
+
apiKey: process.env.LOGGING_SEQ_APIKEY,
|
|
31
|
+
onError: ((e) => { console.error('Failed to configure Seq logging transport', e) }),
|
|
32
|
+
format: winston.format.combine(
|
|
33
|
+
winston.format.errors({ stack: true }),
|
|
34
|
+
winston.format.json(),
|
|
35
|
+
),
|
|
36
|
+
handleExceptions: true,
|
|
37
|
+
handleRejections: true,
|
|
38
|
+
})
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const logger = winston.createLogger({
|
|
43
|
+
level: process.env.LOGGING_LEVEL,
|
|
44
|
+
defaultMeta: {
|
|
45
|
+
seekaAppId: process.env.SEEKA_APP_ID,
|
|
46
|
+
Hosting_Provider: 'azure',
|
|
47
|
+
Release_Version: `v${packageJson.version}`,
|
|
48
|
+
Hosting_Region: process.env.REGION_NAME,
|
|
49
|
+
Service_Instance_Id: process.env.WEBSITE_ROLE_INSTANCE_ID,
|
|
50
|
+
AzureFunctions_FunctionName: process.env.WEBSITE_SITE_NAME,
|
|
51
|
+
Service: `app/${packageJson.name}`,
|
|
52
|
+
},
|
|
53
|
+
transports: loggerTransports,
|
|
54
|
+
exitOnError: false,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const childLogger = (meta: object) => logger.child(meta);
|
|
58
|
+
|
|
59
|
+
export const webhookLogger = (payload: SeekaWebhookPayload, functionContext: InvocationContext) => {
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
const meta: any = {
|
|
62
|
+
seekaWebhookType: payload.type,
|
|
63
|
+
seekaWebhookIsTest: payload.isTest,
|
|
64
|
+
RequestId: payload.requestId,
|
|
65
|
+
AzureFunctions_InvocationId: functionContext.invocationId,
|
|
66
|
+
CausationId: payload.causationId,
|
|
67
|
+
CorrelationId: payload.causationId,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const context = (payload as SeekaWebhookPayloadOfSeekaAppWebhookContext).context;
|
|
71
|
+
if (context) {
|
|
72
|
+
meta.seekaAppInstallId = context.applicationInstallId;
|
|
73
|
+
meta.seekaAppInstallOrganisationBrandId = context.organisationBrandId;
|
|
74
|
+
meta.seekaAppInstallOrganisationId = context.organisationId;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return childLogger(meta)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const backgroundJobLogger = (jobName: string, jobContext: BackgroundJobRequestContext | undefined, functionContext: InvocationContext) => {
|
|
81
|
+
const meta: object = {
|
|
82
|
+
jobContext,
|
|
83
|
+
jobName,
|
|
84
|
+
AzureFunctions_InvocationId: functionContext.invocationId,
|
|
85
|
+
CausationId: jobContext?.causationId,
|
|
86
|
+
CorrelationId: jobContext?.causationId,
|
|
87
|
+
seekaAppInstallId: jobContext?.applicationInstallId,
|
|
88
|
+
seekaAppInstallOrganisationBrandId: jobContext?.organisationBrandId,
|
|
89
|
+
seekaAppInstallOrganisationId: jobContext?.organisationId
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return childLogger(meta)
|
|
93
93
|
}
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import { createClient } from 'redis';
|
|
2
|
-
|
|
3
|
-
import { logger } from '../../logging';
|
|
4
|
-
import winston from 'winston';
|
|
5
|
-
|
|
6
|
-
const redisProtocol = process.env.REDIS_CONNECTION_TLS === 'true' ? 'rediss://' : 'redis://';
|
|
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
|
-
|
|
9
|
-
const redisClient = createClient({
|
|
10
|
-
url: redisConn
|
|
11
|
-
})
|
|
12
|
-
.on('error', (err: any) => logger.error('Redis Client ', { ex: winston.exceptions.getAllInfo(err) }));
|
|
13
|
-
|
|
14
|
-
export const connect = async () => {
|
|
15
|
-
await redisClient.connect();
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const isConnected = () => {
|
|
19
|
-
return redisClient.isOpen;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const disconnect = async () => {
|
|
23
|
-
await redisClient.disconnect();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const getKeyPrefix = (stateType: string) => `seeka:app:${process.env.SEEKA_APP_ID}:${stateType}`
|
|
27
|
-
const getKey = (stateType: string, key: string) => `${getKeyPrefix(stateType)}:${key}`
|
|
28
|
-
|
|
29
|
-
export async function getOrCreate<TState>(stateType: string, key: string, toCreate: TState): Promise<TState> {
|
|
30
|
-
const fullKey = getKey(stateType, key);
|
|
31
|
-
const existingStr = await redisClient.get(fullKey);
|
|
32
|
-
if (existingStr) return JSON.parse(existingStr);
|
|
33
|
-
|
|
34
|
-
await redisClient.set(fullKey, JSON.stringify(toCreate));
|
|
35
|
-
|
|
36
|
-
return toCreate;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export async function tryGet<TState>(stateType: string, key: string): Promise<TState | null> {
|
|
40
|
-
const fullKey = getKey(stateType, key);
|
|
41
|
-
const existingStr = await redisClient.get(fullKey);
|
|
42
|
-
if (existingStr) return JSON.parse(existingStr);
|
|
43
|
-
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export async function getList<TState>(stateType: string): Promise<TState[]> {
|
|
48
|
-
const prefix = getKeyPrefix(stateType);
|
|
49
|
-
const allKeys = await redisClient.keys(`${prefix}:*`);
|
|
50
|
-
const listStr = await redisClient.mGet(allKeys);
|
|
51
|
-
|
|
52
|
-
if (listStr) return listStr.filter(e => Boolean(e)).map(e => JSON.parse(e as string));
|
|
53
|
-
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function set<TState>(stateType: string, key: string, toCreate: TState): Promise<void> {
|
|
58
|
-
const fullKey = getKey(stateType, key);
|
|
59
|
-
await redisClient.set(fullKey, JSON.stringify(toCreate));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function remove(stateType: string, key: string): Promise<void> {
|
|
63
|
-
const fullKey = getKey(stateType, key);
|
|
64
|
-
await redisClient.del(fullKey);
|
|
1
|
+
import { createClient } from 'redis';
|
|
2
|
+
|
|
3
|
+
import { logger } from '../../logging';
|
|
4
|
+
import winston from 'winston';
|
|
5
|
+
|
|
6
|
+
const redisProtocol = process.env.REDIS_CONNECTION_TLS === 'true' ? 'rediss://' : 'redis://';
|
|
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
|
+
|
|
9
|
+
const redisClient = createClient({
|
|
10
|
+
url: redisConn
|
|
11
|
+
})
|
|
12
|
+
.on('error', (err: any) => logger.error('Redis Client ', { ex: winston.exceptions.getAllInfo(err) }));
|
|
13
|
+
|
|
14
|
+
export const connect = async () => {
|
|
15
|
+
await redisClient.connect();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const isConnected = () => {
|
|
19
|
+
return redisClient.isOpen;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const disconnect = async () => {
|
|
23
|
+
await redisClient.disconnect();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getKeyPrefix = (stateType: string) => `seeka:app:${process.env.SEEKA_APP_ID}:${stateType}`
|
|
27
|
+
const getKey = (stateType: string, key: string) => `${getKeyPrefix(stateType)}:${key}`
|
|
28
|
+
|
|
29
|
+
export async function getOrCreate<TState>(stateType: string, key: string, toCreate: TState): Promise<TState> {
|
|
30
|
+
const fullKey = getKey(stateType, key);
|
|
31
|
+
const existingStr = await redisClient.get(fullKey);
|
|
32
|
+
if (existingStr) return JSON.parse(existingStr);
|
|
33
|
+
|
|
34
|
+
await redisClient.set(fullKey, JSON.stringify(toCreate));
|
|
35
|
+
|
|
36
|
+
return toCreate;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function tryGet<TState>(stateType: string, key: string): Promise<TState | null> {
|
|
40
|
+
const fullKey = getKey(stateType, key);
|
|
41
|
+
const existingStr = await redisClient.get(fullKey);
|
|
42
|
+
if (existingStr) return JSON.parse(existingStr);
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function getList<TState>(stateType: string): Promise<TState[]> {
|
|
48
|
+
const prefix = getKeyPrefix(stateType);
|
|
49
|
+
const allKeys = await redisClient.keys(`${prefix}:*`);
|
|
50
|
+
const listStr = await redisClient.mGet(allKeys);
|
|
51
|
+
|
|
52
|
+
if (listStr) return listStr.filter(e => Boolean(e)).map(e => JSON.parse(e as string));
|
|
53
|
+
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function set<TState>(stateType: string, key: string, toCreate: TState): Promise<void> {
|
|
58
|
+
const fullKey = getKey(stateType, key);
|
|
59
|
+
await redisClient.set(fullKey, JSON.stringify(toCreate));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function remove(stateType: string, key: string): Promise<void> {
|
|
63
|
+
const fullKey = getKey(stateType, key);
|
|
64
|
+
await redisClient.del(fullKey);
|
|
65
65
|
}
|