Skip to content

LivePersonInc/faas-client-node

Repository files navigation

Functions-Client

The LivePerson Functions client for NodeJS.

It offers functionality to retrieve all lambdas and invoke them via lambda UUID or event IDs.

Table of Contents:

🚨 Important Project Update: Functions V2 compatibility released! 🚨

We are excited to announce a major update to our client: Functions V2 is now available! 🎉

What's New?

  • The client is fully compatible with both Functions V1 and V2. It has been implemented in a way that minimizes the need for changes. The client will automatically call the appropriate platform (V1 or V2) based on your account's CSDS domain.
  • The following configuration properties and types have been deprecated. They are still supported for V1, but we recommend updating them, if possible, for a smoother transition to V2:
    • DefaultConfig apiVersion: not available in V2. Still used for V1.
    • DefaultConfig getLambdasUri: use getFunctionsUri instead.
    • InvocationMetricData externalSystem: use lpEventSource instead.
    • Method getLambdas(): Now compatible with V1 and V2. Only for V2 getFunctions is recommended.
    • Type BaseQuery: not used in V2. Still used for V1.
    • Type GetLambdasQuery: replaced by GetFunctionsQuery in V2.
    • Type LambdaRequest: replaced by FunctionRequestin V2.
  • The error response body returned by the V2 platform has changed, but the Functions client response format remains the same. For more details, please refer to the Error handling section.

How to Use the New Feature

The client should continue functioning as it currently does, calling either the V1 or V2 platform based on the account. If you are handling function errors from the error response body within the client response payload (jse_cause.jse_info.response.body), please note that the V2 error format has changed. You will need to adjust your handling accordingly. For more details, refer to the Error handling section.

Action Required

  • Update the client to Version 2.x.x.
  • Adjust your error handling if you're using the jse_cause.jse_info.response.body For more details, refer to the Error handling section.
  • If you are using getFunctions/getLambdas method, you mus consider that functions data format has changed in V2.
  • Optional, but recommended: Replace any usage of deprecated V1 properties/types when possible.

Overview

Capabilities:

  • Invoking Lambdas
  • Checking if an event is implemented by the account
  • Getting lambdas of an account

Install

yarn add liveperson-functions-client

or

npm install liveperson-functions-client

Usage

Note: The library exposes typings for IDE assistance/auto-complete and compile time validation for TS projects.

Initializing the client

The client will use the OAuth2.0 flow client_credentials for authorization. Please refer to these docs for further information on that. On each request the client will check if the JWT is about to expire. If this is the case, the client will try to refresh it. If the JWT is expired and the client failed to refresh it, an Error is thrown. The time after which the refreshing logic will kick in can be specified via the property jwtRefreshAfterMinutes. Alternatively, you can provide your own authorization method that generates a suitable authorization header by implementing a method that fulfills the GetAuthorizationHeader-Interface:

export type GetAuthorizationHeader = (input: {
  url: string;
  method: string;
}) => Promise<string>;

Example:

// Default: Provide client id and client secret as auth strategy as follows
import { Client } from 'liveperson-functions-client';

const client = new Client({
  accountId: 'myAccountId',
  authStrategy: {
    clientId: 'myClientId',
    clientSecret: 'myClientSecret',
  },
});


// Alternative: Provide your own implementation of our type 'GetAuthorizationHeader'
import { Client, GetAuthorizationHeader } from 'liveperson-functions-client';

const getAuthorizationHeader: GetAuthorizationHeader = async ({
  url,
  method,
}) => {
  // example custom implementation
  const oauthApiKey = '...';
  const oauthApiSecret = '...';
  const oauthSignatureMethod = '...';

  const oAuth = new OAuth({
    consumer: {
      key: oauthApiKey,
      secret: oauthApiSecret,
    },
    signature_method: oauthSignatureMethod,
    realm: '',
    hash_function: (baseString: string, key: string): string => {
      return createHmac(oauthSignatureMethod.split('-')[1], key)
        .update(baseString)
        .digest('base64');
    },
  });

  return oAuth.toHeader(oAuth.authorize({ url, method })).Authorization;
};

const client = new Client({
  accountId: 'myAccountId',
  authStrategy: getAuthorizationHeader,
});

DPoP authorization

The client supports Oauth2+DPoP authorization ONLY FOR INTERNAL USE in service-to-service. Required methods can be configured during the initialization as 'authStrategy.' You must provide your implementation of the getAccessTokenInternal and getDpopHeaderInternal methods.

Example:

const client = new Client({
  accountId: 'myAccountId',
  authStrategy: {
      getAccessTokenInternal: async (domainUrl: string) => {
        // you implementation
        return 'accessToken';
      },
      getDpopHeaderInternal: async (url: string, method: string, accessToken?: string) => {
        // you implementation
        return 'dopHeader';
      },
    }
});

Optional configuration parameters

While authStrategy and accountId are obligatory parameters for the configuration of the client, multiple other parameters can be set optionally. If you do not set them we default back to our default configuration.

The optional parameters can be found in the interface default config which looks like this.

export interface DefaultConfig {
  readonly gwCsdsServiceName?: string;
  readonly uiCsdsServiceName?: string;
  readonly apiVersion?: string;
  readonly timeout?: number;
  readonly protocol?: typeof Protocol[keyof typeof Protocol];
  readonly getLambdasUri?: string;
  readonly invokeUuidUri?: string;
  readonly invokeEventUri?: string;
  readonly isImplementedUri?: string;
  readonly failOnErrorStatusCode?: boolean;
  /** Optional HTTP request headers that should be included in CSDS requests. */
  readonly csdsHttpHeaders?: { [key: string]: any };
  readonly csdsTtlSeconds?: number;
  /**
   * Time after which the JWT should be refreshed.
   */
  readonly jwtRefreshAfterMinutes?: number;

  readonly isImplementedCacheDurationInSeconds?: number;
}

// These values default to:

export const defaultConfig: Required<DefaultConfig> = {
  gwCsdsServiceName: 'faasGW',
  uiCsdsServiceName: 'faasUI',
  apiVersion: '1',
  timeout: 35000, // ms
  protocol: Protocol.HTTPS,
  getLambdasUri: 'api/account/%s/lambdas/',
  invokeUuidUri: 'api/account/%s/lambdas/%s/invoke',
  invokeEventUri: 'api/account/%s/events/%s/invoke',
  isImplementedUri: 'api/account/%s/events/%s/isImplemented',
  failOnErrorStatusCode: false,
  csdsHttpHeaders: {},
  csdsTtlSeconds: 600,
  jwtRefreshAfterMinutes: 30,
  isImplementedCacheDurationInSeconds: 60,
};

If you want to set one of these parameters, just add them to the object passed to the client as its first paramter in the initialization process. Below you can find an example where the optional config param isImplementedCacheDurationInSeconds is set.

const client = new Client({
  accountId: 'myAccountId',
  authStrategy: {
    clientId: 'myClientId',
    clientSecret: 'myClientSecret',
  },
  isImplementedCacheDurationInSeconds: 20;
});

Collecting metrics

If you want to collect metrics you may implement the interface MetricCollector and pass your implementation to the client when initializing it. The metrics are only held in memory and are not persisted. If you pass no implementation of MetricCollector the metrics will be ignored.

import { Client } from 'liveperson-functions-client';
const client = new Client(
  {
    accountId: 'myAccountId',
    authStrategy: {
      clientId: 'myClientId',
      clientSecret: 'myClientSecret',
    },
  },
{
    metricCollector: new MyMetricCollector(),
});

Method call examples

Invoking a lambda by UUID

const response = await client.invoke({
  lambdaUuid: 'uuid',
  lpEventSource: 'demoSystem',
  body: {
    headers: [],
    payload: {
      foo: 'bar',
    },
  },
});

Invoking a lambda by event

const response = await client.invoke({
  eventId: 'eventId',
  lpEventSource: 'demoSystem',
  body: {
    headers: [],
    payload: {
      foo: 'bar',
    },
  },
});

Check if an event type/id is available/implemented

const response = await client.isImplemented({
  eventId: 'eventId',
  externalSystem: 'demoSystem',
});

Getting a list of existing lambdas

You have to use your own authentication method when fetching lambdas as it still relies on OAuth 1.0.

const response = await client.getLambdas({
  eventId: 'eventId', // filter lambdas for events
  state: ['Productive', 'Draft'], // filter lambdas for deployment states
  userId: 'userId',
});

Error handling

Errors with the name FaaSLambdaError are raised when the invocation fails due to a custom implementation error. The client internally uses verror. We recommend to log the stack in order to get detailed information about the root cause.

try {
  // invoke here
  ...
} catch (error) {
  /**
   * LivePerson FunctionsLambdaErrors occur when the lambda fails due to the implementation.
   * These exceptions are not relevant for alerting, because there are no issues with the service itself.
   */
  if (error.name === "FaaSLambdaError") {
    console.info(error.stack, "Error caused by implementation of lambda.");
  } else {
    console.error(error.stack, "Something unexpected happened.");
  }
}

More detailed information on errors that can occur can be found here.

V2 Errors

The functions client can handle V2 errors in the same way as V1, with the only difference being the response body property in jse_cause.jse_info.response.body.

Example of a V1 error response body:

{
    errorCode: "com.liveperson.faas.handler.custom-failure",
    errorMsg: "Function Invocation failed due to an issue caused by customer coding",
}

Example of a V2 error response body:

{
  code: "com.customer.faas.function.threw-error",
  message: "Function Invocation failed due to an issue caused by customer coding",
}

To preserve the V1 format, you can set v1CompError to true in the invoke call. The error will be mapped to the V1 format with the corresponding error code, if available. Please note that error messages will not be mapped, and the V2 message will remain unchanged.

  await client.invoke({
    lambdaUuid: errorLambdaUUID,
    lpEventSource: 'demoSystem',
    v1CompError: false,
    body: {
      headers: [],
      payload,
    },
  });

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 15