@openfin/cloud-interop-core-api 0.0.1-alpha.e646bca → 0.0.1-alpha.e648cd4

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
@@ -3,6 +3,8 @@
3
3
  var buffer = require('buffer');
4
4
  var mqtt = require('mqtt');
5
5
 
6
+ var l=t=>{let e=t.replaceAll("-","+").replaceAll("_","/");return e.padEnd(e.length+(4-e.length%4)%4,"=")};
7
+
6
8
  class CloudInteropAPIError extends Error {
7
9
  code;
8
10
  constructor(message = 'An unexpected error has occurred', code = 'UNEXPECTED_ERROR', cause) {
@@ -353,6 +355,7 @@ class CloudInteropAPI {
353
355
  #attemptingToReconnect = false;
354
356
  #events = new EventController();
355
357
  #intents;
358
+ #sessionTimer;
356
359
  constructor(cloudInteropSettings) {
357
360
  this.#cloudInteropSettings = cloudInteropSettings;
358
361
  }
@@ -365,11 +368,11 @@ class CloudInteropAPI {
365
368
  /**
366
369
  * Connects and creates a session on the Cloud Interop service
367
370
  *
368
- * @param {ConnectParameters} parameters - The parameters to use to connect
369
- * @returns {*} {Promise<void>}
371
+ * @param parameters - The parameters to use to connect
372
+ * @returns Promise that resolves when connection is established
370
373
  * @memberof CloudInteropAPI
371
- * @throws {CloudInteropAPIError} - If an error occurs during connection
372
- * @throws {AuthorizationError} - If the connection is unauthorized
374
+ * @throws CloudInteropAPIError - If an error occurs during connection
375
+ * @throws AuthorizationError - If the connection is unauthorized
373
376
  */
374
377
  async connect(parameters) {
375
378
  this.#validateConnectParams(parameters);
@@ -393,6 +396,11 @@ class CloudInteropAPI {
393
396
  throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this.#cloudInteropSettings.url}`, 'ERR_CONNECT', new Error(createSessionResponse.statusText));
394
397
  }
395
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
+ }
396
404
  const sessionRootTopic = this.#sessionDetails.sessionRootTopic;
397
405
  const clientOptions = {
398
406
  keepalive: this.#keepAliveIntervalSeconds,
@@ -422,9 +430,7 @@ class CloudInteropAPI {
422
430
  if (error instanceof mqtt.ErrorWithReasonCode) {
423
431
  switch (error.code) {
424
432
  case BadUserNamePasswordError: {
425
- await this.#disconnect(false);
426
- this.#logger('warn', `Session expired`);
427
- this.#events.emitEvent('session-expired');
433
+ this.#handleSessionExpiry();
428
434
  return;
429
435
  }
430
436
  default: {
@@ -478,9 +484,9 @@ class CloudInteropAPI {
478
484
  /**
479
485
  * Disconnects from the Cloud Interop service
480
486
  *
481
- * @returns {*} {Promise<void>}
487
+ * @returns Promise that resolves when disconnected
482
488
  * @memberof CloudInteropAPI
483
- * @throws {CloudInteropAPIError} - If an error occurs during disconnection
489
+ * @throws CloudInteropAPIError - If an error occurs during disconnection
484
490
  */
485
491
  async disconnect() {
486
492
  await this.#disconnect(true);
@@ -488,9 +494,9 @@ class CloudInteropAPI {
488
494
  /**
489
495
  * Publishes a new context for the given context group to the other connected sessions
490
496
  *
491
- * @param {string} contextGroup - The context group to publish to
492
- * @param {object} context - The context to publish
493
- * @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
494
500
  * @memberof CloudInteropAPI
495
501
  */
496
502
  async setContext(contextGroup, context) {
@@ -517,9 +523,9 @@ class CloudInteropAPI {
517
523
  /**
518
524
  * Starts an intent discovery operation
519
525
  *
520
- * @returns {*} {Promise<void>}
526
+ * @returns Promise that resolves when intent discovery is started
521
527
  * @memberof CloudInteropAPI
522
- * @throws {CloudInteropAPIError} - If an error occurs during intent discovery
528
+ * @throws CloudInteropAPIError - If an error occurs during intent discovery
523
529
  */
524
530
  async startIntentDiscovery(options) {
525
531
  this.#throwIfNotConnected();
@@ -560,6 +566,11 @@ class CloudInteropAPI {
560
566
  if (!this.#connectionParams) {
561
567
  throw new Error('Connect parameters must be provided');
562
568
  }
569
+ // Cancel session timer if it's running
570
+ if (this.#sessionTimer) {
571
+ clearTimeout(this.#sessionTimer);
572
+ this.#sessionTimer = undefined;
573
+ }
563
574
  const disconnectResponse = await fetch(`${this.#cloudInteropSettings.url}/api/sessions/${this.#sessionDetails.sessionId}`, {
564
575
  method: 'DELETE',
565
576
  headers: getRequestHeaders(this.#connectionParams),
@@ -658,6 +669,88 @@ class CloudInteropAPI {
658
669
  throw new Error('MQTT client not connected');
659
670
  }
660
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.Buffer.from(l(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
+ }
661
754
  }
662
755
 
663
756
  exports.AuthorizationError = AuthorizationError;
package/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import mqtt from 'mqtt';
3
3
 
4
+ var l=t=>{let e=t.replaceAll("-","+").replaceAll("_","/");return e.padEnd(e.length+(4-e.length%4)%4,"=")};
5
+
4
6
  class CloudInteropAPIError extends Error {
5
7
  code;
6
8
  constructor(message = 'An unexpected error has occurred', code = 'UNEXPECTED_ERROR', cause) {
@@ -351,6 +353,7 @@ class CloudInteropAPI {
351
353
  #attemptingToReconnect = false;
352
354
  #events = new EventController();
353
355
  #intents;
356
+ #sessionTimer;
354
357
  constructor(cloudInteropSettings) {
355
358
  this.#cloudInteropSettings = cloudInteropSettings;
356
359
  }
@@ -363,11 +366,11 @@ class CloudInteropAPI {
363
366
  /**
364
367
  * Connects and creates a session on the Cloud Interop service
365
368
  *
366
- * @param {ConnectParameters} parameters - The parameters to use to connect
367
- * @returns {*} {Promise<void>}
369
+ * @param parameters - The parameters to use to connect
370
+ * @returns Promise that resolves when connection is established
368
371
  * @memberof CloudInteropAPI
369
- * @throws {CloudInteropAPIError} - If an error occurs during connection
370
- * @throws {AuthorizationError} - If the connection is unauthorized
372
+ * @throws CloudInteropAPIError - If an error occurs during connection
373
+ * @throws AuthorizationError - If the connection is unauthorized
371
374
  */
372
375
  async connect(parameters) {
373
376
  this.#validateConnectParams(parameters);
@@ -391,6 +394,11 @@ class CloudInteropAPI {
391
394
  throw new CloudInteropAPIError(`Failed to connect to the Cloud Interop service: ${this.#cloudInteropSettings.url}`, 'ERR_CONNECT', new Error(createSessionResponse.statusText));
392
395
  }
393
396
  this.#sessionDetails = (await createSessionResponse.json());
397
+ // If local session expiry handling is enabled, start the session timer
398
+ if (this.#sessionDetails.localSessionExpiryHandling) {
399
+ this.#logger('debug', `Local session expiry handling is enabled`);
400
+ this.#startSessionTimer();
401
+ }
394
402
  const sessionRootTopic = this.#sessionDetails.sessionRootTopic;
395
403
  const clientOptions = {
396
404
  keepalive: this.#keepAliveIntervalSeconds,
@@ -420,9 +428,7 @@ class CloudInteropAPI {
420
428
  if (error instanceof mqtt.ErrorWithReasonCode) {
421
429
  switch (error.code) {
422
430
  case BadUserNamePasswordError: {
423
- await this.#disconnect(false);
424
- this.#logger('warn', `Session expired`);
425
- this.#events.emitEvent('session-expired');
431
+ this.#handleSessionExpiry();
426
432
  return;
427
433
  }
428
434
  default: {
@@ -476,9 +482,9 @@ class CloudInteropAPI {
476
482
  /**
477
483
  * Disconnects from the Cloud Interop service
478
484
  *
479
- * @returns {*} {Promise<void>}
485
+ * @returns Promise that resolves when disconnected
480
486
  * @memberof CloudInteropAPI
481
- * @throws {CloudInteropAPIError} - If an error occurs during disconnection
487
+ * @throws CloudInteropAPIError - If an error occurs during disconnection
482
488
  */
483
489
  async disconnect() {
484
490
  await this.#disconnect(true);
@@ -486,9 +492,9 @@ class CloudInteropAPI {
486
492
  /**
487
493
  * Publishes a new context for the given context group to the other connected sessions
488
494
  *
489
- * @param {string} contextGroup - The context group to publish to
490
- * @param {object} context - The context to publish
491
- * @returns {*} {Promise<void>}
495
+ * @param contextGroup - The context group to publish to
496
+ * @param context - The context to publish
497
+ * @returns Promise that resolves when context is published
492
498
  * @memberof CloudInteropAPI
493
499
  */
494
500
  async setContext(contextGroup, context) {
@@ -515,9 +521,9 @@ class CloudInteropAPI {
515
521
  /**
516
522
  * Starts an intent discovery operation
517
523
  *
518
- * @returns {*} {Promise<void>}
524
+ * @returns Promise that resolves when intent discovery is started
519
525
  * @memberof CloudInteropAPI
520
- * @throws {CloudInteropAPIError} - If an error occurs during intent discovery
526
+ * @throws CloudInteropAPIError - If an error occurs during intent discovery
521
527
  */
522
528
  async startIntentDiscovery(options) {
523
529
  this.#throwIfNotConnected();
@@ -558,6 +564,11 @@ class CloudInteropAPI {
558
564
  if (!this.#connectionParams) {
559
565
  throw new Error('Connect parameters must be provided');
560
566
  }
567
+ // Cancel session timer if it's running
568
+ if (this.#sessionTimer) {
569
+ clearTimeout(this.#sessionTimer);
570
+ this.#sessionTimer = undefined;
571
+ }
561
572
  const disconnectResponse = await fetch(`${this.#cloudInteropSettings.url}/api/sessions/${this.#sessionDetails.sessionId}`, {
562
573
  method: 'DELETE',
563
574
  headers: getRequestHeaders(this.#connectionParams),
@@ -656,6 +667,88 @@ class CloudInteropAPI {
656
667
  throw new Error('MQTT client not connected');
657
668
  }
658
669
  }
670
+ /**
671
+ * Extracts the expiration timestamp from a JWT token.
672
+ *
673
+ * @param token - The JWT token string
674
+ * @returns The expiration timestamp in seconds, or null if extraction fails
675
+ */
676
+ #extractExpirationFromJwt(token) {
677
+ try {
678
+ // JWT tokens have three parts separated by dots: header.payload.signature
679
+ // The exp claim is in the payload
680
+ const parts = token.split('.');
681
+ if (parts.length < 2) {
682
+ this.#logger('warn', 'Invalid JWT token format: expected at least 2 parts');
683
+ return null;
684
+ }
685
+ const payload = parts[1];
686
+ // Decode base64url encoded payload
687
+ const decodedBytes = Buffer.from(l(payload), 'base64');
688
+ const payloadJson = decodedBytes.toString('utf8');
689
+ // Parse JSON to get the exp claim
690
+ const claims = JSON.parse(payloadJson);
691
+ const exp = claims.exp;
692
+ if (exp === undefined || exp === null) {
693
+ this.#logger('warn', "JWT token does not contain 'exp' claim");
694
+ return null;
695
+ }
696
+ if (typeof exp !== 'number') {
697
+ this.#logger('warn', `JWT token 'exp' claim is not a number: ${exp}`);
698
+ return null;
699
+ }
700
+ return exp;
701
+ }
702
+ catch (error) {
703
+ this.#logger('error', `Failed to extract expiration from JWT token: ${error instanceof Error ? error.message : error}`);
704
+ return null;
705
+ }
706
+ }
707
+ /**
708
+ * Start a session timer that will expire at the time specified in the JWT token's exp claim.
709
+ * When the timer fires, it executes the same actions as the BadUserNamePasswordError case.
710
+ */
711
+ #startSessionTimer() {
712
+ if (!this.#sessionDetails?.localSessionExpiryHandling) {
713
+ return;
714
+ }
715
+ const token = this.#sessionDetails.token;
716
+ if (!token) {
717
+ this.#logger('warn', 'Cannot start session timer: token not available');
718
+ return;
719
+ }
720
+ // Extract expiration time from JWT token
721
+ const expTimestamp = this.#extractExpirationFromJwt(token);
722
+ if (expTimestamp === null) {
723
+ this.#logger('warn', 'Cannot start session timer: could not extract expiration from JWT token');
724
+ return;
725
+ }
726
+ const currentTimeSeconds = Math.floor(Date.now() / 1000);
727
+ const delaySeconds = expTimestamp - currentTimeSeconds;
728
+ if (delaySeconds <= 0) {
729
+ this.#logger('warn', 'JWT token has already expired or expires immediately');
730
+ // Execute the same actions as BadUserNamePasswordError case
731
+ this.#handleSessionExpiry();
732
+ return;
733
+ }
734
+ // Clear any existing timer
735
+ if (this.#sessionTimer) {
736
+ clearTimeout(this.#sessionTimer);
737
+ }
738
+ const expirationTimeString = new Date(expTimestamp * 1000).toISOString();
739
+ this.#logger('debug', `Starting session timer to expire in ${delaySeconds} seconds (at ${expirationTimeString})`);
740
+ this.#sessionTimer = setTimeout(async () => {
741
+ this.#handleSessionExpiry();
742
+ }, delaySeconds * 1000);
743
+ }
744
+ /**
745
+ * Handles session expiry by executing the same actions as the BadUserNamePasswordError case.
746
+ */
747
+ async #handleSessionExpiry() {
748
+ await this.#disconnect(false);
749
+ this.#logger('warn', 'Session expired');
750
+ this.#events.emitEvent('session-expired');
751
+ }
659
752
  }
660
753
 
661
754
  export { AuthorizationError, CloudInteropAPI, CloudInteropAPIError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfin/cloud-interop-core-api",
3
- "version": "0.0.1-alpha.e646bca",
3
+ "version": "0.0.1-alpha.e648cd4",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "./index.cjs",