@kapeta/local-cluster-service 0.59.0 → 0.60.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/cjs/src/clusterService.d.ts +1 -2
- package/dist/cjs/src/clusterService.js +14 -5
- package/dist/cjs/src/storm/UIServer.d.ts +11 -0
- package/dist/cjs/src/storm/UIServer.js +47 -0
- package/dist/cjs/src/storm/event-parser.d.ts +1 -0
- package/dist/cjs/src/storm/event-parser.js +7 -0
- package/dist/cjs/src/storm/routes.js +22 -2
- package/dist/cjs/test/storm/event-parser.test.js +4 -0
- package/dist/esm/src/clusterService.d.ts +1 -2
- package/dist/esm/src/clusterService.js +14 -5
- package/dist/esm/src/storm/UIServer.d.ts +11 -0
- package/dist/esm/src/storm/UIServer.js +47 -0
- package/dist/esm/src/storm/event-parser.d.ts +1 -0
- package/dist/esm/src/storm/event-parser.js +7 -0
- package/dist/esm/src/storm/routes.js +22 -2
- package/dist/esm/test/storm/event-parser.test.js +4 -0
- package/package.json +1 -1
- package/src/clusterService.ts +14 -5
- package/src/storm/UIServer.ts +50 -0
- package/src/storm/event-parser.ts +8 -0
- package/src/storm/routes.ts +26 -2
- package/test/storm/event-parser.test.ts +5 -0
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## [0.60.1](https://github.com/kapetacom/local-cluster-service/compare/v0.60.0...v0.60.1) (2024-08-06)
|
2
|
+
|
3
|
+
|
4
|
+
### Bug Fixes
|
5
|
+
|
6
|
+
* broke tests ([e916371](https://github.com/kapetacom/local-cluster-service/commit/e9163710178a01259723346efa346c094794de0b))
|
7
|
+
* ensure valid base name for ai generated services ([b3a15ec](https://github.com/kapetacom/local-cluster-service/commit/b3a15ecc297b98671b3029d668b2da46113086ff))
|
8
|
+
|
9
|
+
# [0.60.0](https://github.com/kapetacom/local-cluster-service/compare/v0.59.0...v0.60.0) (2024-08-05)
|
10
|
+
|
11
|
+
|
12
|
+
### Features
|
13
|
+
|
14
|
+
* Host ui pages on a temp server so we can have it on its own server ([#208](https://github.com/kapetacom/local-cluster-service/issues/208)) ([31ba4b8](https://github.com/kapetacom/local-cluster-service/commit/31ba4b8142c4618cc347037004f8fed87c69c6b7))
|
15
|
+
|
1
16
|
# [0.59.0](https://github.com/kapetacom/local-cluster-service/compare/v0.58.6...v0.59.0) (2024-08-01)
|
2
17
|
|
3
18
|
|
@@ -14,9 +14,8 @@ declare class ClusterService {
|
|
14
14
|
_findClusterServicePort(): Promise<void>;
|
15
15
|
/**
|
16
16
|
* Gets next available port
|
17
|
-
* @return {Promise<number>}
|
18
17
|
*/
|
19
|
-
getNextAvailablePort(): Promise<number>;
|
18
|
+
getNextAvailablePort(startPort?: number): Promise<number>;
|
20
19
|
_checkIfPortIsUsed(port: number, host?: string): Promise<unknown>;
|
21
20
|
/**
|
22
21
|
* The port of this local cluster service itself
|
@@ -48,14 +48,23 @@ class ClusterService {
|
|
48
48
|
}
|
49
49
|
/**
|
50
50
|
* Gets next available port
|
51
|
-
* @return {Promise<number>}
|
52
51
|
*/
|
53
|
-
async getNextAvailablePort() {
|
52
|
+
async getNextAvailablePort(startPort = -1) {
|
53
|
+
let receivedStartPort = startPort > 0;
|
54
|
+
if (!receivedStartPort) {
|
55
|
+
startPort = this._currentPort;
|
56
|
+
}
|
54
57
|
while (true) {
|
55
|
-
while (this._reservedPorts.indexOf(
|
56
|
-
|
58
|
+
while (this._reservedPorts.indexOf(startPort) > -1) {
|
59
|
+
startPort++;
|
60
|
+
if (!receivedStartPort) {
|
61
|
+
this._currentPort = startPort;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
const nextPort = startPort++;
|
65
|
+
if (!receivedStartPort) {
|
66
|
+
this._currentPort = startPort;
|
57
67
|
}
|
58
|
-
const nextPort = this._currentPort++;
|
59
68
|
const isUsed = await this._checkIfPortIsUsed(nextPort);
|
60
69
|
if (!isUsed) {
|
61
70
|
return nextPort;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { StormEventPage } from './events';
|
2
|
+
export declare class UIServer {
|
3
|
+
private readonly express;
|
4
|
+
private readonly systemId;
|
5
|
+
private port;
|
6
|
+
private server;
|
7
|
+
constructor(systemId: string);
|
8
|
+
start(): Promise<void>;
|
9
|
+
close(): void;
|
10
|
+
resolveUrl(screenData: StormEventPage): string;
|
11
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.UIServer = void 0;
|
7
|
+
/**
|
8
|
+
* Copyright 2023 Kapeta Inc.
|
9
|
+
* SPDX-License-Identifier: BUSL-1.1
|
10
|
+
*/
|
11
|
+
const express_1 = __importDefault(require("express"));
|
12
|
+
const page_utils_1 = require("./page-utils");
|
13
|
+
const clusterService_1 = require("../clusterService");
|
14
|
+
class UIServer {
|
15
|
+
express;
|
16
|
+
systemId;
|
17
|
+
port = 50000;
|
18
|
+
server;
|
19
|
+
constructor(systemId) {
|
20
|
+
this.systemId = systemId;
|
21
|
+
this.express = (0, express_1.default)();
|
22
|
+
}
|
23
|
+
async start() {
|
24
|
+
this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
|
25
|
+
this.express.all('/*', async (req, res) => {
|
26
|
+
(0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
|
27
|
+
});
|
28
|
+
return new Promise((resolve) => {
|
29
|
+
this.server = this.express.listen(this.port, () => {
|
30
|
+
console.log(`UI Server started on port ${this.port}`);
|
31
|
+
resolve();
|
32
|
+
});
|
33
|
+
});
|
34
|
+
}
|
35
|
+
close() {
|
36
|
+
if (this.server) {
|
37
|
+
console.log('UI Server closed on port: %s', this.port);
|
38
|
+
this.server.close();
|
39
|
+
this.server = undefined;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
resolveUrl(screenData) {
|
43
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
44
|
+
return `http://localhost:${this.port}${path}`;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
exports.UIServer = UIServer;
|
@@ -49,6 +49,7 @@ export declare class StormEventParser {
|
|
49
49
|
static toInstanceId(handle: string, blockName: string): string;
|
50
50
|
static toInstanceIdFromRef(ref: string): string;
|
51
51
|
static toSafeName(name: string): string;
|
52
|
+
static toSafeArtifactName(name: string): string;
|
52
53
|
static toRef(handle: string, name: string): KapetaURI;
|
53
54
|
private events;
|
54
55
|
private planName;
|
@@ -138,6 +138,13 @@ class StormEventParser {
|
|
138
138
|
static toSafeName(name) {
|
139
139
|
return name.toLowerCase().replace(/[^0-9a-z-]/gi, '');
|
140
140
|
}
|
141
|
+
static toSafeArtifactName(name) {
|
142
|
+
let safeName = name.toLowerCase().replace(/[^0-9a-z]/g, '');
|
143
|
+
if (/^[0-9]/.test(safeName)) {
|
144
|
+
safeName = 'a' + safeName; // Prepend a letter if the string starts with a number
|
145
|
+
}
|
146
|
+
return safeName;
|
147
|
+
}
|
141
148
|
static toRef(handle, name) {
|
142
149
|
return (0, nodejs_utils_1.parseKapetaUri)(handle + '/' + this.toSafeName(name) + ':local');
|
143
150
|
}
|
@@ -21,13 +21,18 @@ const assetManager_1 = require("../assetManager");
|
|
21
21
|
const node_uuid_1 = __importDefault(require("node-uuid"));
|
22
22
|
const PromiseQueue_1 = require("./PromiseQueue");
|
23
23
|
const page_utils_1 = require("./page-utils");
|
24
|
+
const UIServer_1 = require("./UIServer");
|
25
|
+
const UI_SERVERS = {};
|
24
26
|
const router = (0, express_promise_router_1.default)();
|
25
27
|
router.use('/', cors_1.corsHandler);
|
26
28
|
router.use('/', stringBody_1.stringBody);
|
27
29
|
function convertPageEvent(screenData, innerConversationId, mainConversationId) {
|
28
30
|
if (screenData.type === 'PAGE') {
|
31
|
+
const server = UI_SERVERS[mainConversationId];
|
32
|
+
if (!server) {
|
33
|
+
console.warn('No server found for conversation', mainConversationId);
|
34
|
+
}
|
29
35
|
screenData.payload.conversationId = innerConversationId;
|
30
|
-
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
31
36
|
return {
|
32
37
|
type: 'PAGE_URL',
|
33
38
|
reason: screenData.reason,
|
@@ -40,7 +45,7 @@ function convertPageEvent(screenData, innerConversationId, mainConversationId) {
|
|
40
45
|
description: screenData.payload.description,
|
41
46
|
prompt: screenData.payload.prompt,
|
42
47
|
path: screenData.payload.path,
|
43
|
-
url:
|
48
|
+
url: server ? server.resolveUrl(screenData) : '',
|
44
49
|
method: screenData.payload.method,
|
45
50
|
conversationId: innerConversationId,
|
46
51
|
},
|
@@ -88,6 +93,18 @@ router.post('/ui/screen', async (req, res) => {
|
|
88
93
|
}
|
89
94
|
}
|
90
95
|
});
|
96
|
+
router.delete('/:handle/ui', async (req, res) => {
|
97
|
+
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
98
|
+
if (!conversationId) {
|
99
|
+
res.status(400).send('Missing conversation id');
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
const server = UI_SERVERS[conversationId];
|
103
|
+
if (server) {
|
104
|
+
server.close();
|
105
|
+
delete UI_SERVERS[conversationId];
|
106
|
+
}
|
107
|
+
});
|
91
108
|
router.post('/:handle/ui', async (req, res) => {
|
92
109
|
const handle = req.params.handle;
|
93
110
|
try {
|
@@ -105,6 +122,9 @@ router.post('/:handle/ui', async (req, res) => {
|
|
105
122
|
onRequestAborted(req, res, () => {
|
106
123
|
queue.cancel();
|
107
124
|
});
|
125
|
+
const systemId = userJourneysStream.getConversationId();
|
126
|
+
UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
|
127
|
+
await UI_SERVERS[systemId].start();
|
108
128
|
userJourneysStream.on('data', async (data) => {
|
109
129
|
try {
|
110
130
|
console.log('Processing user journey event', data);
|
@@ -213,4 +213,8 @@ describe('event-parser', () => {
|
|
213
213
|
expect(postService?.content?.spec?.providers?.length).toBe(1);
|
214
214
|
expect(postService?.content?.spec?.providers[0].spec.source.value.startsWith('controller')).toBeTruthy();
|
215
215
|
});
|
216
|
+
it('toSafeName produces valid artifact names', async () => {
|
217
|
+
const safeName = event_parser_1.StormEventParser.toSafeArtifactName('Browser-based CRM Application');
|
218
|
+
expect(safeName).toBe('browserbasedcrmapplication');
|
219
|
+
});
|
216
220
|
});
|
@@ -14,9 +14,8 @@ declare class ClusterService {
|
|
14
14
|
_findClusterServicePort(): Promise<void>;
|
15
15
|
/**
|
16
16
|
* Gets next available port
|
17
|
-
* @return {Promise<number>}
|
18
17
|
*/
|
19
|
-
getNextAvailablePort(): Promise<number>;
|
18
|
+
getNextAvailablePort(startPort?: number): Promise<number>;
|
20
19
|
_checkIfPortIsUsed(port: number, host?: string): Promise<unknown>;
|
21
20
|
/**
|
22
21
|
* The port of this local cluster service itself
|
@@ -48,14 +48,23 @@ class ClusterService {
|
|
48
48
|
}
|
49
49
|
/**
|
50
50
|
* Gets next available port
|
51
|
-
* @return {Promise<number>}
|
52
51
|
*/
|
53
|
-
async getNextAvailablePort() {
|
52
|
+
async getNextAvailablePort(startPort = -1) {
|
53
|
+
let receivedStartPort = startPort > 0;
|
54
|
+
if (!receivedStartPort) {
|
55
|
+
startPort = this._currentPort;
|
56
|
+
}
|
54
57
|
while (true) {
|
55
|
-
while (this._reservedPorts.indexOf(
|
56
|
-
|
58
|
+
while (this._reservedPorts.indexOf(startPort) > -1) {
|
59
|
+
startPort++;
|
60
|
+
if (!receivedStartPort) {
|
61
|
+
this._currentPort = startPort;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
const nextPort = startPort++;
|
65
|
+
if (!receivedStartPort) {
|
66
|
+
this._currentPort = startPort;
|
57
67
|
}
|
58
|
-
const nextPort = this._currentPort++;
|
59
68
|
const isUsed = await this._checkIfPortIsUsed(nextPort);
|
60
69
|
if (!isUsed) {
|
61
70
|
return nextPort;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { StormEventPage } from './events';
|
2
|
+
export declare class UIServer {
|
3
|
+
private readonly express;
|
4
|
+
private readonly systemId;
|
5
|
+
private port;
|
6
|
+
private server;
|
7
|
+
constructor(systemId: string);
|
8
|
+
start(): Promise<void>;
|
9
|
+
close(): void;
|
10
|
+
resolveUrl(screenData: StormEventPage): string;
|
11
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.UIServer = void 0;
|
7
|
+
/**
|
8
|
+
* Copyright 2023 Kapeta Inc.
|
9
|
+
* SPDX-License-Identifier: BUSL-1.1
|
10
|
+
*/
|
11
|
+
const express_1 = __importDefault(require("express"));
|
12
|
+
const page_utils_1 = require("./page-utils");
|
13
|
+
const clusterService_1 = require("../clusterService");
|
14
|
+
class UIServer {
|
15
|
+
express;
|
16
|
+
systemId;
|
17
|
+
port = 50000;
|
18
|
+
server;
|
19
|
+
constructor(systemId) {
|
20
|
+
this.systemId = systemId;
|
21
|
+
this.express = (0, express_1.default)();
|
22
|
+
}
|
23
|
+
async start() {
|
24
|
+
this.port = await clusterService_1.clusterService.getNextAvailablePort(this.port);
|
25
|
+
this.express.all('/*', async (req, res) => {
|
26
|
+
(0, page_utils_1.readPageFromDisk)(this.systemId, req.params[0], req.method, res);
|
27
|
+
});
|
28
|
+
return new Promise((resolve) => {
|
29
|
+
this.server = this.express.listen(this.port, () => {
|
30
|
+
console.log(`UI Server started on port ${this.port}`);
|
31
|
+
resolve();
|
32
|
+
});
|
33
|
+
});
|
34
|
+
}
|
35
|
+
close() {
|
36
|
+
if (this.server) {
|
37
|
+
console.log('UI Server closed on port: %s', this.port);
|
38
|
+
this.server.close();
|
39
|
+
this.server = undefined;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
resolveUrl(screenData) {
|
43
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
44
|
+
return `http://localhost:${this.port}${path}`;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
exports.UIServer = UIServer;
|
@@ -49,6 +49,7 @@ export declare class StormEventParser {
|
|
49
49
|
static toInstanceId(handle: string, blockName: string): string;
|
50
50
|
static toInstanceIdFromRef(ref: string): string;
|
51
51
|
static toSafeName(name: string): string;
|
52
|
+
static toSafeArtifactName(name: string): string;
|
52
53
|
static toRef(handle: string, name: string): KapetaURI;
|
53
54
|
private events;
|
54
55
|
private planName;
|
@@ -138,6 +138,13 @@ class StormEventParser {
|
|
138
138
|
static toSafeName(name) {
|
139
139
|
return name.toLowerCase().replace(/[^0-9a-z-]/gi, '');
|
140
140
|
}
|
141
|
+
static toSafeArtifactName(name) {
|
142
|
+
let safeName = name.toLowerCase().replace(/[^0-9a-z]/g, '');
|
143
|
+
if (/^[0-9]/.test(safeName)) {
|
144
|
+
safeName = 'a' + safeName; // Prepend a letter if the string starts with a number
|
145
|
+
}
|
146
|
+
return safeName;
|
147
|
+
}
|
141
148
|
static toRef(handle, name) {
|
142
149
|
return (0, nodejs_utils_1.parseKapetaUri)(handle + '/' + this.toSafeName(name) + ':local');
|
143
150
|
}
|
@@ -21,13 +21,18 @@ const assetManager_1 = require("../assetManager");
|
|
21
21
|
const node_uuid_1 = __importDefault(require("node-uuid"));
|
22
22
|
const PromiseQueue_1 = require("./PromiseQueue");
|
23
23
|
const page_utils_1 = require("./page-utils");
|
24
|
+
const UIServer_1 = require("./UIServer");
|
25
|
+
const UI_SERVERS = {};
|
24
26
|
const router = (0, express_promise_router_1.default)();
|
25
27
|
router.use('/', cors_1.corsHandler);
|
26
28
|
router.use('/', stringBody_1.stringBody);
|
27
29
|
function convertPageEvent(screenData, innerConversationId, mainConversationId) {
|
28
30
|
if (screenData.type === 'PAGE') {
|
31
|
+
const server = UI_SERVERS[mainConversationId];
|
32
|
+
if (!server) {
|
33
|
+
console.warn('No server found for conversation', mainConversationId);
|
34
|
+
}
|
29
35
|
screenData.payload.conversationId = innerConversationId;
|
30
|
-
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
31
36
|
return {
|
32
37
|
type: 'PAGE_URL',
|
33
38
|
reason: screenData.reason,
|
@@ -40,7 +45,7 @@ function convertPageEvent(screenData, innerConversationId, mainConversationId) {
|
|
40
45
|
description: screenData.payload.description,
|
41
46
|
prompt: screenData.payload.prompt,
|
42
47
|
path: screenData.payload.path,
|
43
|
-
url:
|
48
|
+
url: server ? server.resolveUrl(screenData) : '',
|
44
49
|
method: screenData.payload.method,
|
45
50
|
conversationId: innerConversationId,
|
46
51
|
},
|
@@ -88,6 +93,18 @@ router.post('/ui/screen', async (req, res) => {
|
|
88
93
|
}
|
89
94
|
}
|
90
95
|
});
|
96
|
+
router.delete('/:handle/ui', async (req, res) => {
|
97
|
+
const conversationId = req.headers[stormClient_1.ConversationIdHeader.toLowerCase()];
|
98
|
+
if (!conversationId) {
|
99
|
+
res.status(400).send('Missing conversation id');
|
100
|
+
return;
|
101
|
+
}
|
102
|
+
const server = UI_SERVERS[conversationId];
|
103
|
+
if (server) {
|
104
|
+
server.close();
|
105
|
+
delete UI_SERVERS[conversationId];
|
106
|
+
}
|
107
|
+
});
|
91
108
|
router.post('/:handle/ui', async (req, res) => {
|
92
109
|
const handle = req.params.handle;
|
93
110
|
try {
|
@@ -105,6 +122,9 @@ router.post('/:handle/ui', async (req, res) => {
|
|
105
122
|
onRequestAborted(req, res, () => {
|
106
123
|
queue.cancel();
|
107
124
|
});
|
125
|
+
const systemId = userJourneysStream.getConversationId();
|
126
|
+
UI_SERVERS[systemId] = new UIServer_1.UIServer(systemId);
|
127
|
+
await UI_SERVERS[systemId].start();
|
108
128
|
userJourneysStream.on('data', async (data) => {
|
109
129
|
try {
|
110
130
|
console.log('Processing user journey event', data);
|
@@ -213,4 +213,8 @@ describe('event-parser', () => {
|
|
213
213
|
expect(postService?.content?.spec?.providers?.length).toBe(1);
|
214
214
|
expect(postService?.content?.spec?.providers[0].spec.source.value.startsWith('controller')).toBeTruthy();
|
215
215
|
});
|
216
|
+
it('toSafeName produces valid artifact names', async () => {
|
217
|
+
const safeName = event_parser_1.StormEventParser.toSafeArtifactName('Browser-based CRM Application');
|
218
|
+
expect(safeName).toBe('browserbasedcrmapplication');
|
219
|
+
});
|
216
220
|
});
|
package/package.json
CHANGED
package/src/clusterService.ts
CHANGED
@@ -56,15 +56,24 @@ class ClusterService {
|
|
56
56
|
|
57
57
|
/**
|
58
58
|
* Gets next available port
|
59
|
-
* @return {Promise<number>}
|
60
59
|
*/
|
61
|
-
async getNextAvailablePort() {
|
60
|
+
public async getNextAvailablePort(startPort: number = -1) {
|
61
|
+
let receivedStartPort = startPort > 0;
|
62
|
+
if (!receivedStartPort) {
|
63
|
+
startPort = this._currentPort;
|
64
|
+
}
|
62
65
|
while (true) {
|
63
|
-
while (this._reservedPorts.indexOf(
|
64
|
-
|
66
|
+
while (this._reservedPorts.indexOf(startPort) > -1) {
|
67
|
+
startPort++;
|
68
|
+
if (!receivedStartPort) {
|
69
|
+
this._currentPort = startPort;
|
70
|
+
}
|
65
71
|
}
|
66
72
|
|
67
|
-
const nextPort =
|
73
|
+
const nextPort = startPort++;
|
74
|
+
if (!receivedStartPort) {
|
75
|
+
this._currentPort = startPort;
|
76
|
+
}
|
68
77
|
const isUsed = await this._checkIfPortIsUsed(nextPort);
|
69
78
|
if (!isUsed) {
|
70
79
|
return nextPort;
|
@@ -0,0 +1,50 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright 2023 Kapeta Inc.
|
3
|
+
* SPDX-License-Identifier: BUSL-1.1
|
4
|
+
*/
|
5
|
+
import express, { Express, Request, Response } from 'express';
|
6
|
+
import { readPageFromDisk } from './page-utils';
|
7
|
+
import { clusterService } from '../clusterService';
|
8
|
+
import { Server } from 'http';
|
9
|
+
import { StormEventPage } from './events';
|
10
|
+
|
11
|
+
export class UIServer {
|
12
|
+
private readonly express: Express;
|
13
|
+
private readonly systemId: string;
|
14
|
+
|
15
|
+
private port: number = 50000;
|
16
|
+
private server: Server | undefined;
|
17
|
+
|
18
|
+
constructor(systemId: string) {
|
19
|
+
this.systemId = systemId;
|
20
|
+
this.express = express();
|
21
|
+
}
|
22
|
+
|
23
|
+
public async start() {
|
24
|
+
this.port = await clusterService.getNextAvailablePort(this.port);
|
25
|
+
|
26
|
+
this.express.all('/*', async (req: Request, res: Response) => {
|
27
|
+
readPageFromDisk(this.systemId, req.params[0], req.method, res);
|
28
|
+
});
|
29
|
+
|
30
|
+
return new Promise<void>((resolve) => {
|
31
|
+
this.server = this.express.listen(this.port, () => {
|
32
|
+
console.log(`UI Server started on port ${this.port}`);
|
33
|
+
resolve();
|
34
|
+
});
|
35
|
+
});
|
36
|
+
}
|
37
|
+
|
38
|
+
public close() {
|
39
|
+
if (this.server) {
|
40
|
+
console.log('UI Server closed on port: %s', this.port);
|
41
|
+
this.server.close();
|
42
|
+
this.server = undefined;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
resolveUrl(screenData: StormEventPage) {
|
47
|
+
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
48
|
+
return `http://localhost:${this.port}${path}`;
|
49
|
+
}
|
50
|
+
}
|
@@ -246,6 +246,14 @@ export class StormEventParser {
|
|
246
246
|
return name.toLowerCase().replace(/[^0-9a-z-]/gi, '');
|
247
247
|
}
|
248
248
|
|
249
|
+
public static toSafeArtifactName(name: string): string {
|
250
|
+
let safeName = name.toLowerCase().replace(/[^0-9a-z]/g, '');
|
251
|
+
if (/^[0-9]/.test(safeName)) {
|
252
|
+
safeName = 'a' + safeName; // Prepend a letter if the string starts with a number
|
253
|
+
}
|
254
|
+
return safeName;
|
255
|
+
}
|
256
|
+
|
249
257
|
public static toRef(handle: string, name: string) {
|
250
258
|
return parseKapetaUri(handle + '/' + this.toSafeName(name) + ':local');
|
251
259
|
}
|
package/src/storm/routes.ts
CHANGED
@@ -26,7 +26,9 @@ import { assetManager } from '../assetManager';
|
|
26
26
|
import uuid from 'node-uuid';
|
27
27
|
import { PromiseQueue } from './PromiseQueue';
|
28
28
|
import { readPageFromDisk, readPageFromDiskAsString, SystemIdHeader, writePageToDisk } from './page-utils';
|
29
|
+
import { UIServer } from './UIServer';
|
29
30
|
|
31
|
+
const UI_SERVERS: { [key: string]: UIServer } = {};
|
30
32
|
const router = Router();
|
31
33
|
|
32
34
|
router.use('/', corsHandler);
|
@@ -34,8 +36,11 @@ router.use('/', stringBody);
|
|
34
36
|
|
35
37
|
function convertPageEvent(screenData: StormEvent, innerConversationId: string, mainConversationId: string): StormEvent {
|
36
38
|
if (screenData.type === 'PAGE') {
|
39
|
+
const server: UIServer | undefined = UI_SERVERS[mainConversationId];
|
40
|
+
if (!server) {
|
41
|
+
console.warn('No server found for conversation', mainConversationId);
|
42
|
+
}
|
37
43
|
screenData.payload.conversationId = innerConversationId;
|
38
|
-
const path = screenData.payload.path.startsWith('/') ? screenData.payload.path : `/${screenData.payload.path}`;
|
39
44
|
return {
|
40
45
|
type: 'PAGE_URL',
|
41
46
|
reason: screenData.reason,
|
@@ -48,7 +53,7 @@ function convertPageEvent(screenData: StormEvent, innerConversationId: string, m
|
|
48
53
|
description: screenData.payload.description,
|
49
54
|
prompt: screenData.payload.prompt,
|
50
55
|
path: screenData.payload.path,
|
51
|
-
url:
|
56
|
+
url: server ? server.resolveUrl(screenData) : '',
|
52
57
|
method: screenData.payload.method,
|
53
58
|
conversationId: innerConversationId,
|
54
59
|
},
|
@@ -106,6 +111,20 @@ router.post('/ui/screen', async (req: KapetaBodyRequest, res: Response) => {
|
|
106
111
|
}
|
107
112
|
});
|
108
113
|
|
114
|
+
router.delete('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
115
|
+
const conversationId = req.headers[ConversationIdHeader.toLowerCase()] as string | undefined;
|
116
|
+
if (!conversationId) {
|
117
|
+
res.status(400).send('Missing conversation id');
|
118
|
+
return;
|
119
|
+
}
|
120
|
+
|
121
|
+
const server = UI_SERVERS[conversationId];
|
122
|
+
if (server) {
|
123
|
+
server.close();
|
124
|
+
delete UI_SERVERS[conversationId];
|
125
|
+
}
|
126
|
+
});
|
127
|
+
|
109
128
|
router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
110
129
|
const handle = req.params.handle as string;
|
111
130
|
try {
|
@@ -130,6 +149,11 @@ router.post('/:handle/ui', async (req: KapetaBodyRequest, res: Response) => {
|
|
130
149
|
queue.cancel();
|
131
150
|
});
|
132
151
|
|
152
|
+
const systemId = userJourneysStream.getConversationId();
|
153
|
+
|
154
|
+
UI_SERVERS[systemId] = new UIServer(systemId);
|
155
|
+
await UI_SERVERS[systemId].start();
|
156
|
+
|
133
157
|
userJourneysStream.on('data', async (data: StormEvent) => {
|
134
158
|
try {
|
135
159
|
console.log('Processing user journey event', data);
|
@@ -240,4 +240,9 @@ describe('event-parser', () => {
|
|
240
240
|
expect(postService?.content?.spec?.providers?.length).toBe(1);
|
241
241
|
expect(postService?.content?.spec?.providers![0].spec.source.value.startsWith('controller')).toBeTruthy();
|
242
242
|
});
|
243
|
+
|
244
|
+
it('toSafeName produces valid artifact names', async () => {
|
245
|
+
const safeName = StormEventParser.toSafeArtifactName('Browser-based CRM Application');
|
246
|
+
expect(safeName).toBe('browserbasedcrmapplication');
|
247
|
+
});
|
243
248
|
});
|