# SmartShopping SDK

## Installation

`npm i smartshopping-sdk`
`yarn add smartshopping-sdk`

## Integration

"storage", "tabs" and "alarms" permissions required

To use the `checkAdblockAndCookie` function in Manifest V3 need add "scripting" permission and host_permissions in manifest:

```
"host_permissions": [
  "*://*/*"
],
```

For Manifest V2 add "management" and "<all_urls>" permissions

### Background script

Background script of extension using **SmartShopping SDK** might look like this:

```
import { bootstrap } from 'smartshopping-sdk';
import { requirePromocodes } from '../utils';

(async () => {
  const { install, startEngine, setCodes, checkAdblockAndCookie } = bootstrap({
    clientID: 'demo',
    key: 'very secret key',
  });

  install();

  chrome.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
    if (changeInfo.status === 'complete') {
      startEngine(tabId);
    }
  });

  chrome.tabs.onReplaced.addListener(async (tabId) => {
    startEngine(tabId);
  });

  chrome.runtime.onMessage.addListener(
    async (message, sender, sendResponse) => {
      const tabId = sender?.tab?.id;
      if (!tabId) {
        return;
      }

      if (message.type === 'ready_to_CAA') {
        const codes = await requirePromocodes(tabId);
        if (codes.length) {
          setCodes(tabId, codes);
        }
      }

      if (message.type === 'check_adblock_cookie') {
        checkAdblockAndCookie()
          .then(({ isAdblockDisabled, isCookieEnabled }) => sendResponse({ isAdblockDisabled, isCookieEnabled }))
          .catch(err => console.log(err))
      }
    }
  );
})();
```

`bootstrap` takes three arguments – client ID, secret key (which is used for data decryption) and optional - serverUrl (not used by default). If you dont know your ID and secret key, contact **SmartShopping** tech support

It returns four functions:

- `install: () => Promise<void>` uploads and stores merchants data; also sets up message passing between background and content parts of **SmartShopping**.

- `startEngine: (tabId: number) => Promise<void>` identifies merchant in an active tab, uploads corresponding config and initializes `Engine` in a content script.
  Takes browser tab ID as an argument.

- `setCodes: (tabId: number, codes: string[]) => void` sends to the `Engine` a list of coupons
  Takes browser tab ID and array of promo codes as an arguments

- `checkAdblockAndCookie: () => Promise<{ isAdblockDisabled: boolean; isCookieEnabled: boolean; }>;` function checks if Adblock extension is disabled and checks if cookies are enabled and returns an object with two boolean keys.


In the example above `requirePromocodes` is a function defined by host extension, responsible for finding promocodes which will be passed to `setCodes`.

### Content Script

In a content script you need to import an `engine` instance:

```
import { engine } from 'smartshopping-sdk';

```

`engine` receives config object from background script and manages coupon autoapply flow or detect a successful coupon.

Coupon autoapply flow stages:

1. `engine.inspect()` – analyzing checkout page and collecting information;

2. `engine.apply()` – applying promocodes and storing results into internal execution context;

3. `engine.applyBest()` – choosing and applying best promocode;

4. `engine.productInspect()` – product information extraction;

All three stages can be executed consistently via `engine.fullCycle()`.

Detect stage - `engine.detect()` – detects if a user tried to apply a coupons;

The execution of the stages: `detect`, `apply`, `applyBest` can be aborted using the `engine.abort()` method.

Since it is up to you to show the modal window with the suggestion to apply coupons, call the `engine.notifyAboutShowModal()` method before it is shown so that we can get these statistics.

And if the user closes the modal prompting them to apply codes, call the `engine.notifyAboutCloseModal()` method.

When a user tries to apply a third-party coupon, you can show them a modal window notifying them that they are trying to apply a third-party coupon, to collect statistics you can use methods:

1. `notifyAboutShow3dPartyModal` - when opening the modal window;

2. `notifyAboutClose3dPartyModal` - when closing the modal window;

3. `notifyAboutReactivate3dParty` - when reactivating coupon (if you want to offer a user to reactivate a coupon);

Config object looks like this:

```
type EngineConfig {
  version: number;
  shopId: string;
  shopName: string;
  shopUrl: string;
  checkoutUrl: string;
  productUrl: string;
  inspect: Array<Command>;
  detect: Array<Command>;
  product: Array<Command>;
  apply: Array<Command>;
  applyBest: Array<Command>;
  selectorsToCheck: Array<string>;
}
```

- `version` – version of config spec for intrinsic usage

- `shopId` – merchant's unique ID in **SmartShopping** database

- `shopName` – merchant's name

- `shopUrl` – RegEx matching all merchant's URLs

- `checkoutUrl` – RegEx matching merchant's checkout page

- `productUrl` – RegEx matching merchant's product page

- `inspect`, `product`, `detect`, `apply` and `applyBest` – arrays of commands for respective stages of auto apply flow.

- `selectorsToCheck` – array of selectors which are necessary to proceed. If any of them are invalid, execution stops with an error.

`engine` stores info in the following properties:

1. `config: EngineConfig`
   Config object
2. `progress: EngineProgress`
   Execution status

```
 type EngineProgress =
  | 'IDLE'
  | 'INSPECT'
  | 'INSPECT_END'
  | 'DETECT'
  | 'APPLY'
  | 'APPLY_END'
  | 'APPLY-BEST'
  | 'PRODUCT'
  | 'APPLY-BEST_END'
  | 'ERROR'
  | 'CANCEL';
```

3. `checkoutState: EngineCheckoutState`
   Info collected during `inspect` stage
   `checkoutState.total` – cart total before applying any codes

```
type EngineCheckoutState {
  total: number | null;
}
```

4. `finalCost: EngineFinalCost`
   This object contains pairs `"promocode": "cart total after applying promocode"`

```
type EngineFinalCost = { [key: string]: number }
```

5. `promocodes: Array<string>`
   Array of promocodes for testing
6. `currentCode: string`
   Promocode currently being processed
7. `detectState: EngineDetectState`
   Info collected during `detect` stage
   `detectState.userCode` - promocode entered by the user. If `detectState.userCode === 'UNDEFINED_CODE'` it means that we can't get the value of the code, but we know that it was entered
   `detectState.isValid` - validity of the code entered by the user. `true` if the code is successfully applied and `false` if the entered code turned out to be erroneous

  ```
  type EngineDetectState {
    userCode: string | 'UNDEFINED_CODE';
    isValid: boolean;
  }
  ```

8. `bestCode: string`
   Most profitable promocode
   If none of the codes worked, `bestCode === ''`
9. `checkout: boolean | null`
   The flag is null if you are on a page for which we don't have a config yet. In other cases, the flag has a boolean value and indicates whether you are on the checkout page
10. `productState: EngineProductState`
       Info collected during `product` stage
       `productState.title` – Product name
       `productState.fullPrice` – Full Price
       `productState.discountPrice` – Discount Price
       `productState.sku` – SKU
       `productState.imageUrl` – Image URL
       `productState.variants` – Variants

```
type EngineProductState {
  title: null | string;
  fullPrice: null | number;
  discountPrice: null | number;
  sku: null | string;
  imageUrl: null | string;
  imageUrl: null | string;
  variants: EngineProductVariantGroup[];
}

interface EngineProductVariantGroup {
  label: string;
  items: EngineProductVariantValue[];
}

interface EngineProductVariantValue {
  label: string;
}
```

You can subscribe to those properties' changes via `engine.subscribe()`...

```
const unbinders = engine.subscribe(
    {
      config: configListener,
      checkoutState: checkoutStateListener,
      finalCost: finalCostListener,
      promocodes: promocodesListener,
      progress: progressListener,
      currentCode: currentCodeListener,
      detectState: detectStateListener,
      bestCode: bestCodeListener,
      checkout: checkoutListener,
      product: productListener,
    }
);
```

...and unsubscribe via `engine.unsubscribe()`, passing return value of `engine.subscribe()` as an argument:

```
engine.unsubscribe(unbinders);
```

Subscribing for properties is optional so you may only choose ones you need and skip the rest:

```
const unbinders = engine.subscribe(
    {
      progress: listener1,
      currentCode: listener2,
      bestCode: listener3,
      checkout: listener4,
    }
);
```

`listener` - functions look like this:

```
listener: (value: %property_type%, state: EngineState) => void;

interface EngineState {
  checkoutState: EngineCheckoutState;
  productState: EngineProductState;
  finalCost: EngineFinalCost;
  progress: EngineProgress;
  config: EngineConfig;
  promocodes: Array<string>;
  detectState: EngineDetectState;
  bestCode: string;
  currentCode: string;
  checkout: boolean | null;
}
```

You can see more in our [demo-extension](https://github.com/cupcakedev/smartshopping-demo)

# Changelog

<details>
  <summary>All notable changes to this project will be documented in this.</summary>

  ## [0.8.5] - 2025-06-19

  ### Update
 
  - Update api url

  ## [0.8.4] - 2025-05-30

  ### Fixed
    
  - Fixed duplicated variants data

  ## [0.8.3] - 2025-05-28

  ### Fixed
    
  - Added support variants for manifest v3

  ## [0.8.2] - 2025-05-27
    
  ### Added

  - Added support product page variants

  ## [0.8.1] - 2025-05-16

  ### Added   

  - Added support product page parsing

  ## [0.8.0] - 2025-02-28

  ### Added

  - Added support SmartShopping promocodes

  ## [0.7.0] - 2023-11-16

  ### Changed

  - Engine initialization

  ### Added

  - Added support for user configs
  - Extended error log
  - SDK version in server request
  - Methods for 3d party coupons

  ## [0.6.11] - 2023-07-10

  ### Added

  - Added Edge supports for adblock check

  ## [0.6.10] - 2023-07-06

  ### Update

  - Update documentation
  - Update @types/chrome package

  ## [0.6.9] - 2023-06-29

  ### Added
  - Export 'AdblockAndCookieOutput' type

  ## [0.6.8] - 2023-06-23

  ### Changed
  - Now the checkout state is of type `boolean | null`. If we don't support the merchant from the page, we return `null` in the `checkoutListener`

  ## [0.6.7] - 2023-06-21

  ### Fixed
  - `checkAdblockAndCookie` for use with Manifest V3
  - Error in console
  - Update documentation
  
  ## [0.6.6] - 2023-06-15

  ### Fixed
  - Error in console

  ## [0.6.5] - 2023-06-15

  ### Added
  - Export `checkAdblockAndCookie` function 

  ## [0.6.4] - 2023-05-29

  ### Added
  - Export `BootstrapOutput` type

  ## [0.6.3] - 2023-05-26

  ### Fixed
  - Optimizing the size of the data requested from the server

  ## [0.6.2] - 2023-03-03

  ### Changed
  - Change method name from `sendСodes` to `setCodes`

  ## [0.6.1] - 2023-02-27

  ### Added
  - README.md file with Changelog

  ## [0.6.0] - 2023-02-16

  ### Added
  - `sendCodes` method

  ### Changed
  - Remove `codes` param from `startEngine` method

  ## [0.5.21] - 2023-01-24

  ### Fixed
  - Cache work
</details>
