@machhub-dev/sdk-ts 0.0.7 → 0.0.9
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/cjs/classes/collection.js +12 -2
- package/dist/cjs/sdk-ts.js +11 -11
- package/dist/cjs/services/http.service.d.ts +5 -4
- package/dist/cjs/services/http.service.js +16 -23
- package/dist/cjs/services/mqtt.service.js +4 -4
- package/dist/cjs/services/nats.service.d.ts +2 -2
- package/dist/cjs/services/nats.service.js +45 -17
- package/dist/classes/collection.js +12 -2
- package/dist/sdk-ts.js +11 -11
- package/dist/services/http.service.d.ts +5 -4
- package/dist/services/http.service.js +16 -23
- package/dist/services/mqtt.service.js +4 -4
- package/dist/services/nats.service.d.ts +2 -2
- package/dist/services/nats.service.js +45 -17
- package/package.json +5 -5
- package/src/classes/collection.ts +14 -2
- package/src/sdk-ts.ts +12 -12
- package/src/services/http.service.ts +19 -26
- package/src/services/mqtt.service.ts +4 -4
- package/src/services/nats.service.ts +49 -18
|
@@ -55,10 +55,20 @@ class Collection {
|
|
|
55
55
|
}
|
|
56
56
|
async create(data) {
|
|
57
57
|
try {
|
|
58
|
-
|
|
58
|
+
const formData = new FormData();
|
|
59
|
+
for (const [key, value] of Object.entries(data)) {
|
|
60
|
+
if (value instanceof File) {
|
|
61
|
+
formData.append(key, value, value.name);
|
|
62
|
+
data[key] = value.name;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
formData.append("record", JSON.stringify(data));
|
|
66
|
+
return await this.httpService.request
|
|
67
|
+
.withBody(formData)
|
|
68
|
+
.post(this.collectionName);
|
|
59
69
|
}
|
|
60
70
|
catch (error) {
|
|
61
|
-
throw new CollectionError(
|
|
71
|
+
throw new CollectionError("create", this.collectionName, error);
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
74
|
async update(id, data) {
|
package/dist/cjs/sdk-ts.js
CHANGED
|
@@ -18,8 +18,8 @@ class HTTPClient {
|
|
|
18
18
|
* @param applicationID The ID for your application (required)
|
|
19
19
|
* @param httpUrl The base URL for HTTP connection (default = http://localhost:6188)
|
|
20
20
|
*/
|
|
21
|
-
constructor(applicationID, httpUrl, developerKey) {
|
|
22
|
-
this.httpService = new http_service_js_1.HTTPService(httpUrl, MACHHUB_SDK_PATH, applicationID, developerKey);
|
|
21
|
+
constructor(applicationID, httpUrl, developerKey, runtimeID) {
|
|
22
|
+
this.httpService = new http_service_js_1.HTTPService(httpUrl, MACHHUB_SDK_PATH, applicationID, developerKey, runtimeID);
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Gets server info
|
|
@@ -147,20 +147,20 @@ class SDK {
|
|
|
147
147
|
if (!config.application_id)
|
|
148
148
|
config = { application_id: "" };
|
|
149
149
|
// console.log("Using application_id:", config.application_id);
|
|
150
|
-
const
|
|
151
|
-
console.log("Port:",
|
|
150
|
+
const { port, runtimeID } = await getEnvConfig();
|
|
151
|
+
console.log("Port:", port);
|
|
152
152
|
if (!config.httpUrl) {
|
|
153
|
-
config.httpUrl = "http://localhost:" +
|
|
153
|
+
config.httpUrl = "http://localhost:" + port;
|
|
154
154
|
}
|
|
155
155
|
if (!config.mqttUrl) {
|
|
156
|
-
config.mqttUrl = "ws://localhost:" +
|
|
156
|
+
config.mqttUrl = "ws://localhost:" + port + "/mqtt";
|
|
157
157
|
}
|
|
158
158
|
if (!config.natsUrl) {
|
|
159
|
-
config.natsUrl = "ws://localhost:" +
|
|
159
|
+
config.natsUrl = "ws://localhost:" + port + "/nats";
|
|
160
160
|
}
|
|
161
161
|
const { application_id, httpUrl, mqttUrl, natsUrl } = config;
|
|
162
162
|
console.log("SDK Config:", { application_id, httpUrl, mqttUrl, natsUrl, developer_key: config.developer_key?.split('').map((_, i) => i < config.developer_key.length - 4 ? '*' : config.developer_key[i]).join('') });
|
|
163
|
-
this.http = new HTTPClient(application_id, httpUrl, config.developer_key);
|
|
163
|
+
this.http = new HTTPClient(application_id, httpUrl, config.developer_key, runtimeID);
|
|
164
164
|
this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
|
|
165
165
|
this.nats = await NATSClient.getInstance(application_id, natsUrl);
|
|
166
166
|
this._historian = new historian_js_1.Historian(this.http["httpService"], this.mqtt["mqttService"]);
|
|
@@ -234,7 +234,7 @@ class SDK {
|
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
exports.SDK = SDK;
|
|
237
|
-
async function
|
|
237
|
+
async function getEnvConfig() {
|
|
238
238
|
try {
|
|
239
239
|
// Try to find the configuration endpoint by testing different base paths
|
|
240
240
|
const configUrl = await findConfigEndpoint();
|
|
@@ -242,12 +242,12 @@ async function getEnvPort() {
|
|
|
242
242
|
// console.log('Response:', response);
|
|
243
243
|
// console.log('runtimeID: ', response.runtimeID);
|
|
244
244
|
// console.log('applicationID: ', response.runtimeID.split('XmchX')[0]);
|
|
245
|
-
return response.port;
|
|
245
|
+
return { port: response.port, runtimeID: response.runtimeID };
|
|
246
246
|
}
|
|
247
247
|
catch (error) {
|
|
248
248
|
// console.log('No configured runtime ID:', error);
|
|
249
249
|
// TODO: Use DevPort from SDK Config or default to 61888
|
|
250
|
-
return "61888";
|
|
250
|
+
return { port: "61888", runtimeID: "" };
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
/**
|
|
@@ -9,19 +9,19 @@ export declare class HTTPService {
|
|
|
9
9
|
private url;
|
|
10
10
|
private applicationID;
|
|
11
11
|
private developerKey?;
|
|
12
|
-
|
|
12
|
+
private runtimeID?;
|
|
13
|
+
constructor(url: string, prefix: string, applicationID: string, developerKey?: string, runtimeID?: string);
|
|
13
14
|
get request(): RequestParameters;
|
|
14
|
-
private addRuntimeHeaders;
|
|
15
|
-
private getCookie;
|
|
16
15
|
}
|
|
17
16
|
declare class RequestParameters {
|
|
18
17
|
private base;
|
|
19
18
|
private applicationID;
|
|
20
19
|
private developerKey?;
|
|
20
|
+
private runtimeID?;
|
|
21
21
|
query?: Record<string, string>;
|
|
22
22
|
init?: RequestInit;
|
|
23
23
|
headers?: Record<string, string>;
|
|
24
|
-
constructor(base: URL, applicationID: string, developerKey?: string, query?: Record<string, string>);
|
|
24
|
+
constructor(base: URL, applicationID: string, developerKey?: string, runtimeID?: string, query?: Record<string, string>);
|
|
25
25
|
private withQuery;
|
|
26
26
|
private parseInit;
|
|
27
27
|
withBody(body: BodyInit): RequestParameters;
|
|
@@ -33,6 +33,7 @@ declare class RequestParameters {
|
|
|
33
33
|
setHeader(key: string, value: string): RequestParameters;
|
|
34
34
|
setBearerToken(token: string): RequestParameters;
|
|
35
35
|
withAccessToken(): RequestParameters;
|
|
36
|
+
withRuntimeID(): RequestParameters;
|
|
36
37
|
withDomain(): RequestParameters;
|
|
37
38
|
withDeveloperKey(): RequestParameters;
|
|
38
39
|
withContentType(mime: string): RequestParameters;
|
|
@@ -14,47 +14,30 @@ class HTTPException extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
exports.HTTPException = HTTPException;
|
|
16
16
|
class HTTPService {
|
|
17
|
-
constructor(url, prefix, applicationID, developerKey) {
|
|
17
|
+
constructor(url, prefix, applicationID, developerKey, runtimeID) {
|
|
18
18
|
if (prefix == null)
|
|
19
19
|
prefix = "";
|
|
20
20
|
this.url = new URL(prefix, url);
|
|
21
21
|
this.applicationID = "domains:" + applicationID;
|
|
22
22
|
this.developerKey = developerKey;
|
|
23
|
+
this.runtimeID = runtimeID;
|
|
23
24
|
}
|
|
24
25
|
get request() {
|
|
25
|
-
return new RequestParameters(this.url, this.applicationID, this.developerKey);
|
|
26
|
-
}
|
|
27
|
-
addRuntimeHeaders(headers = {}) {
|
|
28
|
-
// Add runtime ID from cookie if available (for hosted applications)
|
|
29
|
-
if (typeof document !== 'undefined') {
|
|
30
|
-
const runtimeID = this.getCookie('machhub_runtime_id');
|
|
31
|
-
if (runtimeID) {
|
|
32
|
-
headers['X-MachHub-Runtime-ID'] = runtimeID;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return headers;
|
|
36
|
-
}
|
|
37
|
-
getCookie(name) {
|
|
38
|
-
if (typeof document === 'undefined')
|
|
39
|
-
return null;
|
|
40
|
-
const value = `; ${document.cookie}`;
|
|
41
|
-
const parts = value.split(`; ${name}=`);
|
|
42
|
-
if (parts.length === 2) {
|
|
43
|
-
return parts.pop()?.split(';').shift() || null;
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
26
|
+
return new RequestParameters(this.url, this.applicationID, this.developerKey, this.runtimeID);
|
|
46
27
|
}
|
|
47
28
|
}
|
|
48
29
|
exports.HTTPService = HTTPService;
|
|
49
30
|
class RequestParameters {
|
|
50
|
-
constructor(base, applicationID, developerKey, query) {
|
|
31
|
+
constructor(base, applicationID, developerKey, runtimeID, query) {
|
|
51
32
|
this.base = base;
|
|
52
33
|
this.applicationID = applicationID;
|
|
53
34
|
this.developerKey = developerKey;
|
|
35
|
+
this.runtimeID = runtimeID;
|
|
54
36
|
this.query = query;
|
|
55
37
|
this.withDomain(); // Ensure withDomain() is called by default
|
|
56
38
|
this.withAccessToken(); // Ensure withAccessToken() is called by default
|
|
57
39
|
this.withDeveloperKey(); // Ensure withDeveloperKey() is called by default
|
|
40
|
+
this.withRuntimeID(); // Ensure withRuntimeID() is called by default
|
|
58
41
|
}
|
|
59
42
|
withQuery(path, query) {
|
|
60
43
|
const newPath = [this.base.pathname, path].map(pathPart => pathPart.replace(/(^\/|\/$)/g, "")).join("/");
|
|
@@ -138,6 +121,10 @@ class RequestParameters {
|
|
|
138
121
|
this.setHeader("Authorization", `Bearer ${tkn}`);
|
|
139
122
|
return this;
|
|
140
123
|
}
|
|
124
|
+
withRuntimeID() {
|
|
125
|
+
this.setHeader("X-Machhub-Runtime-Id", this.runtimeID ?? "");
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
141
128
|
withDomain() {
|
|
142
129
|
this.setHeader("Domain", this.applicationID);
|
|
143
130
|
return this;
|
|
@@ -168,6 +155,12 @@ class RequestParameters {
|
|
|
168
155
|
if (body) {
|
|
169
156
|
if (body instanceof FormData) {
|
|
170
157
|
init.body = body;
|
|
158
|
+
// Remove Content-Type header if it exists, let browser set it for FormData
|
|
159
|
+
if (init.headers && typeof init.headers === 'object') {
|
|
160
|
+
const headers = init.headers;
|
|
161
|
+
delete headers['Content-Type'];
|
|
162
|
+
delete headers['content-type'];
|
|
163
|
+
}
|
|
171
164
|
}
|
|
172
165
|
else {
|
|
173
166
|
init.body = JSON.stringify(body);
|
|
@@ -90,20 +90,20 @@ class MQTTService {
|
|
|
90
90
|
this.client.on('message', (topic, message) => {
|
|
91
91
|
for (const subscribedTopic of this.subscribedTopics) {
|
|
92
92
|
if (topic === subscribedTopic.topic) {
|
|
93
|
-
const parsedMessage = this.parseMessage(message);
|
|
93
|
+
const parsedMessage = this.parseMessage(message, topic);
|
|
94
94
|
subscribedTopic.handler(parsedMessage);
|
|
95
95
|
break;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
-
parseMessage(message) {
|
|
100
|
+
parseMessage(message, topic) {
|
|
101
101
|
try {
|
|
102
102
|
return JSON.parse(message.toString());
|
|
103
103
|
}
|
|
104
104
|
catch (error) {
|
|
105
|
-
console.
|
|
106
|
-
return
|
|
105
|
+
console.warn("Failed to parse message as JSON '" + topic + "' : ", message.toString());
|
|
106
|
+
return message.toString();
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
}
|
|
@@ -66,9 +66,9 @@ export declare class NATSService {
|
|
|
66
66
|
*/
|
|
67
67
|
printFunctions(): void;
|
|
68
68
|
/**
|
|
69
|
-
* Parses a message buffer into a JSON object.
|
|
69
|
+
* Parses a message buffer into a JSON object or returns raw data.
|
|
70
70
|
* @param message {Uint8Array} The message buffer.
|
|
71
|
-
* @returns {unknown} The parsed message.
|
|
71
|
+
* @returns {unknown} The parsed message (JSON object) or the raw string if parsing fails.
|
|
72
72
|
*/
|
|
73
73
|
private parseMessage;
|
|
74
74
|
/**
|
|
@@ -107,20 +107,31 @@ class NATSService {
|
|
|
107
107
|
console.error("Error handling message:", err);
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
msg.
|
|
114
|
-
|
|
110
|
+
try {
|
|
111
|
+
const dataStr = Buffer.from(msg.data).toString('utf-8');
|
|
112
|
+
const data = JSON.parse(dataStr);
|
|
113
|
+
const subjectParts = msg.subject.split(".");
|
|
114
|
+
if (subjectParts.length !== 4) {
|
|
115
|
+
msg.respond(JSON.stringify({ error: "Invalid subject format" }));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const functionName = subjectParts[3];
|
|
119
|
+
this.executeFunction(functionName, data)
|
|
120
|
+
.then((result) => {
|
|
121
|
+
msg.respond(JSON.stringify(result));
|
|
122
|
+
})
|
|
123
|
+
.catch((e) => {
|
|
124
|
+
console.log("Error executing function '" + functionName + "': ", e);
|
|
125
|
+
msg.respond(JSON.stringify({ status: "failed", error: e.message }));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (parseError) {
|
|
129
|
+
console.error("Error parsing function execution message:", parseError);
|
|
130
|
+
msg.respond(JSON.stringify({
|
|
131
|
+
status: "failed",
|
|
132
|
+
error: `Failed to parse message: ${parseError.message}`
|
|
133
|
+
}));
|
|
115
134
|
}
|
|
116
|
-
const functionName = subjectParts[3];
|
|
117
|
-
this.executeFunction(functionName, data)
|
|
118
|
-
.then((result) => {
|
|
119
|
-
msg.respond(JSON.stringify(result));
|
|
120
|
-
})
|
|
121
|
-
.catch((e) => {
|
|
122
|
-
msg.respond(JSON.stringify({ status: "failed", error: e.message }));
|
|
123
|
-
});
|
|
124
135
|
},
|
|
125
136
|
});
|
|
126
137
|
if (sub)
|
|
@@ -195,17 +206,34 @@ class NATSService {
|
|
|
195
206
|
NATSService.log("Available functions: ", Object.keys(this.functions).join(", "));
|
|
196
207
|
}
|
|
197
208
|
/**
|
|
198
|
-
* Parses a message buffer into a JSON object.
|
|
209
|
+
* Parses a message buffer into a JSON object or returns raw data.
|
|
199
210
|
* @param message {Uint8Array} The message buffer.
|
|
200
|
-
* @returns {unknown} The parsed message.
|
|
211
|
+
* @returns {unknown} The parsed message (JSON object) or the raw string if parsing fails.
|
|
201
212
|
*/
|
|
202
213
|
parseMessage(message) {
|
|
203
214
|
try {
|
|
204
|
-
|
|
215
|
+
const messageStr = Buffer.from(message).toString('utf-8');
|
|
216
|
+
// Check if the message is empty
|
|
217
|
+
if (!messageStr || messageStr.trim().length === 0) {
|
|
218
|
+
NATSService.log("Received empty message");
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
// Try to parse as JSON
|
|
222
|
+
return JSON.parse(messageStr);
|
|
205
223
|
}
|
|
206
224
|
catch (error) {
|
|
225
|
+
// If JSON parsing fails, check if it's binary data or non-JSON content
|
|
226
|
+
const messageStr = Buffer.from(message).toString('utf-8');
|
|
227
|
+
// Check if it looks like binary data (contains non-printable characters)
|
|
228
|
+
const isBinary = message.some(byte => byte < 32 && byte !== 9 && byte !== 10 && byte !== 13);
|
|
229
|
+
if (isBinary) {
|
|
230
|
+
NATSService.log("Received binary data, returning as Uint8Array");
|
|
231
|
+
return message; // Return raw binary data
|
|
232
|
+
}
|
|
233
|
+
// If it's a plain string (not JSON), return as is
|
|
234
|
+
NATSService.log("Failed to parse message as JSON, returning as string:", messageStr.substring(0, 100));
|
|
207
235
|
console.error("Error parsing message:", error);
|
|
208
|
-
return
|
|
236
|
+
return messageStr;
|
|
209
237
|
}
|
|
210
238
|
}
|
|
211
239
|
/**
|
|
@@ -51,10 +51,20 @@ export class Collection {
|
|
|
51
51
|
}
|
|
52
52
|
async create(data) {
|
|
53
53
|
try {
|
|
54
|
-
|
|
54
|
+
const formData = new FormData();
|
|
55
|
+
for (const [key, value] of Object.entries(data)) {
|
|
56
|
+
if (value instanceof File) {
|
|
57
|
+
formData.append(key, value, value.name);
|
|
58
|
+
data[key] = value.name;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
formData.append("record", JSON.stringify(data));
|
|
62
|
+
return await this.httpService.request
|
|
63
|
+
.withBody(formData)
|
|
64
|
+
.post(this.collectionName);
|
|
55
65
|
}
|
|
56
66
|
catch (error) {
|
|
57
|
-
throw new CollectionError(
|
|
67
|
+
throw new CollectionError("create", this.collectionName, error);
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
70
|
async update(id, data) {
|
package/dist/sdk-ts.js
CHANGED
|
@@ -15,8 +15,8 @@ class HTTPClient {
|
|
|
15
15
|
* @param applicationID The ID for your application (required)
|
|
16
16
|
* @param httpUrl The base URL for HTTP connection (default = http://localhost:6188)
|
|
17
17
|
*/
|
|
18
|
-
constructor(applicationID, httpUrl, developerKey) {
|
|
19
|
-
this.httpService = new HTTPService(httpUrl, MACHHUB_SDK_PATH, applicationID, developerKey);
|
|
18
|
+
constructor(applicationID, httpUrl, developerKey, runtimeID) {
|
|
19
|
+
this.httpService = new HTTPService(httpUrl, MACHHUB_SDK_PATH, applicationID, developerKey, runtimeID);
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Gets server info
|
|
@@ -144,20 +144,20 @@ export class SDK {
|
|
|
144
144
|
if (!config.application_id)
|
|
145
145
|
config = { application_id: "" };
|
|
146
146
|
// console.log("Using application_id:", config.application_id);
|
|
147
|
-
const
|
|
148
|
-
console.log("Port:",
|
|
147
|
+
const { port, runtimeID } = await getEnvConfig();
|
|
148
|
+
console.log("Port:", port);
|
|
149
149
|
if (!config.httpUrl) {
|
|
150
|
-
config.httpUrl = "http://localhost:" +
|
|
150
|
+
config.httpUrl = "http://localhost:" + port;
|
|
151
151
|
}
|
|
152
152
|
if (!config.mqttUrl) {
|
|
153
|
-
config.mqttUrl = "ws://localhost:" +
|
|
153
|
+
config.mqttUrl = "ws://localhost:" + port + "/mqtt";
|
|
154
154
|
}
|
|
155
155
|
if (!config.natsUrl) {
|
|
156
|
-
config.natsUrl = "ws://localhost:" +
|
|
156
|
+
config.natsUrl = "ws://localhost:" + port + "/nats";
|
|
157
157
|
}
|
|
158
158
|
const { application_id, httpUrl, mqttUrl, natsUrl } = config;
|
|
159
159
|
console.log("SDK Config:", { application_id, httpUrl, mqttUrl, natsUrl, developer_key: config.developer_key?.split('').map((_, i) => i < config.developer_key.length - 4 ? '*' : config.developer_key[i]).join('') });
|
|
160
|
-
this.http = new HTTPClient(application_id, httpUrl, config.developer_key);
|
|
160
|
+
this.http = new HTTPClient(application_id, httpUrl, config.developer_key, runtimeID);
|
|
161
161
|
this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
|
|
162
162
|
this.nats = await NATSClient.getInstance(application_id, natsUrl);
|
|
163
163
|
this._historian = new Historian(this.http["httpService"], this.mqtt["mqttService"]);
|
|
@@ -230,7 +230,7 @@ export class SDK {
|
|
|
230
230
|
return new Collection(this.http["httpService"], this.mqtt ? this.mqtt["mqttService"] : null, collectionName);
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
-
async function
|
|
233
|
+
async function getEnvConfig() {
|
|
234
234
|
try {
|
|
235
235
|
// Try to find the configuration endpoint by testing different base paths
|
|
236
236
|
const configUrl = await findConfigEndpoint();
|
|
@@ -238,12 +238,12 @@ async function getEnvPort() {
|
|
|
238
238
|
// console.log('Response:', response);
|
|
239
239
|
// console.log('runtimeID: ', response.runtimeID);
|
|
240
240
|
// console.log('applicationID: ', response.runtimeID.split('XmchX')[0]);
|
|
241
|
-
return response.port;
|
|
241
|
+
return { port: response.port, runtimeID: response.runtimeID };
|
|
242
242
|
}
|
|
243
243
|
catch (error) {
|
|
244
244
|
// console.log('No configured runtime ID:', error);
|
|
245
245
|
// TODO: Use DevPort from SDK Config or default to 61888
|
|
246
|
-
return "61888";
|
|
246
|
+
return { port: "61888", runtimeID: "" };
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
/**
|
|
@@ -9,19 +9,19 @@ export declare class HTTPService {
|
|
|
9
9
|
private url;
|
|
10
10
|
private applicationID;
|
|
11
11
|
private developerKey?;
|
|
12
|
-
|
|
12
|
+
private runtimeID?;
|
|
13
|
+
constructor(url: string, prefix: string, applicationID: string, developerKey?: string, runtimeID?: string);
|
|
13
14
|
get request(): RequestParameters;
|
|
14
|
-
private addRuntimeHeaders;
|
|
15
|
-
private getCookie;
|
|
16
15
|
}
|
|
17
16
|
declare class RequestParameters {
|
|
18
17
|
private base;
|
|
19
18
|
private applicationID;
|
|
20
19
|
private developerKey?;
|
|
20
|
+
private runtimeID?;
|
|
21
21
|
query?: Record<string, string>;
|
|
22
22
|
init?: RequestInit;
|
|
23
23
|
headers?: Record<string, string>;
|
|
24
|
-
constructor(base: URL, applicationID: string, developerKey?: string, query?: Record<string, string>);
|
|
24
|
+
constructor(base: URL, applicationID: string, developerKey?: string, runtimeID?: string, query?: Record<string, string>);
|
|
25
25
|
private withQuery;
|
|
26
26
|
private parseInit;
|
|
27
27
|
withBody(body: BodyInit): RequestParameters;
|
|
@@ -33,6 +33,7 @@ declare class RequestParameters {
|
|
|
33
33
|
setHeader(key: string, value: string): RequestParameters;
|
|
34
34
|
setBearerToken(token: string): RequestParameters;
|
|
35
35
|
withAccessToken(): RequestParameters;
|
|
36
|
+
withRuntimeID(): RequestParameters;
|
|
36
37
|
withDomain(): RequestParameters;
|
|
37
38
|
withDeveloperKey(): RequestParameters;
|
|
38
39
|
withContentType(mime: string): RequestParameters;
|
|
@@ -10,46 +10,29 @@ export class HTTPException extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
export class HTTPService {
|
|
13
|
-
constructor(url, prefix, applicationID, developerKey) {
|
|
13
|
+
constructor(url, prefix, applicationID, developerKey, runtimeID) {
|
|
14
14
|
if (prefix == null)
|
|
15
15
|
prefix = "";
|
|
16
16
|
this.url = new URL(prefix, url);
|
|
17
17
|
this.applicationID = "domains:" + applicationID;
|
|
18
18
|
this.developerKey = developerKey;
|
|
19
|
+
this.runtimeID = runtimeID;
|
|
19
20
|
}
|
|
20
21
|
get request() {
|
|
21
|
-
return new RequestParameters(this.url, this.applicationID, this.developerKey);
|
|
22
|
-
}
|
|
23
|
-
addRuntimeHeaders(headers = {}) {
|
|
24
|
-
// Add runtime ID from cookie if available (for hosted applications)
|
|
25
|
-
if (typeof document !== 'undefined') {
|
|
26
|
-
const runtimeID = this.getCookie('machhub_runtime_id');
|
|
27
|
-
if (runtimeID) {
|
|
28
|
-
headers['X-MachHub-Runtime-ID'] = runtimeID;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return headers;
|
|
32
|
-
}
|
|
33
|
-
getCookie(name) {
|
|
34
|
-
if (typeof document === 'undefined')
|
|
35
|
-
return null;
|
|
36
|
-
const value = `; ${document.cookie}`;
|
|
37
|
-
const parts = value.split(`; ${name}=`);
|
|
38
|
-
if (parts.length === 2) {
|
|
39
|
-
return parts.pop()?.split(';').shift() || null;
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
22
|
+
return new RequestParameters(this.url, this.applicationID, this.developerKey, this.runtimeID);
|
|
42
23
|
}
|
|
43
24
|
}
|
|
44
25
|
class RequestParameters {
|
|
45
|
-
constructor(base, applicationID, developerKey, query) {
|
|
26
|
+
constructor(base, applicationID, developerKey, runtimeID, query) {
|
|
46
27
|
this.base = base;
|
|
47
28
|
this.applicationID = applicationID;
|
|
48
29
|
this.developerKey = developerKey;
|
|
30
|
+
this.runtimeID = runtimeID;
|
|
49
31
|
this.query = query;
|
|
50
32
|
this.withDomain(); // Ensure withDomain() is called by default
|
|
51
33
|
this.withAccessToken(); // Ensure withAccessToken() is called by default
|
|
52
34
|
this.withDeveloperKey(); // Ensure withDeveloperKey() is called by default
|
|
35
|
+
this.withRuntimeID(); // Ensure withRuntimeID() is called by default
|
|
53
36
|
}
|
|
54
37
|
withQuery(path, query) {
|
|
55
38
|
const newPath = [this.base.pathname, path].map(pathPart => pathPart.replace(/(^\/|\/$)/g, "")).join("/");
|
|
@@ -133,6 +116,10 @@ class RequestParameters {
|
|
|
133
116
|
this.setHeader("Authorization", `Bearer ${tkn}`);
|
|
134
117
|
return this;
|
|
135
118
|
}
|
|
119
|
+
withRuntimeID() {
|
|
120
|
+
this.setHeader("X-Machhub-Runtime-Id", this.runtimeID ?? "");
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
136
123
|
withDomain() {
|
|
137
124
|
this.setHeader("Domain", this.applicationID);
|
|
138
125
|
return this;
|
|
@@ -163,6 +150,12 @@ class RequestParameters {
|
|
|
163
150
|
if (body) {
|
|
164
151
|
if (body instanceof FormData) {
|
|
165
152
|
init.body = body;
|
|
153
|
+
// Remove Content-Type header if it exists, let browser set it for FormData
|
|
154
|
+
if (init.headers && typeof init.headers === 'object') {
|
|
155
|
+
const headers = init.headers;
|
|
156
|
+
delete headers['Content-Type'];
|
|
157
|
+
delete headers['content-type'];
|
|
158
|
+
}
|
|
166
159
|
}
|
|
167
160
|
else {
|
|
168
161
|
init.body = JSON.stringify(body);
|
|
@@ -84,20 +84,20 @@ export class MQTTService {
|
|
|
84
84
|
this.client.on('message', (topic, message) => {
|
|
85
85
|
for (const subscribedTopic of this.subscribedTopics) {
|
|
86
86
|
if (topic === subscribedTopic.topic) {
|
|
87
|
-
const parsedMessage = this.parseMessage(message);
|
|
87
|
+
const parsedMessage = this.parseMessage(message, topic);
|
|
88
88
|
subscribedTopic.handler(parsedMessage);
|
|
89
89
|
break;
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
|
-
parseMessage(message) {
|
|
94
|
+
parseMessage(message, topic) {
|
|
95
95
|
try {
|
|
96
96
|
return JSON.parse(message.toString());
|
|
97
97
|
}
|
|
98
98
|
catch (error) {
|
|
99
|
-
console.
|
|
100
|
-
return
|
|
99
|
+
console.warn("Failed to parse message as JSON '" + topic + "' : ", message.toString());
|
|
100
|
+
return message.toString();
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -66,9 +66,9 @@ export declare class NATSService {
|
|
|
66
66
|
*/
|
|
67
67
|
printFunctions(): void;
|
|
68
68
|
/**
|
|
69
|
-
* Parses a message buffer into a JSON object.
|
|
69
|
+
* Parses a message buffer into a JSON object or returns raw data.
|
|
70
70
|
* @param message {Uint8Array} The message buffer.
|
|
71
|
-
* @returns {unknown} The parsed message.
|
|
71
|
+
* @returns {unknown} The parsed message (JSON object) or the raw string if parsing fails.
|
|
72
72
|
*/
|
|
73
73
|
private parseMessage;
|
|
74
74
|
/**
|
|
@@ -104,20 +104,31 @@ export class NATSService {
|
|
|
104
104
|
console.error("Error handling message:", err);
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
msg.
|
|
111
|
-
|
|
107
|
+
try {
|
|
108
|
+
const dataStr = Buffer.from(msg.data).toString('utf-8');
|
|
109
|
+
const data = JSON.parse(dataStr);
|
|
110
|
+
const subjectParts = msg.subject.split(".");
|
|
111
|
+
if (subjectParts.length !== 4) {
|
|
112
|
+
msg.respond(JSON.stringify({ error: "Invalid subject format" }));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const functionName = subjectParts[3];
|
|
116
|
+
this.executeFunction(functionName, data)
|
|
117
|
+
.then((result) => {
|
|
118
|
+
msg.respond(JSON.stringify(result));
|
|
119
|
+
})
|
|
120
|
+
.catch((e) => {
|
|
121
|
+
console.log("Error executing function '" + functionName + "': ", e);
|
|
122
|
+
msg.respond(JSON.stringify({ status: "failed", error: e.message }));
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (parseError) {
|
|
126
|
+
console.error("Error parsing function execution message:", parseError);
|
|
127
|
+
msg.respond(JSON.stringify({
|
|
128
|
+
status: "failed",
|
|
129
|
+
error: `Failed to parse message: ${parseError.message}`
|
|
130
|
+
}));
|
|
112
131
|
}
|
|
113
|
-
const functionName = subjectParts[3];
|
|
114
|
-
this.executeFunction(functionName, data)
|
|
115
|
-
.then((result) => {
|
|
116
|
-
msg.respond(JSON.stringify(result));
|
|
117
|
-
})
|
|
118
|
-
.catch((e) => {
|
|
119
|
-
msg.respond(JSON.stringify({ status: "failed", error: e.message }));
|
|
120
|
-
});
|
|
121
132
|
},
|
|
122
133
|
});
|
|
123
134
|
if (sub)
|
|
@@ -192,17 +203,34 @@ export class NATSService {
|
|
|
192
203
|
NATSService.log("Available functions: ", Object.keys(this.functions).join(", "));
|
|
193
204
|
}
|
|
194
205
|
/**
|
|
195
|
-
* Parses a message buffer into a JSON object.
|
|
206
|
+
* Parses a message buffer into a JSON object or returns raw data.
|
|
196
207
|
* @param message {Uint8Array} The message buffer.
|
|
197
|
-
* @returns {unknown} The parsed message.
|
|
208
|
+
* @returns {unknown} The parsed message (JSON object) or the raw string if parsing fails.
|
|
198
209
|
*/
|
|
199
210
|
parseMessage(message) {
|
|
200
211
|
try {
|
|
201
|
-
|
|
212
|
+
const messageStr = Buffer.from(message).toString('utf-8');
|
|
213
|
+
// Check if the message is empty
|
|
214
|
+
if (!messageStr || messageStr.trim().length === 0) {
|
|
215
|
+
NATSService.log("Received empty message");
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Try to parse as JSON
|
|
219
|
+
return JSON.parse(messageStr);
|
|
202
220
|
}
|
|
203
221
|
catch (error) {
|
|
222
|
+
// If JSON parsing fails, check if it's binary data or non-JSON content
|
|
223
|
+
const messageStr = Buffer.from(message).toString('utf-8');
|
|
224
|
+
// Check if it looks like binary data (contains non-printable characters)
|
|
225
|
+
const isBinary = message.some(byte => byte < 32 && byte !== 9 && byte !== 10 && byte !== 13);
|
|
226
|
+
if (isBinary) {
|
|
227
|
+
NATSService.log("Received binary data, returning as Uint8Array");
|
|
228
|
+
return message; // Return raw binary data
|
|
229
|
+
}
|
|
230
|
+
// If it's a plain string (not JSON), return as is
|
|
231
|
+
NATSService.log("Failed to parse message as JSON, returning as string:", messageStr.substring(0, 100));
|
|
204
232
|
console.error("Error parsing message:", error);
|
|
205
|
-
return
|
|
233
|
+
return messageStr;
|
|
206
234
|
}
|
|
207
235
|
}
|
|
208
236
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@machhub-dev/sdk-ts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "MACHHUB TYPESCRIPT SDK",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"machhub",
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
"build": "npx tsc && npx tsc --module CommonJS --outDir dist/cjs"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@nats-io/nats-core": "^3.0.2",
|
|
24
|
+
"@nats-io/transport-node": "^3.0.2",
|
|
23
25
|
"mqtt": "^5.10.4",
|
|
24
26
|
"safe-buffer": "^5.2.1",
|
|
25
27
|
"typescript": "^5.8.3",
|
|
26
|
-
"undici-types": "^7.4.0"
|
|
27
|
-
"@nats-io/nats-core": "^3.0.2",
|
|
28
|
-
"@nats-io/transport-node": "^3.0.2"
|
|
28
|
+
"undici-types": "^7.4.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^22.13.5"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|
|
@@ -27,6 +27,7 @@ export class Collection {
|
|
|
27
27
|
this.collectionName = collectionName;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
|
|
30
31
|
filter(fieldName: string, operator: "=" | ">" | "<" | "<=" | ">=" | "!=", value: any): Collection {
|
|
31
32
|
this.queryParams[`filter[${fieldName}][${operator}][${typeof value}]`] = value;
|
|
32
33
|
return this;
|
|
@@ -68,9 +69,20 @@ export class Collection {
|
|
|
68
69
|
|
|
69
70
|
async create(data: Record<string, any>): Promise<any> {
|
|
70
71
|
try {
|
|
71
|
-
|
|
72
|
+
const formData = new FormData();
|
|
73
|
+
|
|
74
|
+
for (const [key, value] of Object.entries(data)) {
|
|
75
|
+
if (value instanceof File) {
|
|
76
|
+
formData.append(key, value, value.name);
|
|
77
|
+
data[key] = value.name
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
formData.append("record", JSON.stringify(data))
|
|
81
|
+
return await this.httpService.request
|
|
82
|
+
.withBody(formData)
|
|
83
|
+
.post(this.collectionName);
|
|
72
84
|
} catch (error) {
|
|
73
|
-
throw new CollectionError(
|
|
85
|
+
throw new CollectionError("create", this.collectionName, error as Error);
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
88
|
|
package/src/sdk-ts.ts
CHANGED
|
@@ -19,8 +19,8 @@ class HTTPClient {
|
|
|
19
19
|
* @param applicationID The ID for your application (required)
|
|
20
20
|
* @param httpUrl The base URL for HTTP connection (default = http://localhost:6188)
|
|
21
21
|
*/
|
|
22
|
-
constructor(applicationID: string, httpUrl:string, developerKey?: string) {
|
|
23
|
-
this.httpService = new HTTPService(httpUrl, MACHHUB_SDK_PATH, applicationID, developerKey);
|
|
22
|
+
constructor(applicationID: string, httpUrl:string, developerKey?: string, runtimeID?: string) {
|
|
23
|
+
this.httpService = new HTTPService(httpUrl, MACHHUB_SDK_PATH, applicationID, developerKey, runtimeID);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -173,26 +173,26 @@ export class SDK {
|
|
|
173
173
|
// console.log("Using application_id:", config.application_id);
|
|
174
174
|
|
|
175
175
|
|
|
176
|
-
const
|
|
177
|
-
console.log("Port:",
|
|
176
|
+
const {port, runtimeID} = await getEnvConfig();
|
|
177
|
+
console.log("Port:", port);
|
|
178
178
|
|
|
179
179
|
if (!config.httpUrl) {
|
|
180
|
-
config.httpUrl = "http://localhost:" +
|
|
180
|
+
config.httpUrl = "http://localhost:" + port;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
if (!config.mqttUrl) {
|
|
184
|
-
config.mqttUrl = "ws://localhost:" +
|
|
184
|
+
config.mqttUrl = "ws://localhost:" + port + "/mqtt";
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
if (!config.natsUrl) {
|
|
188
|
-
config.natsUrl = "ws://localhost:" +
|
|
188
|
+
config.natsUrl = "ws://localhost:" + port + "/nats";
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
const { application_id, httpUrl, mqttUrl, natsUrl } = config;
|
|
192
192
|
|
|
193
193
|
console.log("SDK Config:", { application_id, httpUrl, mqttUrl, natsUrl, developer_key: config.developer_key?.split('').map((_, i) => i < config!.developer_key!.length - 4 ? '*' : config!.developer_key![i]).join('') });
|
|
194
194
|
|
|
195
|
-
this.http = new HTTPClient(application_id, httpUrl, config.developer_key);
|
|
195
|
+
this.http = new HTTPClient(application_id, httpUrl, config.developer_key, runtimeID);
|
|
196
196
|
this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
|
|
197
197
|
this.nats = await NATSClient.getInstance(application_id, natsUrl);
|
|
198
198
|
|
|
@@ -273,19 +273,19 @@ export class SDK {
|
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
async function
|
|
276
|
+
async function getEnvConfig(): Promise<{port:string, runtimeID:string}> {
|
|
277
277
|
try {
|
|
278
278
|
// Try to find the configuration endpoint by testing different base paths
|
|
279
279
|
const configUrl = await findConfigEndpoint();
|
|
280
|
-
const response = await fetchData<{
|
|
280
|
+
const response = await fetchData<{port:string, runtimeID:string}>(configUrl);
|
|
281
281
|
// console.log('Response:', response);
|
|
282
282
|
// console.log('runtimeID: ', response.runtimeID);
|
|
283
283
|
// console.log('applicationID: ', response.runtimeID.split('XmchX')[0]);
|
|
284
|
-
return response.port;
|
|
284
|
+
return { port: response.port, runtimeID: response.runtimeID};
|
|
285
285
|
} catch (error) {
|
|
286
286
|
// console.log('No configured runtime ID:', error);
|
|
287
287
|
// TODO: Use DevPort from SDK Config or default to 61888
|
|
288
|
-
return "61888";
|
|
288
|
+
return { port: "61888", runtimeID: ""};
|
|
289
289
|
}
|
|
290
290
|
}
|
|
291
291
|
|
|
@@ -19,39 +19,18 @@ export class HTTPService {
|
|
|
19
19
|
private url: URL;
|
|
20
20
|
private applicationID: string;
|
|
21
21
|
private developerKey?: string;
|
|
22
|
+
private runtimeID?: string;
|
|
22
23
|
|
|
23
|
-
constructor(url: string, prefix: string, applicationID: string, developerKey?: string) {
|
|
24
|
+
constructor(url: string, prefix: string, applicationID: string, developerKey?: string, runtimeID?: string) {
|
|
24
25
|
if (prefix == null) prefix = "";
|
|
25
26
|
this.url = new URL(prefix, url);
|
|
26
27
|
this.applicationID = "domains:" + applicationID
|
|
27
28
|
this.developerKey = developerKey
|
|
29
|
+
this.runtimeID = runtimeID
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
public get request(): RequestParameters {
|
|
31
|
-
return new RequestParameters(this.url, this.applicationID, this.developerKey);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private addRuntimeHeaders(headers: Record<string, string> = {}): Record<string, string> {
|
|
35
|
-
// Add runtime ID from cookie if available (for hosted applications)
|
|
36
|
-
if (typeof document !== 'undefined') {
|
|
37
|
-
const runtimeID = this.getCookie('machhub_runtime_id');
|
|
38
|
-
if (runtimeID) {
|
|
39
|
-
headers['X-MachHub-Runtime-ID'] = runtimeID;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return headers;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private getCookie(name: string): string | null {
|
|
47
|
-
if (typeof document === 'undefined') return null;
|
|
48
|
-
|
|
49
|
-
const value = `; ${document.cookie}`;
|
|
50
|
-
const parts = value.split(`; ${name}=`);
|
|
51
|
-
if (parts.length === 2) {
|
|
52
|
-
return parts.pop()?.split(';').shift() || null;
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
33
|
+
return new RequestParameters(this.url, this.applicationID, this.developerKey, this.runtimeID);
|
|
55
34
|
}
|
|
56
35
|
}
|
|
57
36
|
|
|
@@ -59,18 +38,21 @@ class RequestParameters {
|
|
|
59
38
|
private base: URL;
|
|
60
39
|
private applicationID: string;
|
|
61
40
|
private developerKey?: string;
|
|
41
|
+
private runtimeID?: string;
|
|
62
42
|
public query?: Record<string, string>;
|
|
63
43
|
public init?: RequestInit;
|
|
64
44
|
public headers?: Record<string, string>;
|
|
65
45
|
|
|
66
|
-
constructor(base: URL, applicationID: string, developerKey?:string, query?: Record<string, string>) {
|
|
46
|
+
constructor(base: URL, applicationID: string, developerKey?: string, runtimeID?: string, query?: Record<string, string>) {
|
|
67
47
|
this.base = base;
|
|
68
48
|
this.applicationID = applicationID;
|
|
69
49
|
this.developerKey = developerKey;
|
|
50
|
+
this.runtimeID = runtimeID;
|
|
70
51
|
this.query = query;
|
|
71
52
|
this.withDomain(); // Ensure withDomain() is called by default
|
|
72
53
|
this.withAccessToken(); // Ensure withAccessToken() is called by default
|
|
73
54
|
this.withDeveloperKey(); // Ensure withDeveloperKey() is called by default
|
|
55
|
+
this.withRuntimeID(); // Ensure withRuntimeID() is called by default
|
|
74
56
|
}
|
|
75
57
|
|
|
76
58
|
private withQuery(path: string, query?: Record<string, string>): URL {
|
|
@@ -170,6 +152,11 @@ class RequestParameters {
|
|
|
170
152
|
return this;
|
|
171
153
|
}
|
|
172
154
|
|
|
155
|
+
public withRuntimeID(): RequestParameters {
|
|
156
|
+
this.setHeader("X-Machhub-Runtime-Id", this.runtimeID ?? "");
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
173
160
|
public withDomain(): RequestParameters {
|
|
174
161
|
this.setHeader("Domain", this.applicationID);
|
|
175
162
|
return this;
|
|
@@ -211,6 +198,12 @@ class RequestParameters {
|
|
|
211
198
|
if (body) {
|
|
212
199
|
if (body instanceof FormData) {
|
|
213
200
|
init.body = body;
|
|
201
|
+
// Remove Content-Type header if it exists, let browser set it for FormData
|
|
202
|
+
if (init.headers && typeof init.headers === 'object') {
|
|
203
|
+
const headers = init.headers as Record<string, string>;
|
|
204
|
+
delete headers['Content-Type'];
|
|
205
|
+
delete headers['content-type'];
|
|
206
|
+
}
|
|
214
207
|
} else {
|
|
215
208
|
init.body = JSON.stringify(body);
|
|
216
209
|
init.headers = {
|
|
@@ -102,7 +102,7 @@ export class MQTTService {
|
|
|
102
102
|
this.client.on('message', (topic: string, message: Buffer) => {
|
|
103
103
|
for (const subscribedTopic of this.subscribedTopics) {
|
|
104
104
|
if (topic === subscribedTopic.topic) {
|
|
105
|
-
const parsedMessage = this.parseMessage(message);
|
|
105
|
+
const parsedMessage = this.parseMessage(message, topic);
|
|
106
106
|
subscribedTopic.handler(parsedMessage);
|
|
107
107
|
break;
|
|
108
108
|
}
|
|
@@ -110,12 +110,12 @@ export class MQTTService {
|
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
private parseMessage(message: Buffer): unknown {
|
|
113
|
+
private parseMessage(message: Buffer, topic: string): unknown {
|
|
114
114
|
try {
|
|
115
115
|
return JSON.parse(message.toString());
|
|
116
116
|
} catch (error) {
|
|
117
|
-
console.
|
|
118
|
-
return
|
|
117
|
+
console.warn("Failed to parse message as JSON '" + topic + "' : ", message.toString());
|
|
118
|
+
return message.toString();
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
@@ -119,21 +119,31 @@ export class NATSService {
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
msg.
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
try {
|
|
123
|
+
const dataStr = Buffer.from(msg.data).toString('utf-8');
|
|
124
|
+
const data = JSON.parse(dataStr) as Record<string, any>;
|
|
125
|
+
const subjectParts = msg.subject.split(".");
|
|
126
|
+
if (subjectParts.length !== 4) {
|
|
127
|
+
msg.respond(JSON.stringify({ error: "Invalid subject format" }));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
const functionName = subjectParts[3];
|
|
132
|
+
this.executeFunction(functionName, data)
|
|
133
|
+
.then((result) => {
|
|
134
|
+
msg.respond(JSON.stringify(result));
|
|
135
|
+
})
|
|
136
|
+
.catch((e: any) => {
|
|
137
|
+
console.log("Error executing function '" + functionName + "': ", e);
|
|
138
|
+
msg.respond(JSON.stringify({ status: "failed", error: e.message }));
|
|
139
|
+
});
|
|
140
|
+
} catch (parseError: any) {
|
|
141
|
+
console.error("Error parsing function execution message:", parseError);
|
|
142
|
+
msg.respond(JSON.stringify({
|
|
143
|
+
status: "failed",
|
|
144
|
+
error: `Failed to parse message: ${parseError.message}`
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
137
147
|
},
|
|
138
148
|
});
|
|
139
149
|
if (sub) this.subscriptions.push(sub);
|
|
@@ -223,16 +233,37 @@ export class NATSService {
|
|
|
223
233
|
}
|
|
224
234
|
|
|
225
235
|
/**
|
|
226
|
-
* Parses a message buffer into a JSON object.
|
|
236
|
+
* Parses a message buffer into a JSON object or returns raw data.
|
|
227
237
|
* @param message {Uint8Array} The message buffer.
|
|
228
|
-
* @returns {unknown} The parsed message.
|
|
238
|
+
* @returns {unknown} The parsed message (JSON object) or the raw string if parsing fails.
|
|
229
239
|
*/
|
|
230
240
|
private parseMessage(message: Uint8Array): unknown {
|
|
231
241
|
try {
|
|
232
|
-
|
|
242
|
+
const messageStr = Buffer.from(message).toString('utf-8');
|
|
243
|
+
// Check if the message is empty
|
|
244
|
+
if (!messageStr || messageStr.trim().length === 0) {
|
|
245
|
+
NATSService.log("Received empty message");
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Try to parse as JSON
|
|
250
|
+
return JSON.parse(messageStr);
|
|
233
251
|
} catch (error) {
|
|
252
|
+
// If JSON parsing fails, check if it's binary data or non-JSON content
|
|
253
|
+
const messageStr = Buffer.from(message).toString('utf-8');
|
|
254
|
+
|
|
255
|
+
// Check if it looks like binary data (contains non-printable characters)
|
|
256
|
+
const isBinary = message.some(byte => byte < 32 && byte !== 9 && byte !== 10 && byte !== 13);
|
|
257
|
+
|
|
258
|
+
if (isBinary) {
|
|
259
|
+
NATSService.log("Received binary data, returning as Uint8Array");
|
|
260
|
+
return message; // Return raw binary data
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// If it's a plain string (not JSON), return as is
|
|
264
|
+
NATSService.log("Failed to parse message as JSON, returning as string:", messageStr.substring(0, 100));
|
|
234
265
|
console.error("Error parsing message:", error);
|
|
235
|
-
return
|
|
266
|
+
return messageStr;
|
|
236
267
|
}
|
|
237
268
|
}
|
|
238
269
|
|