@machhub-dev/sdk-ts 0.0.10 → 0.0.12

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.
@@ -2,10 +2,16 @@ import { HTTPService } from "../services/http.service.js";
2
2
  import { Action, ActionResponse, Feature, Group, LoginResponse, User, ValidateJWTResponse } from "../types/auth.models.js";
3
3
  export declare class Auth {
4
4
  private httpService;
5
- constructor(httpService: HTTPService);
5
+ private applicationID;
6
+ private readonly AUTH_TOKEN_KEY_PREFIX;
7
+ constructor(httpService: HTTPService, applicationID: string);
8
+ private getStorageKey;
6
9
  login(username: string, password: string): Promise<LoginResponse | undefined>;
7
10
  validateJWT(token: string): Promise<ValidateJWTResponse>;
8
11
  logout(): Promise<void>;
12
+ getJWTData(): Promise<any>;
13
+ getCurrentUser(): Promise<User>;
14
+ validateCurrentUser(): Promise<ValidateJWTResponse>;
9
15
  checkAction(feature: string, scope: string): Promise<ActionResponse>;
10
16
  checkPermission(feature: string, scope: string, action: Action): Promise<ActionResponse>;
11
17
  getUsers(): Promise<User[]>;
@@ -1,9 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Auth = void 0;
4
+ const jwt_decode_1 = require("jwt-decode");
4
5
  class Auth {
5
- constructor(httpService) {
6
+ constructor(httpService, applicationID) {
7
+ this.AUTH_TOKEN_KEY_PREFIX = "x-machhub-auth-tkn";
6
8
  this.httpService = httpService;
9
+ this.applicationID = applicationID;
10
+ }
11
+ getStorageKey() {
12
+ return this.applicationID
13
+ ? `${this.AUTH_TOKEN_KEY_PREFIX}-${this.applicationID}`
14
+ : this.AUTH_TOKEN_KEY_PREFIX;
7
15
  }
8
16
  async login(username, password) {
9
17
  let res;
@@ -13,7 +21,8 @@ class Auth {
13
21
  password: password,
14
22
  }).post("/auth/login");
15
23
  if (localStorage) {
16
- localStorage.setItem("x-machhub-auth-tkn", res.tkn); // Set User JWT
24
+ // console.log("storage key:", this.getStorageKey());
25
+ localStorage.setItem(this.getStorageKey(), res.tkn); // Set User JWT
17
26
  }
18
27
  else {
19
28
  console.error("localStorage is not available. The program needs to be in a browser environment.");
@@ -28,11 +37,27 @@ class Auth {
28
37
  }
29
38
  }
30
39
  async validateJWT(token) {
31
- let res = await this.httpService.request.withJSON({ token }).post("/auth/jwt/validate");
32
- return res;
40
+ return await this.httpService.request.withJSON({ token }).post("/auth/jwt/validate");
33
41
  }
34
42
  async logout() {
35
- localStorage.removeItem("x-machhub-auth-tkn"); // Remove User JWT
43
+ localStorage.removeItem(this.getStorageKey());
44
+ }
45
+ async getJWTData() {
46
+ const token = localStorage.getItem(this.getStorageKey());
47
+ if (!token) {
48
+ throw new Error("No JWT token found in localStorage.");
49
+ }
50
+ return (0, jwt_decode_1.jwtDecode)(token);
51
+ }
52
+ async getCurrentUser() {
53
+ return await this.httpService.request.get("/auth/me");
54
+ }
55
+ async validateCurrentUser() {
56
+ const token = localStorage.getItem(this.getStorageKey());
57
+ if (!token) {
58
+ throw new Error("No JWT token found in localStorage.");
59
+ }
60
+ return await this.validateJWT(token);
36
61
  }
37
62
  async checkAction(feature, scope) {
38
63
  try {
@@ -16,7 +16,12 @@ export declare class Collection {
16
16
  sort(field: string, direction?: "asc" | "desc"): Collection;
17
17
  limit(limit: number): Collection;
18
18
  offset(offset: number): Collection;
19
- getAll(): Promise<any[]>;
19
+ expand(fields: string | string[]): Collection;
20
+ private applyOptions;
21
+ first(): Promise<any>;
22
+ getAll(options?: {
23
+ expand?: string | string[];
24
+ }): Promise<any[]>;
20
25
  getOne(id: string): Promise<any>;
21
26
  create(data: Record<string, any>): Promise<any>;
22
27
  update(id: string, data: Record<string, any>): Promise<any>;
@@ -34,8 +34,29 @@ class Collection {
34
34
  this.queryParams.offset = offset;
35
35
  return this;
36
36
  }
37
- async getAll() {
37
+ expand(fields) {
38
+ this.queryParams.expand = Array.isArray(fields) ? fields.join(",") : fields;
39
+ return this;
40
+ }
41
+ applyOptions(options) {
42
+ if (!options)
43
+ return;
44
+ for (const [key, value] of Object.entries(options)) {
45
+ if (value !== undefined && value !== null) {
46
+ this.queryParams[key] = value;
47
+ }
48
+ }
49
+ }
50
+ async first() {
51
+ const results = await this.limit(1).getAll();
52
+ return results[0] ?? null;
53
+ }
54
+ async getAll(options) {
38
55
  try {
56
+ this.applyOptions(options);
57
+ if (options?.expand) {
58
+ this.queryParams.expand = Array.isArray(options.expand) ? options.expand.join() : options.expand;
59
+ }
39
60
  return await this.httpService.request.get(this.collectionName + "/all", this.queryParams);
40
61
  }
41
62
  catch (error) {
@@ -20,6 +20,14 @@ export declare class SDK {
20
20
  private _function;
21
21
  private _flow;
22
22
  private _auth;
23
+ private applicationID;
24
+ /**
25
+ * Extracts the application ID from a runtime ID.
26
+ * Runtime ID format: {applicationID}XmchX{randomID}
27
+ * @param runtimeID The runtime ID to extract from
28
+ * @returns The extracted application ID, or empty string if invalid format
29
+ */
30
+ private extractApplicationIDFromRuntimeID;
23
31
  /**
24
32
  * Initializes the SDK with the required clients.
25
33
  *
@@ -113,6 +113,19 @@ class SDK {
113
113
  this._function = null;
114
114
  this._flow = null;
115
115
  this._auth = null;
116
+ this.applicationID = "";
117
+ }
118
+ /**
119
+ * Extracts the application ID from a runtime ID.
120
+ * Runtime ID format: {applicationID}XmchX{randomID}
121
+ * @param runtimeID The runtime ID to extract from
122
+ * @returns The extracted application ID, or empty string if invalid format
123
+ */
124
+ extractApplicationIDFromRuntimeID(runtimeID) {
125
+ if (!runtimeID)
126
+ return "";
127
+ const parts = runtimeID.split('XmchX');
128
+ return parts.length > 0 ? parts[0] : "";
116
129
  }
117
130
  /**
118
131
  * Initializes the SDK with the required clients.
@@ -146,9 +159,11 @@ class SDK {
146
159
  config = { application_id: "" };
147
160
  if (!config.application_id)
148
161
  config = { application_id: "" };
149
- // console.log("Using application_id:", config.application_id);
150
162
  const envCfg = await getEnvConfig();
151
- // console.log("Environment Config:", envCfg);
163
+ // Extract application_id from runtimeID if not provided in config
164
+ const application_id = config.application_id ||
165
+ this.extractApplicationIDFromRuntimeID(envCfg.runtimeID);
166
+ this.applicationID = application_id;
152
167
  // Determine the hostname - use window.location.hostname in browser, otherwise fallback to localhost
153
168
  const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
154
169
  const secured = typeof window !== 'undefined' ? window.location.protocol === 'https:' : false;
@@ -168,16 +183,16 @@ class SDK {
168
183
  if (!config.natsUrl) {
169
184
  config.natsUrl = `${secured ? 'wss' : 'ws'}://${host}/nats`;
170
185
  }
171
- const { application_id, httpUrl, mqttUrl, natsUrl } = config;
172
- 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('') });
173
- this.http = new HTTPClient(application_id, httpUrl, config.developer_key, envCfg.runtimeID);
174
- this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
175
- this.nats = await NATSClient.getInstance(application_id, natsUrl);
186
+ const { httpUrl, mqttUrl, natsUrl } = config;
187
+ console.log("SDK Config:", { application_id: this.applicationID, httpUrl, mqttUrl, natsUrl, developer_key: config.developer_key?.split('').map((_, i) => i < config.developer_key.length - 4 ? '*' : config.developer_key[i]).join('') });
188
+ this.http = new HTTPClient(this.applicationID, httpUrl, config.developer_key, envCfg.runtimeID);
189
+ this.mqtt = await MQTTClient.getInstance(this.applicationID, mqttUrl, config.developer_key);
190
+ this.nats = await NATSClient.getInstance(this.applicationID, natsUrl);
176
191
  this._historian = new historian_js_1.Historian(this.http["httpService"], this.mqtt["mqttService"]);
177
192
  this._tag = new tag_js_1.Tag(this.http["httpService"], this.mqtt["mqttService"]);
178
193
  this._function = new function_js_1.Function(this.http["httpService"], this.nats["natsService"]);
179
194
  this._flow = new flow_js_1.Flow(this.http["httpService"]);
180
- this._auth = new auth_js_1.Auth(this.http["httpService"]);
195
+ this._auth = new auth_js_1.Auth(this.http["httpService"], this.applicationID);
181
196
  }
182
197
  catch (error) {
183
198
  console.error("Failed to initialize:", error);
@@ -286,26 +301,27 @@ async function findConfigEndpoint() {
286
301
  headers: {
287
302
  'Accept': 'application/json',
288
303
  },
289
- signal: AbortSignal.timeout(2000) // 2 second timeout
290
- });
291
- if (testResponse.ok) {
292
- // Validate that the response is JSON and contains the expected 'port' field
293
- const contentType = testResponse.headers.get('content-type');
294
- if (contentType && contentType.includes('application/json')) {
295
- try {
296
- const testData = await testResponse.json();
297
- // Check if the response has the expected structure with a 'port' field
298
- // TODO: Allow checks for path based hosting as well
299
- if (testData && typeof testData === 'object' && 'port' in testData) {
300
- // console.log(`Found config endpoint at: ${configUrl}`);
301
- return configUrl;
302
- }
303
- }
304
- catch (jsonError) {
305
- // Not valid JSON, continue to next candidate
306
- continue;
304
+ signal: AbortSignal.timeout(2000)
305
+ }).catch(() => { console.log("ERR"); return null; }); // Catch fetch errors silently
306
+ if (!testResponse || !testResponse.ok) {
307
+ continue; // Skip to next candidate
308
+ }
309
+ // Validate that the response is JSON and contains the expected 'port' field
310
+ const contentType = testResponse.headers.get('content-type');
311
+ if (contentType && contentType.includes('application/json')) {
312
+ try {
313
+ const testData = await testResponse.json();
314
+ // Check if the response has the expected structure with a 'port' field
315
+ // TODO: Allow checks for path based hosting as well
316
+ if (testData && typeof testData === 'object' && 'port' in testData) {
317
+ // console.log(`Found config endpoint at: ${configUrl}`);
318
+ return configUrl;
307
319
  }
308
320
  }
321
+ catch (jsonError) {
322
+ // Not valid JSON, continue to next candidate
323
+ continue;
324
+ }
309
325
  }
310
326
  }
311
327
  catch (error) {
@@ -117,8 +117,13 @@ class RequestParameters {
117
117
  return this;
118
118
  }
119
119
  withAccessToken() {
120
- const tkn = localStorage.getItem("x-machhub-auth-tkn");
121
- this.setHeader("Authorization", `Bearer ${tkn}`);
120
+ const rawAppID = this.applicationID.replace("domains:", "");
121
+ const storageKey = rawAppID
122
+ ? `x-machhub-auth-tkn-${rawAppID}`
123
+ : `x-machhub-auth-tkn`;
124
+ const tkn = localStorage.getItem(storageKey);
125
+ if (tkn)
126
+ this.setHeader("Authorization", `Bearer ${tkn}`);
122
127
  return this;
123
128
  }
124
129
  withRuntimeID() {
@@ -2,10 +2,16 @@ import { HTTPService } from "../services/http.service.js";
2
2
  import { Action, ActionResponse, Feature, Group, LoginResponse, User, ValidateJWTResponse } from "../types/auth.models.js";
3
3
  export declare class Auth {
4
4
  private httpService;
5
- constructor(httpService: HTTPService);
5
+ private applicationID;
6
+ private readonly AUTH_TOKEN_KEY_PREFIX;
7
+ constructor(httpService: HTTPService, applicationID: string);
8
+ private getStorageKey;
6
9
  login(username: string, password: string): Promise<LoginResponse | undefined>;
7
10
  validateJWT(token: string): Promise<ValidateJWTResponse>;
8
11
  logout(): Promise<void>;
12
+ getJWTData(): Promise<any>;
13
+ getCurrentUser(): Promise<User>;
14
+ validateCurrentUser(): Promise<ValidateJWTResponse>;
9
15
  checkAction(feature: string, scope: string): Promise<ActionResponse>;
10
16
  checkPermission(feature: string, scope: string, action: Action): Promise<ActionResponse>;
11
17
  getUsers(): Promise<User[]>;
@@ -1,6 +1,14 @@
1
+ import { jwtDecode } from "jwt-decode";
1
2
  export class Auth {
2
- constructor(httpService) {
3
+ constructor(httpService, applicationID) {
4
+ this.AUTH_TOKEN_KEY_PREFIX = "x-machhub-auth-tkn";
3
5
  this.httpService = httpService;
6
+ this.applicationID = applicationID;
7
+ }
8
+ getStorageKey() {
9
+ return this.applicationID
10
+ ? `${this.AUTH_TOKEN_KEY_PREFIX}-${this.applicationID}`
11
+ : this.AUTH_TOKEN_KEY_PREFIX;
4
12
  }
5
13
  async login(username, password) {
6
14
  let res;
@@ -10,7 +18,8 @@ export class Auth {
10
18
  password: password,
11
19
  }).post("/auth/login");
12
20
  if (localStorage) {
13
- localStorage.setItem("x-machhub-auth-tkn", res.tkn); // Set User JWT
21
+ // console.log("storage key:", this.getStorageKey());
22
+ localStorage.setItem(this.getStorageKey(), res.tkn); // Set User JWT
14
23
  }
15
24
  else {
16
25
  console.error("localStorage is not available. The program needs to be in a browser environment.");
@@ -25,11 +34,27 @@ export class Auth {
25
34
  }
26
35
  }
27
36
  async validateJWT(token) {
28
- let res = await this.httpService.request.withJSON({ token }).post("/auth/jwt/validate");
29
- return res;
37
+ return await this.httpService.request.withJSON({ token }).post("/auth/jwt/validate");
30
38
  }
31
39
  async logout() {
32
- localStorage.removeItem("x-machhub-auth-tkn"); // Remove User JWT
40
+ localStorage.removeItem(this.getStorageKey());
41
+ }
42
+ async getJWTData() {
43
+ const token = localStorage.getItem(this.getStorageKey());
44
+ if (!token) {
45
+ throw new Error("No JWT token found in localStorage.");
46
+ }
47
+ return jwtDecode(token);
48
+ }
49
+ async getCurrentUser() {
50
+ return await this.httpService.request.get("/auth/me");
51
+ }
52
+ async validateCurrentUser() {
53
+ const token = localStorage.getItem(this.getStorageKey());
54
+ if (!token) {
55
+ throw new Error("No JWT token found in localStorage.");
56
+ }
57
+ return await this.validateJWT(token);
33
58
  }
34
59
  async checkAction(feature, scope) {
35
60
  try {
@@ -16,7 +16,12 @@ export declare class Collection {
16
16
  sort(field: string, direction?: "asc" | "desc"): Collection;
17
17
  limit(limit: number): Collection;
18
18
  offset(offset: number): Collection;
19
- getAll(): Promise<any[]>;
19
+ expand(fields: string | string[]): Collection;
20
+ private applyOptions;
21
+ first(): Promise<any>;
22
+ getAll(options?: {
23
+ expand?: string | string[];
24
+ }): Promise<any[]>;
20
25
  getOne(id: string): Promise<any>;
21
26
  create(data: Record<string, any>): Promise<any>;
22
27
  update(id: string, data: Record<string, any>): Promise<any>;
@@ -30,8 +30,29 @@ export class Collection {
30
30
  this.queryParams.offset = offset;
31
31
  return this;
32
32
  }
33
- async getAll() {
33
+ expand(fields) {
34
+ this.queryParams.expand = Array.isArray(fields) ? fields.join(",") : fields;
35
+ return this;
36
+ }
37
+ applyOptions(options) {
38
+ if (!options)
39
+ return;
40
+ for (const [key, value] of Object.entries(options)) {
41
+ if (value !== undefined && value !== null) {
42
+ this.queryParams[key] = value;
43
+ }
44
+ }
45
+ }
46
+ async first() {
47
+ const results = await this.limit(1).getAll();
48
+ return results[0] ?? null;
49
+ }
50
+ async getAll(options) {
34
51
  try {
52
+ this.applyOptions(options);
53
+ if (options?.expand) {
54
+ this.queryParams.expand = Array.isArray(options.expand) ? options.expand.join() : options.expand;
55
+ }
35
56
  return await this.httpService.request.get(this.collectionName + "/all", this.queryParams);
36
57
  }
37
58
  catch (error) {
package/dist/sdk-ts.d.ts CHANGED
@@ -20,6 +20,14 @@ export declare class SDK {
20
20
  private _function;
21
21
  private _flow;
22
22
  private _auth;
23
+ private applicationID;
24
+ /**
25
+ * Extracts the application ID from a runtime ID.
26
+ * Runtime ID format: {applicationID}XmchX{randomID}
27
+ * @param runtimeID The runtime ID to extract from
28
+ * @returns The extracted application ID, or empty string if invalid format
29
+ */
30
+ private extractApplicationIDFromRuntimeID;
23
31
  /**
24
32
  * Initializes the SDK with the required clients.
25
33
  *
package/dist/sdk-ts.js CHANGED
@@ -110,6 +110,19 @@ export class SDK {
110
110
  this._function = null;
111
111
  this._flow = null;
112
112
  this._auth = null;
113
+ this.applicationID = "";
114
+ }
115
+ /**
116
+ * Extracts the application ID from a runtime ID.
117
+ * Runtime ID format: {applicationID}XmchX{randomID}
118
+ * @param runtimeID The runtime ID to extract from
119
+ * @returns The extracted application ID, or empty string if invalid format
120
+ */
121
+ extractApplicationIDFromRuntimeID(runtimeID) {
122
+ if (!runtimeID)
123
+ return "";
124
+ const parts = runtimeID.split('XmchX');
125
+ return parts.length > 0 ? parts[0] : "";
113
126
  }
114
127
  /**
115
128
  * Initializes the SDK with the required clients.
@@ -143,9 +156,11 @@ export class SDK {
143
156
  config = { application_id: "" };
144
157
  if (!config.application_id)
145
158
  config = { application_id: "" };
146
- // console.log("Using application_id:", config.application_id);
147
159
  const envCfg = await getEnvConfig();
148
- // console.log("Environment Config:", envCfg);
160
+ // Extract application_id from runtimeID if not provided in config
161
+ const application_id = config.application_id ||
162
+ this.extractApplicationIDFromRuntimeID(envCfg.runtimeID);
163
+ this.applicationID = application_id;
149
164
  // Determine the hostname - use window.location.hostname in browser, otherwise fallback to localhost
150
165
  const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
151
166
  const secured = typeof window !== 'undefined' ? window.location.protocol === 'https:' : false;
@@ -165,16 +180,16 @@ export class SDK {
165
180
  if (!config.natsUrl) {
166
181
  config.natsUrl = `${secured ? 'wss' : 'ws'}://${host}/nats`;
167
182
  }
168
- const { application_id, httpUrl, mqttUrl, natsUrl } = config;
169
- 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('') });
170
- this.http = new HTTPClient(application_id, httpUrl, config.developer_key, envCfg.runtimeID);
171
- this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
172
- this.nats = await NATSClient.getInstance(application_id, natsUrl);
183
+ const { httpUrl, mqttUrl, natsUrl } = config;
184
+ console.log("SDK Config:", { application_id: this.applicationID, httpUrl, mqttUrl, natsUrl, developer_key: config.developer_key?.split('').map((_, i) => i < config.developer_key.length - 4 ? '*' : config.developer_key[i]).join('') });
185
+ this.http = new HTTPClient(this.applicationID, httpUrl, config.developer_key, envCfg.runtimeID);
186
+ this.mqtt = await MQTTClient.getInstance(this.applicationID, mqttUrl, config.developer_key);
187
+ this.nats = await NATSClient.getInstance(this.applicationID, natsUrl);
173
188
  this._historian = new Historian(this.http["httpService"], this.mqtt["mqttService"]);
174
189
  this._tag = new Tag(this.http["httpService"], this.mqtt["mqttService"]);
175
190
  this._function = new Function(this.http["httpService"], this.nats["natsService"]);
176
191
  this._flow = new Flow(this.http["httpService"]);
177
- this._auth = new Auth(this.http["httpService"]);
192
+ this._auth = new Auth(this.http["httpService"], this.applicationID);
178
193
  }
179
194
  catch (error) {
180
195
  console.error("Failed to initialize:", error);
@@ -282,26 +297,27 @@ async function findConfigEndpoint() {
282
297
  headers: {
283
298
  'Accept': 'application/json',
284
299
  },
285
- signal: AbortSignal.timeout(2000) // 2 second timeout
286
- });
287
- if (testResponse.ok) {
288
- // Validate that the response is JSON and contains the expected 'port' field
289
- const contentType = testResponse.headers.get('content-type');
290
- if (contentType && contentType.includes('application/json')) {
291
- try {
292
- const testData = await testResponse.json();
293
- // Check if the response has the expected structure with a 'port' field
294
- // TODO: Allow checks for path based hosting as well
295
- if (testData && typeof testData === 'object' && 'port' in testData) {
296
- // console.log(`Found config endpoint at: ${configUrl}`);
297
- return configUrl;
298
- }
299
- }
300
- catch (jsonError) {
301
- // Not valid JSON, continue to next candidate
302
- continue;
300
+ signal: AbortSignal.timeout(2000)
301
+ }).catch(() => { console.log("ERR"); return null; }); // Catch fetch errors silently
302
+ if (!testResponse || !testResponse.ok) {
303
+ continue; // Skip to next candidate
304
+ }
305
+ // Validate that the response is JSON and contains the expected 'port' field
306
+ const contentType = testResponse.headers.get('content-type');
307
+ if (contentType && contentType.includes('application/json')) {
308
+ try {
309
+ const testData = await testResponse.json();
310
+ // Check if the response has the expected structure with a 'port' field
311
+ // TODO: Allow checks for path based hosting as well
312
+ if (testData && typeof testData === 'object' && 'port' in testData) {
313
+ // console.log(`Found config endpoint at: ${configUrl}`);
314
+ return configUrl;
303
315
  }
304
316
  }
317
+ catch (jsonError) {
318
+ // Not valid JSON, continue to next candidate
319
+ continue;
320
+ }
305
321
  }
306
322
  }
307
323
  catch (error) {
@@ -112,8 +112,13 @@ class RequestParameters {
112
112
  return this;
113
113
  }
114
114
  withAccessToken() {
115
- const tkn = localStorage.getItem("x-machhub-auth-tkn");
116
- this.setHeader("Authorization", `Bearer ${tkn}`);
115
+ const rawAppID = this.applicationID.replace("domains:", "");
116
+ const storageKey = rawAppID
117
+ ? `x-machhub-auth-tkn-${rawAppID}`
118
+ : `x-machhub-auth-tkn`;
119
+ const tkn = localStorage.getItem(storageKey);
120
+ if (tkn)
121
+ this.setHeader("Authorization", `Bearer ${tkn}`);
117
122
  return this;
118
123
  }
119
124
  withRuntimeID() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@machhub-dev/sdk-ts",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "MACHHUB TYPESCRIPT SDK",
5
5
  "keywords": [
6
6
  "machhub",
@@ -22,6 +22,7 @@
22
22
  "dependencies": {
23
23
  "@nats-io/nats-core": "^3.0.2",
24
24
  "@nats-io/transport-node": "^3.0.2",
25
+ "jwt-decode": "^4.0.0",
25
26
  "mqtt": "^5.10.4",
26
27
  "safe-buffer": "^5.2.1",
27
28
  "typescript": "^5.8.3",
@@ -1,11 +1,21 @@
1
1
  import { HTTPService } from "../services/http.service.js";
2
+ import { jwtDecode } from "jwt-decode";
2
3
  import { Action, ActionResponse, Feature, Group, LoginResponse, User, ValidateJWTResponse } from "../types/auth.models.js";
3
4
 
4
5
  export class Auth {
5
6
  private httpService: HTTPService;
7
+ private applicationID: string;
8
+ private readonly AUTH_TOKEN_KEY_PREFIX = "x-machhub-auth-tkn";
6
9
 
7
- constructor(httpService: HTTPService) {
10
+ constructor(httpService: HTTPService, applicationID: string) {
8
11
  this.httpService = httpService;
12
+ this.applicationID = applicationID;
13
+ }
14
+
15
+ private getStorageKey(): string {
16
+ return this.applicationID
17
+ ? `${this.AUTH_TOKEN_KEY_PREFIX}-${this.applicationID}`
18
+ : this.AUTH_TOKEN_KEY_PREFIX;
9
19
  }
10
20
 
11
21
  public async login(username: string, password: string): Promise<LoginResponse | undefined> {
@@ -17,7 +27,8 @@ export class Auth {
17
27
  }).post("/auth/login");
18
28
 
19
29
  if (localStorage) {
20
- localStorage.setItem("x-machhub-auth-tkn", res.tkn); // Set User JWT
30
+ // console.log("storage key:", this.getStorageKey());
31
+ localStorage.setItem(this.getStorageKey(), res.tkn); // Set User JWT
21
32
  } else {
22
33
  console.error("localStorage is not available. The program needs to be in a browser environment.");
23
34
  }
@@ -31,13 +42,34 @@ export class Auth {
31
42
  }
32
43
  }
33
44
 
34
- public async validateJWT(token: string): Promise<ValidateJWTResponse>{
35
- let res:ValidateJWTResponse = await this.httpService.request.withJSON({ token }).post("/auth/jwt/validate");
36
- return res
45
+ public async validateJWT(token: string): Promise<ValidateJWTResponse> {
46
+ return await this.httpService.request.withJSON({ token }).post("/auth/jwt/validate");
37
47
  }
38
48
 
39
49
  public async logout() {
40
- localStorage.removeItem("x-machhub-auth-tkn"); // Remove User JWT
50
+ localStorage.removeItem(this.getStorageKey());
51
+ }
52
+
53
+ public async getJWTData(): Promise<any> {
54
+ const token = localStorage.getItem(this.getStorageKey());
55
+ if (!token) {
56
+ throw new Error("No JWT token found in localStorage.");
57
+ }
58
+
59
+ return jwtDecode(token);
60
+ }
61
+
62
+ public async getCurrentUser(): Promise<User> {
63
+ return await this.httpService.request.get("/auth/me");
64
+ }
65
+
66
+ public async validateCurrentUser(): Promise<ValidateJWTResponse> {
67
+ const token = localStorage.getItem(this.getStorageKey());
68
+ if (!token) {
69
+ throw new Error("No JWT token found in localStorage.");
70
+ }
71
+
72
+ return await this.validateJWT(token);
41
73
  }
42
74
 
43
75
  public async checkAction(feature: string, scope: string): Promise<ActionResponse> {
@@ -48,8 +48,32 @@ export class Collection {
48
48
  return this;
49
49
  }
50
50
 
51
- async getAll(): Promise<any[]> {
51
+ expand(fields: string | string[]): Collection {
52
+ this.queryParams.expand = Array.isArray(fields) ? fields.join(",") : fields;
53
+ return this;
54
+ }
55
+
56
+ private applyOptions(options?: Record<string, any>) {
57
+ if (!options) return;
58
+
59
+ for (const [key, value] of Object.entries(options)) {
60
+ if (value !== undefined && value !== null) {
61
+ this.queryParams[key] = value;
62
+ }
63
+ }
64
+ }
65
+
66
+ async first(): Promise<any> {
67
+ const results = await this.limit(1).getAll();
68
+ return results[0] ?? null
69
+ }
70
+
71
+ async getAll(options?: { expand?: string | string[] }): Promise<any[]> {
52
72
  try {
73
+ this.applyOptions(options)
74
+ if (options?.expand) {
75
+ this.queryParams.expand = Array.isArray(options.expand) ? options.expand.join() : options.expand
76
+ }
53
77
  return await this.httpService.request.get(this.collectionName + "/all", this.queryParams);
54
78
  } catch (error) {
55
79
  throw new CollectionError('getAll', this.collectionName, error as Error);
package/src/sdk-ts.ts CHANGED
@@ -137,6 +137,19 @@ export class SDK {
137
137
  private _function: Function | null = null;
138
138
  private _flow: Flow | null = null;
139
139
  private _auth: Auth | null = null;
140
+ private applicationID: string = "";
141
+
142
+ /**
143
+ * Extracts the application ID from a runtime ID.
144
+ * Runtime ID format: {applicationID}XmchX{randomID}
145
+ * @param runtimeID The runtime ID to extract from
146
+ * @returns The extracted application ID, or empty string if invalid format
147
+ */
148
+ private extractApplicationIDFromRuntimeID(runtimeID: string): string {
149
+ if (!runtimeID) return "";
150
+ const parts = runtimeID.split('XmchX');
151
+ return parts.length > 0 ? parts[0] : "";
152
+ }
140
153
 
141
154
  /**
142
155
  * Initializes the SDK with the required clients.
@@ -167,13 +180,16 @@ export class SDK {
167
180
  // 1. Via application_id + URLs + developer key passed in config parameter
168
181
  // 2. Via development server - Set via Extension/
169
182
  // API to get Config (All SDK URLs default to localhost with port from querying current window or 61888)
170
-
171
183
  if (config === undefined) config = { application_id: "" }
172
184
  if (!config.application_id) config = { application_id: "" }
173
- // console.log("Using application_id:", config.application_id);
174
185
 
175
186
  const envCfg = await getEnvConfig();
176
- // console.log("Environment Config:", envCfg);
187
+
188
+ // Extract application_id from runtimeID if not provided in config
189
+ const application_id = config.application_id ||
190
+ this.extractApplicationIDFromRuntimeID(envCfg.runtimeID);
191
+
192
+ this.applicationID = application_id;
177
193
 
178
194
  // Determine the hostname - use window.location.hostname in browser, otherwise fallback to localhost
179
195
  const hostname = typeof window !== 'undefined' ? window.location.hostname : 'localhost';
@@ -197,19 +213,20 @@ export class SDK {
197
213
  if (!config.natsUrl) {
198
214
  config.natsUrl = `${secured ? 'wss' : 'ws'}://${host}/nats`;
199
215
  }
200
- const { application_id, httpUrl, mqttUrl, natsUrl } = config;
216
+
217
+ const { httpUrl, mqttUrl, natsUrl } = config;
201
218
 
202
- 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('') });
219
+ console.log("SDK Config:", { application_id: this.applicationID, httpUrl, mqttUrl, natsUrl, developer_key: config.developer_key?.split('').map((_, i) => i < config!.developer_key!.length - 4 ? '*' : config!.developer_key![i]).join('') });
203
220
 
204
- this.http = new HTTPClient(application_id, httpUrl, config.developer_key, envCfg.runtimeID);
205
- this.mqtt = await MQTTClient.getInstance(application_id, mqttUrl, config.developer_key);
206
- this.nats = await NATSClient.getInstance(application_id, natsUrl);
221
+ this.http = new HTTPClient(this.applicationID, httpUrl, config.developer_key, envCfg.runtimeID);
222
+ this.mqtt = await MQTTClient.getInstance(this.applicationID, mqttUrl, config.developer_key);
223
+ this.nats = await NATSClient.getInstance(this.applicationID, natsUrl);
207
224
 
208
225
  this._historian = new Historian(this.http["httpService"], this.mqtt["mqttService"]);
209
226
  this._tag = new Tag(this.http["httpService"], this.mqtt["mqttService"]);
210
227
  this._function = new Function(this.http["httpService"], this.nats["natsService"]);
211
228
  this._flow = new Flow(this.http["httpService"]);
212
- this._auth = new Auth(this.http["httpService"]);
229
+ this._auth = new Auth(this.http["httpService"], this.applicationID);
213
230
  } catch (error: any) {
214
231
  console.error("Failed to initialize:", error);
215
232
  return false;
@@ -338,24 +355,26 @@ async function findConfigEndpoint(): Promise<string> {
338
355
  'Accept': 'application/json',
339
356
  },
340
357
  signal: AbortSignal.timeout(2000)
341
- });
342
-
343
- if (testResponse.ok) {
344
- // Validate that the response is JSON and contains the expected 'port' field
345
- const contentType = testResponse.headers.get('content-type');
346
- if (contentType && contentType.includes('application/json')) {
347
- try {
348
- const testData = await testResponse.json();
349
- // Check if the response has the expected structure with a 'port' field
350
- // TODO: Allow checks for path based hosting as well
351
- if (testData && typeof testData === 'object' && 'port' in testData) {
352
- // console.log(`Found config endpoint at: ${configUrl}`);
353
- return configUrl;
354
- }
355
- } catch (jsonError) {
356
- // Not valid JSON, continue to next candidate
357
- continue;
358
+ }).catch(() => {console.log("ERR"); return null}); // Catch fetch errors silently
359
+
360
+ if (!testResponse || !testResponse.ok) {
361
+ continue; // Skip to next candidate
362
+ }
363
+
364
+ // Validate that the response is JSON and contains the expected 'port' field
365
+ const contentType = testResponse.headers.get('content-type');
366
+ if (contentType && contentType.includes('application/json')) {
367
+ try {
368
+ const testData = await testResponse.json();
369
+ // Check if the response has the expected structure with a 'port' field
370
+ // TODO: Allow checks for path based hosting as well
371
+ if (testData && typeof testData === 'object' && 'port' in testData) {
372
+ // console.log(`Found config endpoint at: ${configUrl}`);
373
+ return configUrl;
358
374
  }
375
+ } catch (jsonError) {
376
+ // Not valid JSON, continue to next candidate
377
+ continue;
359
378
  }
360
379
  }
361
380
  } catch (error) {
@@ -147,8 +147,12 @@ class RequestParameters {
147
147
  }
148
148
 
149
149
  public withAccessToken(): RequestParameters {
150
- const tkn = localStorage.getItem("x-machhub-auth-tkn");
151
- this.setHeader("Authorization", `Bearer ${tkn}`);
150
+ const rawAppID = this.applicationID.replace("domains:", "");
151
+ const storageKey = rawAppID
152
+ ? `x-machhub-auth-tkn-${rawAppID}`
153
+ : `x-machhub-auth-tkn`;
154
+ const tkn = localStorage.getItem(storageKey);
155
+ if (tkn) this.setHeader("Authorization", `Bearer ${tkn}`);
152
156
  return this;
153
157
  }
154
158