npm package: @petscreening/embedded-flow-sdk
Description: A library for embedding PetScreening's flow into your application
Author: PetScreening, Inc.
Latest version: 1.1.1

# embedded-flow-sdk

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build embedded-flow-sdk` to build the library.

## Installation

To install the `@petscreening/embedded-flow-sdk` package, run:

```bash
npm install @petscreening/embedded-flow-sdk
```

or with Yarn:

```bash
yarn add @petscreening/embedded-flow-sdk
```

## Environment URLs

The SDK supports different environments with the following base URLs:

- Production: `https://embedded.petscreening.com`
- Release Candidate (RC): `https://embedded.rc.petscreening.com`
- QA: `https://embedded.qa.petscreening.com`

You can specify the environment by setting the `overrideUrl` option accordingly:

```typescript
ps.initEmbeddedFlow('#your-button-selector', {
  overrideUrl: 'https://embedded.qa.petscreening.com', // For QA environment
  queryParams: {
    serviceProviderId: 5126,
    userFirstName: 'John',
    userLastName: 'Doe',
    userEmail: 'john.doe@example.com',
    userPhone: '1234567890',
    userAddress: '1234 Main St',
    userAddressContinued: 'Apt 2B',
    userCity: 'North Carolina',
    userState: 'NC',
    userZip: '27617',
    userMovingTime: 'not_moving',
  },
  callbacks: {
    onEvent: (data) => {
      console.log('Event received:', data);
    },
  },
});
```

## Callbacks

The SDK supports several callbacks to handle different events:

### petProfilesCompleted

This callback is triggered when all pet profiles have been successfully completed in the flow. It receives detailed information about the user and their pet profiles.

```typescript
ps.initEmbeddedFlow('#your-button-selector', {
  callbacks: {
    petProfilesCompleted: (data) => {
      // data contains user and pet profiles information
      const { user, petProfiles } = data;

      console.log('User information:', user);
      // User information includes:
      // - uniqueId: number
      // - name: string
      // - email: string
      // - phone: string | null
      // - address: string
      // - expectedProfiles: number

      console.log('Pet Profiles:', petProfiles);
      // Each pet profile includes:
      // - kind: string
      // - uniqueId: number
      // - species: string
      // - name: string | null
      // - breed: string
      // - secondaryBreed: string
      // - gender: string
      // - weight: number | null
      // - colors: string[]
      // - fidoScore: number | null
      // - createdAt: string
      // - updatedAt: string
      // - status: string
      // - archived: boolean
      // - expiredVaccineDate: string | null
      // - hasExpiredVaccine: boolean
      // - isAssistanceAnimal: boolean
      // - images: Array<{ id: number, url: string }>
    },
  },
});
```

## Security

### Data Signing

To ensure the integrity of pet profile data, you can enable data signing by providing a secret key in the options:

```typescript
ps.initEmbeddedFlow('#your-button-selector', {
  secretKey: 'your-secret-key-here', // Add this for data signing
  callbacks: {
    petProfilesCompleted: (data) => {
      // data now includes:
      // - origin: string (origin of the message)
      // - signature: string (HMAC-SHA256 signature of the data)
      // - timestamp: string (ISO timestamp of when the data was signed)
      // - ... original data ...

      // Verify the signature on your backend using the same secret key
      const isValid = await verifySignature(signedData, 'your-secure-key-here');
      if (isValid) {
        // Process the verified data
        console.log('Data verified:', signedData);
      } else {
        console.error('Invalid signature or expired timestamp');
      }
    },
  },
});
```

To verify the signature on your backend:

```typescript
async function verifySignature(signedData: any, secretKey: string): Promise<boolean> {
  try {
    const { signature, ...dataToVerify } = signedData;

    // Convert the secret key to a crypto key
    const keyData = stringToUint8Array(secretKey);
    const cryptoKey = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);

    // Convert data to string and then to Uint8Array
    const dataString = JSON.stringify(dataToVerify);
    const verifyData = stringToUint8Array(dataString);

    // Convert hex signature back to Uint8Array
    const signatureArray = new Uint8Array(signature.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)));

    // Verify the signature
    const isValid = await crypto.subtle.verify('HMAC', cryptoKey, signatureArray, verifyData);

    // Verify timestamp is recent (e.g., within last 5 minutes)
    const timestamp = new Date(dataToVerify.timestamp);
    const timestampAge = Date.now() - timestamp.getTime();
    const isTimestampValid = timestampAge < 5 * 60 * 1000; // 5 minutes

    return isValid && isTimestampValid;
  } catch (error) {
    console.error('Error verifying signature:', error);
    return false;
  }
}
```

Note: Store your secret key securely and never expose it in client-side code. The key should be managed by your backend services.

## Usage

Here is an example of how to use the `@petscreening/embedded-flow-sdk` in your React project:

```typescript
import React, { useEffect } from 'react';
import { ps } from '@petscreening/embedded-flow-sdk';

const YourComponent = () => {
  useEffect(() => {
    ps.initEmbeddedFlow('#your-button-selector', {
      queryParams: {
        serviceProviderId: 5126,
        userFirstName: 'John',
        userLastName: 'Doe',
        userEmail: 'john.doe@example.com',
        userPhone: '1234567890',
        userAddress: '1234 Main St',
        userAddressContinued: 'Apt 2B',
        userCity: 'North Carolina',
        userState: 'NC',
        userZip: '27617',
        userMovingTime: 'not_moving',
      },
      callbacks: {
        onEvent: (data) => {
          console.log('Event received:', data);
        },
        petProfilesCompleted: (data) => {
          console.log('Pet profiles completed:');
          // Here you can trigger the API call to Parent
          sendProfileCompletionToParent(data);
        },
        close: () => {
          console.log('Embedded flow closed');
          document.body.style.overflowY = 'auto';
        },
      },
    });
  }, []);

  const sendProfileCompletionToParent = async (data: any) => {
    try {
      const response = await fetch('https://your-parent-api-endpoint.com', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });
      if (!response.ok) {
        throw new Error('Failed to send profile completion to Parent');
      }
      console.log('Profile completion sent to Parent successfully');
    } catch (error) {
      console.error('Error sending profile completion to Parent:', error);
    }
  };

  return (
    <div>
      <button id="your-button-selector">Open Embedded Flow</button>
    </div>
  );
};

export default YourComponent;
```

## Using in HTML

You can also use the library directly in an HTML file. Here is an example:

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Embedded Flow SDK Example</title>
    <script src="https://cdn.qa.petscreening.com/index.cjs.js"></script>
  </head>
  <body>
    <button id="open-embedded-flow">Open Embedded Flow</button>
    <script>
      document.addEventListener('DOMContentLoaded', function () {
        const ps = window.ps;
        ps.initEmbeddedFlow('#open-embedded-flow', {
          queryParams: {
            serviceProviderId: 5126,
            userFirstName: 'John',
            userLastName: 'Doe',
            userEmail: 'john.doe@example.com',
            userPhone: '1234567890',
            userAddress: '1234 Main St',
            userAddressContinued: 'Apt 2B',
            userCity: 'North Carolina',
            userState: 'NC',
            userZip: '27617',
            userMovingTime: 'not_moving',
          },
          callbacks: {
            close: () => {
              document.body.style.overflowY = 'auto';
            },
            petProfilesCompleted: (data) => {
              console.log('Pet profiles completed:', data);
            },
            onEvent: (data) => {
              console.log('Event received:', data);
            },
          },
        });
      });
    </script>
  </body>
</html>
```

## API

### `initEmbeddedFlow(selector: string, options?: Options): void`

Initializes the embedded flow by attaching a click event listener to the specified button.

- `selector`: The CSS selector for the button.
- `options`: Optional configuration options.

### `openIframe(options?: Options): void`

Opens the embedded iframe with the specified options.

- `options`: Optional configuration options.

### `buildUrl(options?: Options): string`

Builds the URL for the embedded iframe.

- `options`: Optional configuration options.
- Returns: The URL as a string.

### `setupMessageListener(options?: Options): void`

Sets up a message listener for communication between the parent window and the iframe.

- `options`: Optional configuration options.

### `closeIframe(): void`

Closes the embedded iframe.

## Options

The `Options` interface allows you to customize the behavior of the embedded flow.

```typescript
interface Options {
  overrideUrl?: string;
  queryParams?: {
    serviceProviderId?: number;
    userFirstName?: string;
    userLastName?: string;
    userEmail?: string;
    userPhone?: string;
    userAddress?: string;
    userAddressContinued?: string;
    userCity?: string;
    userState?: string;
    userZip?: string;
    userMovingTime?: string;
    [key: string]: string | number | undefined;
  };
  callbacks?: {
    onEvent?: (data: any) => void;
    close?: () => void;
    petProfilesCompleted?: (data: any) => void;
  };
}
```

- `overrideUrl`: A custom URL to override the default URL.
- `queryParams`: A record of query parameters to include in the URL, including user information.
- `callbacks`: A record of callback functions to handle events and closing the iframe.