@openfin/cloud-interop-core-api 0.0.1-alpha.ee17268 → 0.0.1-alpha.ee4dac8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # @openfin/cloud-interop-core-api
2
2
 
3
- This package contains the core interop library that handles all interactions with the Here™ Cloud Interop Service.
3
+ This package contains the core interop library that handles all interactions with the HERE Cloud Interop Service.
4
4
 
5
5
  It is callable via browser or node applications.
6
6
 
7
7
 
8
8
  ## Authentication
9
9
 
10
- The library supports authentication with the Here™ Cloud Interop Service using the following methods:
10
+ The library supports authentication with the HERE Cloud Interop Service using the following methods:
11
11
  - Basic Authentication
12
12
  - JWT Token Authentication
13
13
  - Default Authentication i.e. Interactive session based authentication using cookies
package/bundle.d.ts CHANGED
@@ -245,36 +245,36 @@ export declare class CloudInteropAPI {
245
245
  /**
246
246
  * Connects and creates a session on the Cloud Interop service
247
247
  *
248
- * @param {ConnectParameters} parameters - The parameters to use to connect
249
- * @returns {*} {Promise<void>}
248
+ * @param parameters - The parameters to use to connect
249
+ * @returns Promise that resolves when connection is established
250
250
  * @memberof CloudInteropAPI
251
- * @throws {CloudInteropAPIError} - If an error occurs during connection
252
- * @throws {AuthorizationError} - If the connection is unauthorized
251
+ * @throws CloudInteropAPIError - If an error occurs during connection
252
+ * @throws AuthorizationError - If the connection is unauthorized
253
253
  */
254
254
  connect(parameters: ConnectParameters): Promise<void>;
255
255
  /**
256
256
  * Disconnects from the Cloud Interop service
257
257
  *
258
- * @returns {*} {Promise<void>}
258
+ * @returns Promise that resolves when disconnected
259
259
  * @memberof CloudInteropAPI
260
- * @throws {CloudInteropAPIError} - If an error occurs during disconnection
260
+ * @throws CloudInteropAPIError - If an error occurs during disconnection
261
261
  */
262
262
  disconnect(): Promise<void>;
263
263
  /**
264
264
  * Publishes a new context for the given context group to the other connected sessions
265
265
  *
266
- * @param {string} contextGroup - The context group to publish to
267
- * @param {object} context - The context to publish
268
- * @returns {*} {Promise<void>}
266
+ * @param contextGroup - The context group to publish to
267
+ * @param context - The context to publish
268
+ * @returns Promise that resolves when context is published
269
269
  * @memberof CloudInteropAPI
270
270
  */
271
271
  setContext(contextGroup: string, context: InferredContext): Promise<void>;
272
272
  /**
273
273
  * Starts an intent discovery operation
274
274
  *
275
- * @returns {*} {Promise<void>}
275
+ * @returns Promise that resolves when intent discovery is started
276
276
  * @memberof CloudInteropAPI
277
- * @throws {CloudInteropAPIError} - If an error occurs during intent discovery
277
+ * @throws CloudInteropAPIError - If an error occurs during intent discovery
278
278
  */
279
279
  startIntentDiscovery(options: StartIntentDiscoveryOptions): Promise<void>;
280
280
  raiseIntent(options: RaiseIntentAPIOptions): Promise<void>;
@@ -418,6 +418,7 @@ export declare type CreateSessionResponse = {
418
418
  sub: string;
419
419
  platformId: string;
420
420
  sourceId: string;
421
+ localSessionExpiryHandling?: boolean;
421
422
  };
422
423
 
423
424
  declare const errorSchema: z.ZodObject<{
package/index.cjs CHANGED
@@ -1,8 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var buffer = require('buffer');
4
+ require('fs');
5
+ require('path');
4
6
  var mqtt = require('mqtt');
5
7
 
8
+ var u=n=>{let e=n.replaceAll("-","+").replaceAll("_","/");return e.padEnd(e.length+(4-e.length%4)%4,"=")};
9
+
6
10
  class CloudInteropAPIError extends Error {
7
11
  code;
8
12
  constructor(message = 'An unexpected error has occurred', code = 'UNEXPECTED_ERROR', cause) {
@@ -353,6 +357,7 @@ class CloudInteropAPI {
353
357
  #attemptingToReconnect = false;
354
358
  #events = new EventController();
355
359
  #intents;
360
+ #sessionTimer;
356
361
  constructor(cloudInteropSettings) {
357
362
  this.#cloudInteropSettings = cloudInteropSettings;
358
363
  }
@@ -365,11 +370,11 @@ class CloudInteropAPI {
365
370
  /**
366
371
  * Connects and creates a session on the Cloud Interop service
367
372
  *
368
- * @param {ConnectParameters} parameters - The parameters to use to connect
369
- * @returns {*} {Promise<void>}
373
+ * @param parameters - The parameters to use to connect
374
+ * @returns Promise that resolves when connection is established
370
375
  * @memberof CloudInteropAPI
371
- * @throws {CloudInteropAPIError} - If an error occurs during connection
372
- * @throws {AuthorizationError} - If the connection is unauthorized
376
+ * @throws CloudInteropAPIError - If an error occurs during connection
377
+ * @throws AuthorizationError - If the connection is unauthorized
373
378
  */
374
379
  async connect(parameters) {
375
380
  this.#validateConnectParams(parameters);
@@ -393,6 +398,11 @@ class CloudInteropAPI {
393
398
  throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this.#cloudInteropSettings.url}`, 'ERR_CONNECT', new Error(createSessionResponse.statusText));
394
399
  }
395
400
  this.#sessionDetails = (await createSessionResponse.json());
401
+ // If local session expiry handling is enabled, start the session timer
402
+ if (this.#sessionDetails.localSessionExpiryHandling) {
403
+ this.#logger('debug', `Local session expiry handling is enabled`);
404
+ this.#startSessionTimer();
405
+ }
396
406
  const sessionRootTopic = this.#sessionDetails.sessionRootTopic;
397
407
  const clientOptions = {
398
408
  keepalive: this.#keepAliveIntervalSeconds,
@@ -422,9 +432,7 @@ class CloudInteropAPI {
422
432
  if (error instanceof mqtt.ErrorWithReasonCode) {
423
433
  switch (error.code) {
424
434
  case BadUserNamePasswordError: {
425
- await this.#disconnect(false);
426
- this.#logger('warn', `Session expired`);
427
- this.#events.emitEvent('session-expired');
435
+ this.#handleSessionExpiry();
428
436
  return;
429
437
  }
430
438
  default: {
@@ -478,9 +486,9 @@ class CloudInteropAPI {
478
486
  /**
479
487
  * Disconnects from the Cloud Interop service
480
488
  *
481
- * @returns {*} {Promise<void>}
489
+ * @returns Promise that resolves when disconnected
482
490
  * @memberof CloudInteropAPI
483
- * @throws {CloudInteropAPIError} - If an error occurs during disconnection
491
+ * @throws CloudInteropAPIError - If an error occurs during disconnection
484
492
  */
485
493
  async disconnect() {
486
494
  await this.#disconnect(true);
@@ -488,9 +496,9 @@ class CloudInteropAPI {
488
496
  /**
489
497
  * Publishes a new context for the given context group to the other connected sessions
490
498
  *
491
- * @param {string} contextGroup - The context group to publish to
492
- * @param {object} context - The context to publish
493
- * @returns {*} {Promise<void>}
499
+ * @param contextGroup - The context group to publish to
500
+ * @param context - The context to publish
501
+ * @returns Promise that resolves when context is published
494
502
  * @memberof CloudInteropAPI
495
503
  */
496
504
  async setContext(contextGroup, context) {
@@ -517,9 +525,9 @@ class CloudInteropAPI {
517
525
  /**
518
526
  * Starts an intent discovery operation
519
527
  *
520
- * @returns {*} {Promise<void>}
528
+ * @returns Promise that resolves when intent discovery is started
521
529
  * @memberof CloudInteropAPI
522
- * @throws {CloudInteropAPIError} - If an error occurs during intent discovery
530
+ * @throws CloudInteropAPIError - If an error occurs during intent discovery
523
531
  */
524
532
  async startIntentDiscovery(options) {
525
533
  this.#throwIfNotConnected();
@@ -560,6 +568,11 @@ class CloudInteropAPI {
560
568
  if (!this.#connectionParams) {
561
569
  throw new Error('Connect parameters must be provided');
562
570
  }
571
+ // Cancel session timer if it's running
572
+ if (this.#sessionTimer) {
573
+ clearTimeout(this.#sessionTimer);
574
+ this.#sessionTimer = undefined;
575
+ }
563
576
  const disconnectResponse = await fetch(`${this.#cloudInteropSettings.url}/api/sessions/${this.#sessionDetails.sessionId}`, {
564
577
  method: 'DELETE',
565
578
  headers: getRequestHeaders(this.#connectionParams),
@@ -658,6 +671,88 @@ class CloudInteropAPI {
658
671
  throw new Error('MQTT client not connected');
659
672
  }
660
673
  }
674
+ /**
675
+ * Extracts the expiration timestamp from a JWT token.
676
+ *
677
+ * @param token - The JWT token string
678
+ * @returns The expiration timestamp in seconds, or null if extraction fails
679
+ */
680
+ #extractExpirationFromJwt(token) {
681
+ try {
682
+ // JWT tokens have three parts separated by dots: header.payload.signature
683
+ // The exp claim is in the payload
684
+ const parts = token.split('.');
685
+ if (parts.length < 2) {
686
+ this.#logger('warn', 'Invalid JWT token format: expected at least 2 parts');
687
+ return null;
688
+ }
689
+ const payload = parts[1];
690
+ // Decode base64url encoded payload
691
+ const decodedBytes = buffer.Buffer.from(u(payload), 'base64');
692
+ const payloadJson = decodedBytes.toString('utf8');
693
+ // Parse JSON to get the exp claim
694
+ const claims = JSON.parse(payloadJson);
695
+ const exp = claims.exp;
696
+ if (exp === undefined || exp === null) {
697
+ this.#logger('warn', "JWT token does not contain 'exp' claim");
698
+ return null;
699
+ }
700
+ if (typeof exp !== 'number') {
701
+ this.#logger('warn', `JWT token 'exp' claim is not a number: ${exp}`);
702
+ return null;
703
+ }
704
+ return exp;
705
+ }
706
+ catch (error) {
707
+ this.#logger('error', `Failed to extract expiration from JWT token: ${error instanceof Error ? error.message : error}`);
708
+ return null;
709
+ }
710
+ }
711
+ /**
712
+ * Start a session timer that will expire at the time specified in the JWT token's exp claim.
713
+ * When the timer fires, it executes the same actions as the BadUserNamePasswordError case.
714
+ */
715
+ #startSessionTimer() {
716
+ if (!this.#sessionDetails?.localSessionExpiryHandling) {
717
+ return;
718
+ }
719
+ const token = this.#sessionDetails.token;
720
+ if (!token) {
721
+ this.#logger('warn', 'Cannot start session timer: token not available');
722
+ return;
723
+ }
724
+ // Extract expiration time from JWT token
725
+ const expTimestamp = this.#extractExpirationFromJwt(token);
726
+ if (expTimestamp === null) {
727
+ this.#logger('warn', 'Cannot start session timer: could not extract expiration from JWT token');
728
+ return;
729
+ }
730
+ const currentTimeSeconds = Math.floor(Date.now() / 1000);
731
+ const delaySeconds = expTimestamp - currentTimeSeconds;
732
+ if (delaySeconds <= 0) {
733
+ this.#logger('warn', 'JWT token has already expired or expires immediately');
734
+ // Execute the same actions as BadUserNamePasswordError case
735
+ this.#handleSessionExpiry();
736
+ return;
737
+ }
738
+ // Clear any existing timer
739
+ if (this.#sessionTimer) {
740
+ clearTimeout(this.#sessionTimer);
741
+ }
742
+ const expirationTimeString = new Date(expTimestamp * 1000).toISOString();
743
+ this.#logger('debug', `Starting session timer to expire in ${delaySeconds} seconds (at ${expirationTimeString})`);
744
+ this.#sessionTimer = setTimeout(async () => {
745
+ this.#handleSessionExpiry();
746
+ }, delaySeconds * 1000);
747
+ }
748
+ /**
749
+ * Handles session expiry by executing the same actions as the BadUserNamePasswordError case.
750
+ */
751
+ async #handleSessionExpiry() {
752
+ await this.#disconnect(false);
753
+ this.#logger('warn', 'Session expired');
754
+ this.#events.emitEvent('session-expired');
755
+ }
661
756
  }
662
757
 
663
758
  exports.AuthorizationError = AuthorizationError;
package/index.mjs CHANGED
@@ -1,6 +1,10 @@
1
1
  import { Buffer } from 'buffer';
2
+ import 'fs';
3
+ import 'path';
2
4
  import mqtt from 'mqtt';
3
5
 
6
+ var u=n=>{let e=n.replaceAll("-","+").replaceAll("_","/");return e.padEnd(e.length+(4-e.length%4)%4,"=")};
7
+
4
8
  class CloudInteropAPIError extends Error {
5
9
  code;
6
10
  constructor(message = 'An unexpected error has occurred', code = 'UNEXPECTED_ERROR', cause) {
@@ -351,6 +355,7 @@ class CloudInteropAPI {
351
355
  #attemptingToReconnect = false;
352
356
  #events = new EventController();
353
357
  #intents;
358
+ #sessionTimer;
354
359
  constructor(cloudInteropSettings) {
355
360
  this.#cloudInteropSettings = cloudInteropSettings;
356
361
  }
@@ -363,11 +368,11 @@ class CloudInteropAPI {
363
368
  /**
364
369
  * Connects and creates a session on the Cloud Interop service
365
370
  *
366
- * @param {ConnectParameters} parameters - The parameters to use to connect
367
- * @returns {*} {Promise<void>}
371
+ * @param parameters - The parameters to use to connect
372
+ * @returns Promise that resolves when connection is established
368
373
  * @memberof CloudInteropAPI
369
- * @throws {CloudInteropAPIError} - If an error occurs during connection
370
- * @throws {AuthorizationError} - If the connection is unauthorized
374
+ * @throws CloudInteropAPIError - If an error occurs during connection
375
+ * @throws AuthorizationError - If the connection is unauthorized
371
376
  */
372
377
  async connect(parameters) {
373
378
  this.#validateConnectParams(parameters);
@@ -391,6 +396,11 @@ class CloudInteropAPI {
391
396
  throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this.#cloudInteropSettings.url}`, 'ERR_CONNECT', new Error(createSessionResponse.statusText));
392
397
  }
393
398
  this.#sessionDetails = (await createSessionResponse.json());
399
+ // If local session expiry handling is enabled, start the session timer
400
+ if (this.#sessionDetails.localSessionExpiryHandling) {
401
+ this.#logger('debug', `Local session expiry handling is enabled`);
402
+ this.#startSessionTimer();
403
+ }
394
404
  const sessionRootTopic = this.#sessionDetails.sessionRootTopic;
395
405
  const clientOptions = {
396
406
  keepalive: this.#keepAliveIntervalSeconds,
@@ -420,9 +430,7 @@ class CloudInteropAPI {
420
430
  if (error instanceof mqtt.ErrorWithReasonCode) {
421
431
  switch (error.code) {
422
432
  case BadUserNamePasswordError: {
423
- await this.#disconnect(false);
424
- this.#logger('warn', `Session expired`);
425
- this.#events.emitEvent('session-expired');
433
+ this.#handleSessionExpiry();
426
434
  return;
427
435
  }
428
436
  default: {
@@ -476,9 +484,9 @@ class CloudInteropAPI {
476
484
  /**
477
485
  * Disconnects from the Cloud Interop service
478
486
  *
479
- * @returns {*} {Promise<void>}
487
+ * @returns Promise that resolves when disconnected
480
488
  * @memberof CloudInteropAPI
481
- * @throws {CloudInteropAPIError} - If an error occurs during disconnection
489
+ * @throws CloudInteropAPIError - If an error occurs during disconnection
482
490
  */
483
491
  async disconnect() {
484
492
  await this.#disconnect(true);
@@ -486,9 +494,9 @@ class CloudInteropAPI {
486
494
  /**
487
495
  * Publishes a new context for the given context group to the other connected sessions
488
496
  *
489
- * @param {string} contextGroup - The context group to publish to
490
- * @param {object} context - The context to publish
491
- * @returns {*} {Promise<void>}
497
+ * @param contextGroup - The context group to publish to
498
+ * @param context - The context to publish
499
+ * @returns Promise that resolves when context is published
492
500
  * @memberof CloudInteropAPI
493
501
  */
494
502
  async setContext(contextGroup, context) {
@@ -515,9 +523,9 @@ class CloudInteropAPI {
515
523
  /**
516
524
  * Starts an intent discovery operation
517
525
  *
518
- * @returns {*} {Promise<void>}
526
+ * @returns Promise that resolves when intent discovery is started
519
527
  * @memberof CloudInteropAPI
520
- * @throws {CloudInteropAPIError} - If an error occurs during intent discovery
528
+ * @throws CloudInteropAPIError - If an error occurs during intent discovery
521
529
  */
522
530
  async startIntentDiscovery(options) {
523
531
  this.#throwIfNotConnected();
@@ -558,6 +566,11 @@ class CloudInteropAPI {
558
566
  if (!this.#connectionParams) {
559
567
  throw new Error('Connect parameters must be provided');
560
568
  }
569
+ // Cancel session timer if it's running
570
+ if (this.#sessionTimer) {
571
+ clearTimeout(this.#sessionTimer);
572
+ this.#sessionTimer = undefined;
573
+ }
561
574
  const disconnectResponse = await fetch(`${this.#cloudInteropSettings.url}/api/sessions/${this.#sessionDetails.sessionId}`, {
562
575
  method: 'DELETE',
563
576
  headers: getRequestHeaders(this.#connectionParams),
@@ -656,6 +669,88 @@ class CloudInteropAPI {
656
669
  throw new Error('MQTT client not connected');
657
670
  }
658
671
  }
672
+ /**
673
+ * Extracts the expiration timestamp from a JWT token.
674
+ *
675
+ * @param token - The JWT token string
676
+ * @returns The expiration timestamp in seconds, or null if extraction fails
677
+ */
678
+ #extractExpirationFromJwt(token) {
679
+ try {
680
+ // JWT tokens have three parts separated by dots: header.payload.signature
681
+ // The exp claim is in the payload
682
+ const parts = token.split('.');
683
+ if (parts.length < 2) {
684
+ this.#logger('warn', 'Invalid JWT token format: expected at least 2 parts');
685
+ return null;
686
+ }
687
+ const payload = parts[1];
688
+ // Decode base64url encoded payload
689
+ const decodedBytes = Buffer.from(u(payload), 'base64');
690
+ const payloadJson = decodedBytes.toString('utf8');
691
+ // Parse JSON to get the exp claim
692
+ const claims = JSON.parse(payloadJson);
693
+ const exp = claims.exp;
694
+ if (exp === undefined || exp === null) {
695
+ this.#logger('warn', "JWT token does not contain 'exp' claim");
696
+ return null;
697
+ }
698
+ if (typeof exp !== 'number') {
699
+ this.#logger('warn', `JWT token 'exp' claim is not a number: ${exp}`);
700
+ return null;
701
+ }
702
+ return exp;
703
+ }
704
+ catch (error) {
705
+ this.#logger('error', `Failed to extract expiration from JWT token: ${error instanceof Error ? error.message : error}`);
706
+ return null;
707
+ }
708
+ }
709
+ /**
710
+ * Start a session timer that will expire at the time specified in the JWT token's exp claim.
711
+ * When the timer fires, it executes the same actions as the BadUserNamePasswordError case.
712
+ */
713
+ #startSessionTimer() {
714
+ if (!this.#sessionDetails?.localSessionExpiryHandling) {
715
+ return;
716
+ }
717
+ const token = this.#sessionDetails.token;
718
+ if (!token) {
719
+ this.#logger('warn', 'Cannot start session timer: token not available');
720
+ return;
721
+ }
722
+ // Extract expiration time from JWT token
723
+ const expTimestamp = this.#extractExpirationFromJwt(token);
724
+ if (expTimestamp === null) {
725
+ this.#logger('warn', 'Cannot start session timer: could not extract expiration from JWT token');
726
+ return;
727
+ }
728
+ const currentTimeSeconds = Math.floor(Date.now() / 1000);
729
+ const delaySeconds = expTimestamp - currentTimeSeconds;
730
+ if (delaySeconds <= 0) {
731
+ this.#logger('warn', 'JWT token has already expired or expires immediately');
732
+ // Execute the same actions as BadUserNamePasswordError case
733
+ this.#handleSessionExpiry();
734
+ return;
735
+ }
736
+ // Clear any existing timer
737
+ if (this.#sessionTimer) {
738
+ clearTimeout(this.#sessionTimer);
739
+ }
740
+ const expirationTimeString = new Date(expTimestamp * 1000).toISOString();
741
+ this.#logger('debug', `Starting session timer to expire in ${delaySeconds} seconds (at ${expirationTimeString})`);
742
+ this.#sessionTimer = setTimeout(async () => {
743
+ this.#handleSessionExpiry();
744
+ }, delaySeconds * 1000);
745
+ }
746
+ /**
747
+ * Handles session expiry by executing the same actions as the BadUserNamePasswordError case.
748
+ */
749
+ async #handleSessionExpiry() {
750
+ await this.#disconnect(false);
751
+ this.#logger('warn', 'Session expired');
752
+ this.#events.emitEvent('session-expired');
753
+ }
659
754
  }
660
755
 
661
756
  export { AuthorizationError, CloudInteropAPI, CloudInteropAPIError };
package/package.json CHANGED
@@ -1,18 +1,15 @@
1
1
  {
2
2
  "name": "@openfin/cloud-interop-core-api",
3
- "version": "0.0.1-alpha.ee17268",
4
- "type": "module",
3
+ "version": "0.0.1-alpha.ee4dac8",
5
4
  "description": "",
5
+ "type": "module",
6
6
  "main": "./index.cjs",
7
7
  "browser": "./index.mjs",
8
8
  "types": "./bundle.d.ts",
9
- "author": "",
9
+ "author": "support@here.io",
10
10
  "license": "SEE LICENSE IN LICENSE.md",
11
11
  "dependencies": {
12
- "mqtt": "^5.13.0",
13
- "zod": "^3.24.4"
14
- },
15
- "optionalDependencies": {
16
- "@rollup/rollup-linux-x64-gnu": "*"
12
+ "mqtt": "^5.15.1",
13
+ "zod": "catalog:"
17
14
  }
18
15
  }