@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.
@@ -55,10 +55,20 @@ class Collection {
55
55
  }
56
56
  async create(data) {
57
57
  try {
58
- return await this.httpService.request.withJSON(data).post(this.collectionName);
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('create', this.collectionName, error);
71
+ throw new CollectionError("create", this.collectionName, error);
62
72
  }
63
73
  }
64
74
  async update(id, data) {
@@ -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 PORT = await getEnvPort();
151
- console.log("Port:", PORT);
150
+ const { port, runtimeID } = await getEnvConfig();
151
+ console.log("Port:", port);
152
152
  if (!config.httpUrl) {
153
- config.httpUrl = "http://localhost:" + PORT;
153
+ config.httpUrl = "http://localhost:" + port;
154
154
  }
155
155
  if (!config.mqttUrl) {
156
- config.mqttUrl = "ws://localhost:" + PORT + "/mqtt";
156
+ config.mqttUrl = "ws://localhost:" + port + "/mqtt";
157
157
  }
158
158
  if (!config.natsUrl) {
159
- config.natsUrl = "ws://localhost:" + PORT + "/nats";
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 getEnvPort() {
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
- constructor(url: string, prefix: string, applicationID: string, developerKey?: string);
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.error("Error parsing message:", error);
106
- return null;
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
- const data = JSON.parse(msg.data.toString());
111
- const subjectParts = msg.subject.split(".");
112
- if (subjectParts.length !== 4) {
113
- msg.respond(JSON.stringify({ error: "Invalid subject format" }));
114
- return;
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
- return JSON.parse(Buffer.from(message).toString());
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 null;
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
- return await this.httpService.request.withJSON(data).post(this.collectionName);
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('create', this.collectionName, error);
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 PORT = await getEnvPort();
148
- console.log("Port:", PORT);
147
+ const { port, runtimeID } = await getEnvConfig();
148
+ console.log("Port:", port);
149
149
  if (!config.httpUrl) {
150
- config.httpUrl = "http://localhost:" + PORT;
150
+ config.httpUrl = "http://localhost:" + port;
151
151
  }
152
152
  if (!config.mqttUrl) {
153
- config.mqttUrl = "ws://localhost:" + PORT + "/mqtt";
153
+ config.mqttUrl = "ws://localhost:" + port + "/mqtt";
154
154
  }
155
155
  if (!config.natsUrl) {
156
- config.natsUrl = "ws://localhost:" + PORT + "/nats";
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 getEnvPort() {
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
- constructor(url: string, prefix: string, applicationID: string, developerKey?: string);
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.error("Error parsing message:", error);
100
- return null;
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
- const data = JSON.parse(msg.data.toString());
108
- const subjectParts = msg.subject.split(".");
109
- if (subjectParts.length !== 4) {
110
- msg.respond(JSON.stringify({ error: "Invalid subject format" }));
111
- return;
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
- return JSON.parse(Buffer.from(message).toString());
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 null;
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.7",
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
- return await this.httpService.request.withJSON(data).post(this.collectionName);
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('create', this.collectionName, error as Error);
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 PORT = await getEnvPort();
177
- console.log("Port:", PORT);
176
+ const {port, runtimeID} = await getEnvConfig();
177
+ console.log("Port:", port);
178
178
 
179
179
  if (!config.httpUrl) {
180
- config.httpUrl = "http://localhost:" + PORT;
180
+ config.httpUrl = "http://localhost:" + port;
181
181
  }
182
182
 
183
183
  if (!config.mqttUrl) {
184
- config.mqttUrl = "ws://localhost:" + PORT + "/mqtt";
184
+ config.mqttUrl = "ws://localhost:" + port + "/mqtt";
185
185
  }
186
186
 
187
187
  if (!config.natsUrl) {
188
- config.natsUrl = "ws://localhost:" + PORT + "/nats";
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 getEnvPort(): Promise<string> {
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<{runtimeID:string, port:string}>(configUrl);
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.error("Error parsing message:", error);
118
- return null;
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
- const data = JSON.parse(msg.data.toString()) as Record<string, any>;
123
- const subjectParts = msg.subject.split(".");
124
- if (subjectParts.length !== 4) {
125
- msg.respond(JSON.stringify({ error: "Invalid subject format" }));
126
- return;
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
- const functionName = subjectParts[3];
130
- this.executeFunction(functionName, data)
131
- .then((result) => {
132
- msg.respond(JSON.stringify(result));
133
- })
134
- .catch((e: any) => {
135
- msg.respond(JSON.stringify({ status: "failed", error: e.message }));
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
- return JSON.parse(Buffer.from(message).toString());
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 null;
266
+ return messageStr;
236
267
  }
237
268
  }
238
269