@openfin/cloud-interop-core-api 0.0.1-alpha.c7e246f → 0.0.1-alpha.c89de1a

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