@machhub-dev/sdk-ts 0.0.5 → 0.0.7

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.
@@ -1,5 +1,11 @@
1
1
  import { HTTPService } from "../services/http.service.js";
2
2
  import { MQTTService } from "../services/mqtt.service.js";
3
+ export declare class CollectionError extends Error {
4
+ operation: string;
5
+ collectionName: string;
6
+ originalError: Error;
7
+ constructor(operation: string, collectionName: string, originalError: Error);
8
+ }
3
9
  export declare class Collection {
4
10
  protected httpService: HTTPService;
5
11
  protected mqttService: MQTTService | null;
@@ -1,6 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Collection = void 0;
3
+ exports.Collection = exports.CollectionError = void 0;
4
+ class CollectionError extends Error {
5
+ constructor(operation, collectionName, originalError) {
6
+ super(`Collection operation '${operation}' failed on '${collectionName}': ${originalError.message}`);
7
+ this.name = 'CollectionError';
8
+ this.operation = operation;
9
+ this.collectionName = collectionName;
10
+ this.originalError = originalError;
11
+ }
12
+ }
13
+ exports.CollectionError = CollectionError;
4
14
  class Collection {
5
15
  constructor(httpService, mqttService, collectionName) {
6
16
  this.queryParams = {};
@@ -25,28 +35,53 @@ class Collection {
25
35
  return this;
26
36
  }
27
37
  async getAll() {
28
- return this.httpService.request.get(this.collectionName + "/all", this.queryParams);
38
+ try {
39
+ return await this.httpService.request.get(this.collectionName + "/all", this.queryParams);
40
+ }
41
+ catch (error) {
42
+ throw new CollectionError('getAll', this.collectionName, error);
43
+ }
29
44
  }
30
45
  async getOne(id) {
31
46
  if (!id) {
32
47
  throw new Error("ID must be provided");
33
48
  }
34
- return this.httpService.request.get(id);
49
+ try {
50
+ return await this.httpService.request.get(id);
51
+ }
52
+ catch (error) {
53
+ throw new CollectionError('getOne', this.collectionName, error);
54
+ }
35
55
  }
36
56
  async create(data) {
37
- return this.httpService.request.withJSON(data).post(this.collectionName);
57
+ try {
58
+ return await this.httpService.request.withJSON(data).post(this.collectionName);
59
+ }
60
+ catch (error) {
61
+ throw new CollectionError('create', this.collectionName, error);
62
+ }
38
63
  }
39
64
  async update(id, data) {
40
65
  if (!id) {
41
66
  throw new Error("ID must be provided");
42
67
  }
43
- return this.httpService.request.withJSON(data).put(id);
68
+ try {
69
+ return await this.httpService.request.withJSON(data).put(id);
70
+ }
71
+ catch (error) {
72
+ throw new CollectionError('update', this.collectionName, error);
73
+ }
44
74
  }
45
75
  async delete(id) {
46
76
  if (!id) {
47
77
  throw new Error("ID must be provided");
48
78
  }
49
- return this.httpService.request.delete(id);
79
+ try {
80
+ return await this.httpService.request.delete(id);
81
+ }
82
+ catch (error) {
83
+ throw new CollectionError('delete', this.collectionName, error);
84
+ }
50
85
  }
51
86
  }
52
87
  exports.Collection = Collection;
@@ -148,7 +148,7 @@ class SDK {
148
148
  config = { application_id: "" };
149
149
  // console.log("Using application_id:", config.application_id);
150
150
  const PORT = await getEnvPort();
151
- // console.log("Using port:", PORT);
151
+ console.log("Port:", PORT);
152
152
  if (!config.httpUrl) {
153
153
  config.httpUrl = "http://localhost:" + PORT;
154
154
  }
@@ -159,7 +159,7 @@ class SDK {
159
159
  config.natsUrl = "ws://localhost:" + PORT + "/nats";
160
160
  }
161
161
  const { application_id, httpUrl, mqttUrl, natsUrl } = config;
162
- // console.log("Final config:", { application_id, httpUrl, mqttUrl, natsUrl });
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
163
  this.http = new HTTPClient(application_id, httpUrl, config.developer_key);
164
164
  this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
165
165
  this.nats = await NATSClient.getInstance(application_id, natsUrl);
@@ -236,7 +236,9 @@ class SDK {
236
236
  exports.SDK = SDK;
237
237
  async function getEnvPort() {
238
238
  try {
239
- const response = await fetchData(window.location.origin + "/_cfg");
239
+ // Try to find the configuration endpoint by testing different base paths
240
+ const configUrl = await findConfigEndpoint();
241
+ const response = await fetchData(configUrl);
240
242
  // console.log('Response:', response);
241
243
  // console.log('runtimeID: ', response.runtimeID);
242
244
  // console.log('applicationID: ', response.runtimeID.split('XmchX')[0]);
@@ -248,13 +250,90 @@ async function getEnvPort() {
248
250
  return "61888";
249
251
  }
250
252
  }
251
- async function fetchData(url) {
253
+ /**
254
+ * Attempts to find the correct configuration endpoint by trying different base paths
255
+ * Handles both port-based hosting (direct) and path-based hosting (reverse proxy)
256
+ */
257
+ async function findConfigEndpoint() {
258
+ const origin = window.location.origin;
259
+ const pathname = window.location.pathname;
260
+ // List of potential base paths to try, ordered by likelihood
261
+ const basePaths = [
262
+ // 1. Try origin directly (for port-based hosting like localhost:6190)
263
+ origin,
264
+ // 2. Try current path segments for path-based hosting
265
+ ...generatePathCandidates(pathname),
266
+ // 3. Try common root paths as fallback
267
+ origin,
268
+ ];
269
+ // Remove duplicates while preserving order
270
+ const uniqueBasePaths = [...new Set(basePaths)];
271
+ for (const basePath of uniqueBasePaths) {
272
+ try {
273
+ const configUrl = `${basePath}/_cfg`;
274
+ // Test if this endpoint returns valid JSON config by making a GET request
275
+ const testResponse = await fetch(configUrl, {
276
+ method: 'GET',
277
+ headers: {
278
+ 'Accept': 'application/json',
279
+ },
280
+ signal: AbortSignal.timeout(2000) // 2 second timeout
281
+ });
282
+ if (testResponse.ok) {
283
+ // Validate that the response is JSON and contains the expected 'port' field
284
+ const contentType = testResponse.headers.get('content-type');
285
+ if (contentType && contentType.includes('application/json')) {
286
+ try {
287
+ const testData = await testResponse.json();
288
+ // Check if the response has the expected structure with a 'port' field
289
+ if (testData && typeof testData === 'object' && 'port' in testData) {
290
+ // console.log(`Found config endpoint at: ${configUrl}`);
291
+ return configUrl;
292
+ }
293
+ }
294
+ catch (jsonError) {
295
+ // Not valid JSON, continue to next candidate
296
+ continue;
297
+ }
298
+ }
299
+ }
300
+ }
301
+ catch (error) {
302
+ // Continue to next candidate
303
+ continue;
304
+ }
305
+ }
306
+ // If all attempts fail, default to origin + /_cfg
307
+ console.warn('Could not find config endpoint, using default origin');
308
+ return `${origin}/_cfg`;
309
+ }
310
+ /**
311
+ * Generates potential base path candidates from the current pathname
312
+ * For example, /demo2/homepage/settings would generate:
313
+ * - http://localhost/demo2/homepage
314
+ * - http://localhost/demo2
315
+ * - http://localhost
316
+ */
317
+ function generatePathCandidates(pathname) {
318
+ const origin = window.location.origin;
319
+ const pathSegments = pathname.split('/').filter(segment => segment.length > 0);
320
+ const candidates = [];
321
+ // Generate paths by progressively removing segments from the end
322
+ for (let i = pathSegments.length; i > 0; i--) {
323
+ const path = '/' + pathSegments.slice(0, i).join('/');
324
+ candidates.push(origin + path);
325
+ }
326
+ return candidates;
327
+ }
328
+ async function fetchData(url, options) {
252
329
  try {
253
330
  const response = await fetch(url, {
254
331
  method: 'GET',
255
332
  headers: {
256
333
  'Accept': 'application/json',
257
334
  },
335
+ signal: AbortSignal.timeout(5000), // 5 second timeout
336
+ ...options
258
337
  });
259
338
  if (!response.ok) {
260
339
  throw new Error(`HTTP error! status: ${response.status}`);
@@ -42,7 +42,7 @@ class MQTTService {
42
42
  this.subscribedTopics.push({ topic, handler });
43
43
  if (topic == "")
44
44
  return;
45
- console.log("New Subscription Handler:", topic);
45
+ // console.log("New Subscription Handler:", topic);
46
46
  this.client.subscribe(topic, { qos: 2 }, (err) => {
47
47
  if (err) {
48
48
  console.error(`Failed to subscribe to topic ${topic}:`, err);
@@ -61,7 +61,7 @@ class MQTTService {
61
61
  publish(topic, message) {
62
62
  try {
63
63
  const payload = JSON.stringify(message);
64
- console.log("Publishing to", topic, "with payload:", payload);
64
+ // console.log("Publishing to", topic, "with payload:", payload);
65
65
  this.client.publish(topic, payload, {
66
66
  qos: 2,
67
67
  retain: true,
@@ -1,5 +1,11 @@
1
1
  import { HTTPService } from "../services/http.service.js";
2
2
  import { MQTTService } from "../services/mqtt.service.js";
3
+ export declare class CollectionError extends Error {
4
+ operation: string;
5
+ collectionName: string;
6
+ originalError: Error;
7
+ constructor(operation: string, collectionName: string, originalError: Error);
8
+ }
3
9
  export declare class Collection {
4
10
  protected httpService: HTTPService;
5
11
  protected mqttService: MQTTService | null;
@@ -1,3 +1,12 @@
1
+ export class CollectionError extends Error {
2
+ constructor(operation, collectionName, originalError) {
3
+ super(`Collection operation '${operation}' failed on '${collectionName}': ${originalError.message}`);
4
+ this.name = 'CollectionError';
5
+ this.operation = operation;
6
+ this.collectionName = collectionName;
7
+ this.originalError = originalError;
8
+ }
9
+ }
1
10
  export class Collection {
2
11
  constructor(httpService, mqttService, collectionName) {
3
12
  this.queryParams = {};
@@ -22,27 +31,52 @@ export class Collection {
22
31
  return this;
23
32
  }
24
33
  async getAll() {
25
- return this.httpService.request.get(this.collectionName + "/all", this.queryParams);
34
+ try {
35
+ return await this.httpService.request.get(this.collectionName + "/all", this.queryParams);
36
+ }
37
+ catch (error) {
38
+ throw new CollectionError('getAll', this.collectionName, error);
39
+ }
26
40
  }
27
41
  async getOne(id) {
28
42
  if (!id) {
29
43
  throw new Error("ID must be provided");
30
44
  }
31
- return this.httpService.request.get(id);
45
+ try {
46
+ return await this.httpService.request.get(id);
47
+ }
48
+ catch (error) {
49
+ throw new CollectionError('getOne', this.collectionName, error);
50
+ }
32
51
  }
33
52
  async create(data) {
34
- return this.httpService.request.withJSON(data).post(this.collectionName);
53
+ try {
54
+ return await this.httpService.request.withJSON(data).post(this.collectionName);
55
+ }
56
+ catch (error) {
57
+ throw new CollectionError('create', this.collectionName, error);
58
+ }
35
59
  }
36
60
  async update(id, data) {
37
61
  if (!id) {
38
62
  throw new Error("ID must be provided");
39
63
  }
40
- return this.httpService.request.withJSON(data).put(id);
64
+ try {
65
+ return await this.httpService.request.withJSON(data).put(id);
66
+ }
67
+ catch (error) {
68
+ throw new CollectionError('update', this.collectionName, error);
69
+ }
41
70
  }
42
71
  async delete(id) {
43
72
  if (!id) {
44
73
  throw new Error("ID must be provided");
45
74
  }
46
- return this.httpService.request.delete(id);
75
+ try {
76
+ return await this.httpService.request.delete(id);
77
+ }
78
+ catch (error) {
79
+ throw new CollectionError('delete', this.collectionName, error);
80
+ }
47
81
  }
48
82
  }
package/dist/sdk-ts.js CHANGED
@@ -145,7 +145,7 @@ export class SDK {
145
145
  config = { application_id: "" };
146
146
  // console.log("Using application_id:", config.application_id);
147
147
  const PORT = await getEnvPort();
148
- // console.log("Using port:", PORT);
148
+ console.log("Port:", PORT);
149
149
  if (!config.httpUrl) {
150
150
  config.httpUrl = "http://localhost:" + PORT;
151
151
  }
@@ -156,7 +156,7 @@ export class SDK {
156
156
  config.natsUrl = "ws://localhost:" + PORT + "/nats";
157
157
  }
158
158
  const { application_id, httpUrl, mqttUrl, natsUrl } = config;
159
- // console.log("Final config:", { application_id, httpUrl, mqttUrl, natsUrl });
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
160
  this.http = new HTTPClient(application_id, httpUrl, config.developer_key);
161
161
  this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
162
162
  this.nats = await NATSClient.getInstance(application_id, natsUrl);
@@ -232,7 +232,9 @@ export class SDK {
232
232
  }
233
233
  async function getEnvPort() {
234
234
  try {
235
- const response = await fetchData(window.location.origin + "/_cfg");
235
+ // Try to find the configuration endpoint by testing different base paths
236
+ const configUrl = await findConfigEndpoint();
237
+ const response = await fetchData(configUrl);
236
238
  // console.log('Response:', response);
237
239
  // console.log('runtimeID: ', response.runtimeID);
238
240
  // console.log('applicationID: ', response.runtimeID.split('XmchX')[0]);
@@ -244,13 +246,90 @@ async function getEnvPort() {
244
246
  return "61888";
245
247
  }
246
248
  }
247
- async function fetchData(url) {
249
+ /**
250
+ * Attempts to find the correct configuration endpoint by trying different base paths
251
+ * Handles both port-based hosting (direct) and path-based hosting (reverse proxy)
252
+ */
253
+ async function findConfigEndpoint() {
254
+ const origin = window.location.origin;
255
+ const pathname = window.location.pathname;
256
+ // List of potential base paths to try, ordered by likelihood
257
+ const basePaths = [
258
+ // 1. Try origin directly (for port-based hosting like localhost:6190)
259
+ origin,
260
+ // 2. Try current path segments for path-based hosting
261
+ ...generatePathCandidates(pathname),
262
+ // 3. Try common root paths as fallback
263
+ origin,
264
+ ];
265
+ // Remove duplicates while preserving order
266
+ const uniqueBasePaths = [...new Set(basePaths)];
267
+ for (const basePath of uniqueBasePaths) {
268
+ try {
269
+ const configUrl = `${basePath}/_cfg`;
270
+ // Test if this endpoint returns valid JSON config by making a GET request
271
+ const testResponse = await fetch(configUrl, {
272
+ method: 'GET',
273
+ headers: {
274
+ 'Accept': 'application/json',
275
+ },
276
+ signal: AbortSignal.timeout(2000) // 2 second timeout
277
+ });
278
+ if (testResponse.ok) {
279
+ // Validate that the response is JSON and contains the expected 'port' field
280
+ const contentType = testResponse.headers.get('content-type');
281
+ if (contentType && contentType.includes('application/json')) {
282
+ try {
283
+ const testData = await testResponse.json();
284
+ // Check if the response has the expected structure with a 'port' field
285
+ if (testData && typeof testData === 'object' && 'port' in testData) {
286
+ // console.log(`Found config endpoint at: ${configUrl}`);
287
+ return configUrl;
288
+ }
289
+ }
290
+ catch (jsonError) {
291
+ // Not valid JSON, continue to next candidate
292
+ continue;
293
+ }
294
+ }
295
+ }
296
+ }
297
+ catch (error) {
298
+ // Continue to next candidate
299
+ continue;
300
+ }
301
+ }
302
+ // If all attempts fail, default to origin + /_cfg
303
+ console.warn('Could not find config endpoint, using default origin');
304
+ return `${origin}/_cfg`;
305
+ }
306
+ /**
307
+ * Generates potential base path candidates from the current pathname
308
+ * For example, /demo2/homepage/settings would generate:
309
+ * - http://localhost/demo2/homepage
310
+ * - http://localhost/demo2
311
+ * - http://localhost
312
+ */
313
+ function generatePathCandidates(pathname) {
314
+ const origin = window.location.origin;
315
+ const pathSegments = pathname.split('/').filter(segment => segment.length > 0);
316
+ const candidates = [];
317
+ // Generate paths by progressively removing segments from the end
318
+ for (let i = pathSegments.length; i > 0; i--) {
319
+ const path = '/' + pathSegments.slice(0, i).join('/');
320
+ candidates.push(origin + path);
321
+ }
322
+ return candidates;
323
+ }
324
+ async function fetchData(url, options) {
248
325
  try {
249
326
  const response = await fetch(url, {
250
327
  method: 'GET',
251
328
  headers: {
252
329
  'Accept': 'application/json',
253
330
  },
331
+ signal: AbortSignal.timeout(5000), // 5 second timeout
332
+ ...options
254
333
  });
255
334
  if (!response.ok) {
256
335
  throw new Error(`HTTP error! status: ${response.status}`);
@@ -36,7 +36,7 @@ export class MQTTService {
36
36
  this.subscribedTopics.push({ topic, handler });
37
37
  if (topic == "")
38
38
  return;
39
- console.log("New Subscription Handler:", topic);
39
+ // console.log("New Subscription Handler:", topic);
40
40
  this.client.subscribe(topic, { qos: 2 }, (err) => {
41
41
  if (err) {
42
42
  console.error(`Failed to subscribe to topic ${topic}:`, err);
@@ -55,7 +55,7 @@ export class MQTTService {
55
55
  publish(topic, message) {
56
56
  try {
57
57
  const payload = JSON.stringify(message);
58
- console.log("Publishing to", topic, "with payload:", payload);
58
+ // console.log("Publishing to", topic, "with payload:", payload);
59
59
  this.client.publish(topic, payload, {
60
60
  qos: 2,
61
61
  retain: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@machhub-dev/sdk-ts",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "MACHHUB TYPESCRIPT SDK",
5
5
  "keywords": [
6
6
  "machhub",
@@ -1,6 +1,20 @@
1
1
  import { HTTPService } from "../services/http.service.js";
2
2
  import { MQTTService } from "../services/mqtt.service.js";
3
3
 
4
+ export class CollectionError extends Error {
5
+ public operation: string;
6
+ public collectionName: string;
7
+ public originalError: Error;
8
+
9
+ constructor(operation: string, collectionName: string, originalError: Error) {
10
+ super(`Collection operation '${operation}' failed on '${collectionName}': ${originalError.message}`);
11
+ this.name = 'CollectionError';
12
+ this.operation = operation;
13
+ this.collectionName = collectionName;
14
+ this.originalError = originalError;
15
+ }
16
+ }
17
+
4
18
  export class Collection {
5
19
  protected httpService: HTTPService;
6
20
  protected mqttService: MQTTService | null;
@@ -34,31 +48,51 @@ export class Collection {
34
48
  }
35
49
 
36
50
  async getAll(): Promise<any[]> {
37
- return this.httpService.request.get(this.collectionName + "/all", this.queryParams);
51
+ try {
52
+ return await this.httpService.request.get(this.collectionName + "/all", this.queryParams);
53
+ } catch (error) {
54
+ throw new CollectionError('getAll', this.collectionName, error as Error);
55
+ }
38
56
  }
39
57
 
40
58
  async getOne(id: string): Promise<any> {
41
59
  if (!id) {
42
60
  throw new Error("ID must be provided");
43
61
  }
44
- return this.httpService.request.get(id);
62
+ try {
63
+ return await this.httpService.request.get(id);
64
+ } catch (error) {
65
+ throw new CollectionError('getOne', this.collectionName, error as Error);
66
+ }
45
67
  }
46
68
 
47
69
  async create(data: Record<string, any>): Promise<any> {
48
- return this.httpService.request.withJSON(data).post(this.collectionName);
70
+ try {
71
+ return await this.httpService.request.withJSON(data).post(this.collectionName);
72
+ } catch (error) {
73
+ throw new CollectionError('create', this.collectionName, error as Error);
74
+ }
49
75
  }
50
76
 
51
77
  async update(id: string, data: Record<string, any>): Promise<any> {
52
78
  if (!id) {
53
79
  throw new Error("ID must be provided");
54
80
  }
55
- return this.httpService.request.withJSON(data).put(id);
81
+ try {
82
+ return await this.httpService.request.withJSON(data).put(id);
83
+ } catch (error) {
84
+ throw new CollectionError('update', this.collectionName, error as Error);
85
+ }
56
86
  }
57
87
 
58
88
  async delete(id: string): Promise<any> {
59
89
  if (!id) {
60
90
  throw new Error("ID must be provided");
61
91
  }
62
- return this.httpService.request.delete(id);
92
+ try {
93
+ return await this.httpService.request.delete(id);
94
+ } catch (error) {
95
+ throw new CollectionError('delete', this.collectionName, error as Error);
96
+ }
63
97
  }
64
98
  }
package/src/sdk-ts.ts CHANGED
@@ -174,7 +174,7 @@ export class SDK {
174
174
 
175
175
 
176
176
  const PORT = await getEnvPort();
177
- // console.log("Using port:", PORT);
177
+ console.log("Port:", PORT);
178
178
 
179
179
  if (!config.httpUrl) {
180
180
  config.httpUrl = "http://localhost:" + PORT;
@@ -190,7 +190,7 @@ export class SDK {
190
190
 
191
191
  const { application_id, httpUrl, mqttUrl, natsUrl } = config;
192
192
 
193
- // console.log("Final config:", { application_id, httpUrl, mqttUrl, natsUrl });
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
195
  this.http = new HTTPClient(application_id, httpUrl, config.developer_key);
196
196
  this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
@@ -275,7 +275,9 @@ export class SDK {
275
275
 
276
276
  async function getEnvPort(): Promise<string> {
277
277
  try {
278
- const response = await fetchData<{runtimeID:string, port:string}>(window.location.origin + "/_cfg");
278
+ // Try to find the configuration endpoint by testing different base paths
279
+ const configUrl = await findConfigEndpoint();
280
+ const response = await fetchData<{runtimeID:string, port:string}>(configUrl);
279
281
  // console.log('Response:', response);
280
282
  // console.log('runtimeID: ', response.runtimeID);
281
283
  // console.log('applicationID: ', response.runtimeID.split('XmchX')[0]);
@@ -287,14 +289,101 @@ async function getEnvPort(): Promise<string> {
287
289
  }
288
290
  }
289
291
 
292
+ /**
293
+ * Attempts to find the correct configuration endpoint by trying different base paths
294
+ * Handles both port-based hosting (direct) and path-based hosting (reverse proxy)
295
+ */
296
+ async function findConfigEndpoint(): Promise<string> {
297
+ const origin = window.location.origin;
298
+ const pathname = window.location.pathname;
299
+
300
+ // List of potential base paths to try, ordered by likelihood
301
+ const basePaths = [
302
+ // 1. Try origin directly (for port-based hosting like localhost:6190)
303
+ origin,
304
+
305
+ // 2. Try current path segments for path-based hosting
306
+ ...generatePathCandidates(pathname),
307
+
308
+ // 3. Try common root paths as fallback
309
+ origin,
310
+ ];
311
+
312
+ // Remove duplicates while preserving order
313
+ const uniqueBasePaths = [...new Set(basePaths)];
314
+
315
+ for (const basePath of uniqueBasePaths) {
316
+ try {
317
+ const configUrl = `${basePath}/_cfg`;
318
+
319
+ // Test if this endpoint returns valid JSON config by making a GET request
320
+ const testResponse = await fetch(configUrl, {
321
+ method: 'GET',
322
+ headers: {
323
+ 'Accept': 'application/json',
324
+ },
325
+ signal: AbortSignal.timeout(2000) // 2 second timeout
326
+ });
327
+
328
+ if (testResponse.ok) {
329
+ // Validate that the response is JSON and contains the expected 'port' field
330
+ const contentType = testResponse.headers.get('content-type');
331
+ if (contentType && contentType.includes('application/json')) {
332
+ try {
333
+ const testData = await testResponse.json();
334
+ // Check if the response has the expected structure with a 'port' field
335
+ if (testData && typeof testData === 'object' && 'port' in testData) {
336
+ // console.log(`Found config endpoint at: ${configUrl}`);
337
+ return configUrl;
338
+ }
339
+ } catch (jsonError) {
340
+ // Not valid JSON, continue to next candidate
341
+ continue;
342
+ }
343
+ }
344
+ }
345
+ } catch (error) {
346
+ // Continue to next candidate
347
+ continue;
348
+ }
349
+ }
350
+
351
+ // If all attempts fail, default to origin + /_cfg
352
+ console.warn('Could not find config endpoint, using default origin');
353
+ return `${origin}/_cfg`;
354
+ }
355
+
356
+ /**
357
+ * Generates potential base path candidates from the current pathname
358
+ * For example, /demo2/homepage/settings would generate:
359
+ * - http://localhost/demo2/homepage
360
+ * - http://localhost/demo2
361
+ * - http://localhost
362
+ */
363
+ function generatePathCandidates(pathname: string): string[] {
364
+ const origin = window.location.origin;
365
+ const pathSegments = pathname.split('/').filter(segment => segment.length > 0);
366
+ const candidates: string[] = [];
367
+
368
+ // Generate paths by progressively removing segments from the end
369
+ for (let i = pathSegments.length; i > 0; i--) {
370
+ const path = '/' + pathSegments.slice(0, i).join('/');
371
+ candidates.push(origin + path);
372
+ }
373
+
374
+ return candidates;
375
+ }
376
+
290
377
 
291
- async function fetchData<T>(url: string): Promise<T> {
378
+ async function fetchData<T>(url: string, options?: RequestInit): Promise<T> {
292
379
  try {
293
380
  const response = await fetch(url, {
294
381
  method: 'GET',
295
382
  headers: {
296
383
  'Accept': 'application/json',
297
384
  },
385
+ signal: AbortSignal.timeout(5000), // 5 second timeout
386
+ ...options
298
387
  });
299
388
 
300
389
  if (!response.ok) {
@@ -50,7 +50,7 @@ export class MQTTService {
50
50
  try {
51
51
  this.subscribedTopics.push({ topic, handler });
52
52
  if (topic == "") return;
53
- console.log("New Subscription Handler:", topic);
53
+ // console.log("New Subscription Handler:", topic);
54
54
  this.client.subscribe(topic, { qos: 2 }, (err?: unknown) => {
55
55
  if (err) {
56
56
  console.error(`Failed to subscribe to topic ${topic}:`, err);
@@ -70,7 +70,7 @@ export class MQTTService {
70
70
  public publish(topic: string, message: unknown): boolean {
71
71
  try {
72
72
  const payload = JSON.stringify(message);
73
- console.log("Publishing to", topic, "with payload:", payload);
73
+ // console.log("Publishing to", topic, "with payload:", payload);
74
74
 
75
75
  this.client.publish(topic, payload, {
76
76
  qos: 2,