SDK Methods

The latest release of @deliveryhero/vendor-portal-sdk is 1.62.0.

The codebase is here.

The following methods are available to plugin authors when importing @deliveryhero/vendor-portal-sdk.

getAllVendors()

All vendors of the current user

[{
  "id": "FP_MY;n6no",
  "vendorId": "n6no",
  "globalVendorId": "45EJS8",
  "globalEntityId": "FP_MY",
  "name": "Skippy's BBQ",
  "chainId": "",
  "chainName": "",
  "billingParentId": "",
  "creationDate": "1530623935",
  "businessType": "marketplace",
  "verticalType": "restaurants",
  "keyAccount": false,
  "deliveryTypes": [
    "platform_delivery",
    "pickup"
  ],
  "timezone": "Asia/Kuala_Lumpur",
  "currency": {
    "code": "MYR",
    "symbol": "RM"
  },
  "address": {
    "country": {
      "name": "MY",
      "code": "MY"
    },
    "city": {
      "name": "Kuala Lumpur"
    }
  },
  "accountType": "Branch",
  "isConcept": false,
  "transmission": {
    "methods": [
      {
        "type": "string",
        "is_primary": true,
        "global_transmission_method_id": "string"
      }
    ]
  }
}]

getCurrentVendor()

Vendor metadata that is extracted on application initialisation. This is just for ad-hoc use, you should rather use the addVendorObserver() method, as this triggers every time the current vendor changes.

{
  "id": "TB_KW;12942",
  "vendorId": "12942",
  "globalVendorId": "4O0WIX",
  "globalEntityId": "TB_KW",
  "name": "Skippy's BBQ",
  "chainId": "6612",
  "chainName": "OVENLY",
  "billingParentId": "",
  "creationDate": "1481631224",
  "businessType": "marketplace",
  "verticalType": "restaurants",
  "keyAccount": false,
  "deliveryTypes": [
    "vendor_delivery"
  ],
  "timezone": "Asia/Riyadh",
  "currency": {
    "code": "KWD",
    "symbol": "KD"
  },
  "address": {
    "country": {
      "name": "KW",
      "code": "KW"
    },
    "city": {
      "name": "hawally"
    }
  },
  "accountType": "Branch",
  "isConcept": false,
  "transmission": {
    "methods": [
      {
        "type": "string",
        "is_primary": true,
        "global_transmission_method_id": "string"
      }
    ]
  }
}

addVendorObserver(cb: (vendor: IVendorData) => void): () => void

Adds a vendor observer with a callback that is triggered on currentVendor change. This callback function is also triggered initially when the observer is added, so you don't need to use getCurrentVendor() separately. The callback is called with one argument which is the same as the return value of getCurrentVendor().

This function is returning a disposer function with no arguments, that when called removes the listener. This needs to be done when you add the listener in a non singleton scope.

getSelectedVendors()

Array of Vendor metadata that represents the currently user selected vendors. This is just for ad-hoc use, you should rather use the addSelectedVendorsObserver() method, as this triggers every time the selected vendors change.

[{
  "id": "TB_KW;12942",
  "vendorId": "12942",
  "globalVendorId": "4O0WIX",
  "globalEntityId": "TB_KW",
  "name": "Skippy's BBQ",
  "chainId": "6612",
  "chainName": "Skippy's CHAIN",
  "billingParentId": "",
  "creationDate": "1481631224",
  "businessType": "marketplace",
  "verticalType": "restaurants",
  "keyAccount": false,
  "deliveryTypes": [
    "vendor_delivery"
  ],
  "timezone": "Asia/Riyadh",
  "currency": {
    "code": "KWD",
    "symbol": "KD"
  },
  "address": {
    "country": {
      "name": "KW",
      "code": "KW"
    },
    "city": {
      "name": "hawally"
    }
  },
  "accountType": "Branch",
  "isConcept": false,
  "transmission": {
    "methods": [
      {
        "type": "string",
        "is_primary": true,
        "global_transmission_method_id": "string"
      }
    ]
  }
}]

addSelectedVendorsObserver(cb: (vendors: IVendorData[]) => void): () => void

Adds an observer for vendors selected by the user. It has a callback that is triggered on changes in selectedVendors. This callback function is also triggered initially when the observer is added, so you don't need to use getSelectedVendors() separately. The callback is called with one argument which is the same as the return value of getSelectedVendors().

This function is returning a disposer function with no arguments, that when called removes the listener. This needs to be done when you add the listener in a non singleton scope.

getLocale()

getLocale(); // "en"

getAuthToken()

This hook returns the KEYMAKER TOKEN. This SDK method of fetching the auth token helps the use cases where one might need to create their own kind of API client and handle the refresh mechanism on their own.

  import { useSdk } from '@deliveryhero/vendor-portal-sdk';

  const { getAuthToken } = useSdk();

  const keymakerToken = getAuthToken();

Output:

{
   "accessToken": "header.payload.signature",
   "tokenType": "Bearer"
}

refreshToken()

This hook refreshes the auth tokens. This method returns a promise.

setTranslation(translations: Object, translationUrl?: string)

Defines translations for your plugin so that they can be used with the t function.

setTranslation({
  "main_page.title": "Welcome to my plugin!"
})

Then you can use t("my_plugin_code.main_page.title") which will evaluate to Welcome to my plugin!. Note that the translation keys get prefixed with the plugin code automatically for you.

Please keep in mind that the values of the objects need to be strings. Otherwise it will throw an error and the page will can be blank and show nothing.

Translation keys can also be defined with parameters, allowing to define dynamic texts, for example:

setTranslation({
  "main_page.title": "Welcome to my plugin, {userName}!"
})

For more information to how to use the t() function, look in the next section (including examples of how to use parameters).

Using translationsUrl

Optionally you can define a translationUrl as second argument of the function. This URL is then used to fetch translations. The translations need to be in JSON format. If the portal can't find the user locale (404 response) it will try to fetch the default locale (en). So please make sure, that there always is an English locale file available.

The URL should include the {locale} placeholder (which resolves to the users locale, e.g. 'en' or 'de'). When the translationUrl is set, the translations object from the first argument is used as a fallback.

setTranslation({}, 'https://my-translations.com/translations/{locale}.json');

The content of the fetched JSON file follows the same rules as for the translations object described above.

t(key: string, params?: Record)

Translation helper. The portal has a list of global translation strings, ready to us by any plugin.

t("global.button.logout"); // "Log out"

You can also have parameterized translations:

// example: my_plugin_code.greeting = "Welcome, {name}!"
t("my_plugin_code.greeting", { name: "John" });

This will replace every occurance of {name} with "John". You also can have multiple parameters:

// example: my_plugin_code.greeting = "Welcome, {name}! Your age is {age}."
t("my_plugin_code.greeting", { name: "John", age: 31 });

In addition, if your plugin supports different translations for different platforms, you can just add these translations as my_plugin_code.translation_key.{PLATFORM_KEY}.

As long as there is a value for my_plugin_code.translation_key, the function will try to translate it for {PLATFORM_KEY}. This means that, in the app, all you need to do is use your translations without the {PLATFORM_KEY}

In order to grasp the meaning of the above paragraph, let's assume that the platform is LH_DE and the translations have the following values;

my_plugin_code.joker = "Joker"

my_plugin_code.joker.LH_DE = "Tick Tack"

t("my_plugin_code.joker") // returns Tick Tack

if we had the following values with LH_DE platform;

my_plugin_code.joker = "Joker"

t("my_plugin_code.joker") // returns Joker

if we had the following values with TB platform;

my_plugin_code.joker = "Joker"

my_plugin_code.joker.LH_DE = "Tick Tack"

t("my_plugin_code.joker") // returns Joker

if we had the following values with LH_DE platform;

my_plugin_code.joker.LH_DE = "Tick Tack"

t("my_plugin_code.joker") // returns my_plugin_code.joker

getBaseRoute()

Provide to the plugin the full URL of the

{
    "route": "/hello-world"
}

setNewNavigation(parameters: INewNavigationParameters)

Sets the navigation configuration for the Portal.

setNewNavigation({
  name: "Orders",
  title: "Your Plugin Name", // Already translated field
  // The following properties should be set to this example if you navigate deeper into your plugin
  hasBackButton: true,
  showAppBarPlugins: false,
  showBottomNav: false,
  hasUnsavedChanges: () => Promise.resolve(someLogic()),
  filters: [<ListFilter
    items={[...filterValues]}
    value={selectedFilterValues}
    onChange={(newValues) => /* do something with the selected value(s) */}
  />],
})
Property Type Description Default Required
name String The name for your page (internal) Yes
title String Title shown in the top application bar Yes
hasBackButton Boolean Will exchange the menu icon on the top left with a back button functionality false
showAppBarPlugins Boolean Will remove possible AppBar plugins on the top-right (like restaurant status) true
showBottomNav Boolean Defines if the bottom navigation is visible true
hasUnsavedChanges () => Promise(Boolean) If a function is passed and it resolves to true, prevents the user from going back by prompting him with a confirmation modal, asking if he's sure about wanting to go back.
fixed Boolean Having this property set to true will have the app bar in a fixed state so it always stays at top. false
backUrl String This property is used when the back button is hit.
actionButtons React.ComponentType In case there are components that need to appear on bottom right of the app bar, this property could be used to provide the components.
stickyContent React.ComponentType When you want to have permanently visible content at the top of the page (like a scrolling navigation), you should pass the component to render this content here.
filters React.ComponentType[] List of filter components (look here for more info) to show in the portal SecondaryBar. Should not include RestaurantFilter as this is already provided by the portal.

We also build a router utils library to make working with the setNewNavigation() easier.

You can find it here (alongside with documentation).

getPlugins()

Returns an array of plugins the current user has.

[
   {
      code: 'MY_PLUGIN_CODE',
      route: '/super-plugin',
      type: 'MENU', // Shows in the menu
   },
   {
      code: 'MY_OTHER_PLUGIN_CODE',
      route: '/other-plugin',
      type: 'UNLISTED', // Is not listed in the menu
   },
]

This is helpful when you want to link a different restaurant or want to show accompanying information depending if the plugin is available or not.

getUserData(prop: string)

Retrieves user data from the session.

getUserData("email")

The fields that you can retrieve from the user are: locale, name, email, userId, operatorCode, role.

isUserLoggedIn()

Returns true or false based on the current user session.

isUserLoggedIn()

createGlobalApiV2(tokenExpiredCondition?: ({ res: Response, payload: any }) => boolean)

This introduces a new API client which adds Keymaker token as the authorization header.

Returns a preconfingured instance of API. By default it will automatically insert to all requests the portal authorization header and handle the refreshing token procedure.

  import { useSdk } from '@deliveryhero/vendor-portal-sdk';

  const { createGlobalApiV2 } = useSdk();

  const httpClient = createGlobalApiV2();

  httpClient.fetch('/your-api-endpoint', 200, {
    method: 'POST',
    body: {
        message: 'Hallo!',
    },
  });

The following HTTP response will make the API refresh the JWT token and queue the requests until the API is ready again to resend authenticated requests:

{
   res: {
      status: 401
   },
   payload: {
      code: "TOKEN_EXPIRED"
   }
}

In order to the change the condition to trigger the token refreshing procedure the tokenExpiredCondition param should be used. Ex.:

const api = createGlobalApiV2(({ res, payload }: { res: Response, payload: any }) => {
   return res.status === 401 && payload.code === "TOKEN_EXPIRED";
});

getRestaurantDateParts(date: Date)

Receives a Date object and returns a DateParts object that contains the following number values in the restaurant timezone:

  • year: full year (e.g. 2019)
  • month: number of the month (starting with 1 for January)
  • day: number of the day (starting with 1)
  • hours: hours in 24h format
  • minutes
  • seconds
// Let's say the date is: 2019-12-24 01:23:40
const date = new Date();

const dateParts = getRestaurantDateParts(date):
// RETURNS:
// {
//    year: 2019,
//    month: 12,
//    day: 24,
//    hours: 1,
//    minutes: 23,
//    seconds: 40,
// }

With this function you make sure the restaurant timezone is applied even when the device is in a different timezone.

getRestaurantDateString(date: Date)

Receives a Date object and returns a string describing the date in the restaurant timezone in the format: YYYY-MM-DD. With this function you make sure the restaurant timezone is applied even when the device is in a different timezone.

// Let's say the date is: 2019-12-24 13:23:40
const date = new Date();

const dateString = getRestaurantDateString(date):

if (dateString === "2019-12-24") {
   alert('its christmas');
}

getRestaurantTimeString(date: Date)

Receives a Date object and returns a string describing the time in the restaurant timezone in the format: HH:mm. With this function you make sure the restaurant timezone is applied even when the device is in a different timezone.

// Let's say the date is: 2019-12-24 13:23:40
const date = new Date();

const timeString = getRestaurantDateString(date):

assert(timeString === "13:23");

getRestaurantTimeStringWithSeconds(date: Date)

Receives a Date object and returns a string describing the time in the restaurant timezone in the format: HH:mm:ss. With this function you make sure the restaurant timezone is applied even when the device is in a different timezone.

// Let's say the date is: 2019-12-24 13:23:01
const date = new Date();

const timeString = getRestaurantDateString(date):

assert(timeString === "13:23:01");

getFwfClient()

Returns the Fun with Flags client for your plugin. If there is no client configured this function will return undefined. To configure it properly the portal team needs to receive a clientID for a FWF environment from you. After this clientID is added, this function will receive a preconfigured instance of the FWF.

Following entries are added to the user context and can be used for feature flag rules:

  • userId: The user ID prepended with the user type (e.g. master-123, user-12515).
  • country: The country of the user.
  • vendorsCount: The amount of platform vendors the user is added to, helpful for performance related feature flags.
  • platformIdentifiers: A string of platform vendors (for more information about this, see below).
  • verticalTypes: A string of all the vertical types of the platform vendors
  • role: A string of the role of the user
  • subject: A string of the user id in radmin and it's region. ex: eu-129312
  • isConcept: boolean; it returns true if at least one of the given user attached vendors' isConcept flag is true (accountType = "DH_CONCEPT")

The userId either contains:

  • the master ID with master- prefixed
  • or the RPS restaurant ID with user- prefixed

The platformIdentifiers string is written in a specific format GLOBAL_PLATFORM_ID;PLATFORM_RESTAURANT_ID,GLOBAL_PLATFORM_ID;PLATFORM_RESTAURANT_ID;....

It is a list of platform restaurants separated by comma. The restaurant itself has two parts separated by semicolon:

  • Global Entity ID (e.g. FP_TW, OP_SE, TB_AE)
  • Platform related vendor ID

Example FP_SG;v321.

In FWF you then can use the contains operator for a rule and either setup a rule:

  • for a whole platform by writing only the Global Platform ID
  • for a specific restaurant by writing the whole restaurant representation (e.g. FP_TW;v8xT)
const fwfClient = getFwfClient();

if (fwfClient) {
   // FWF client is configured
} else {
   // client ID is not known to us and we haven't created a client for this plugin
}

getSubject()

Returns the Subject of the current session. Either is Subject.USER for a normal user, or Subject.IMPERSONATOR. This is helpful to hide or show UI for impersonated users (like hiding or graying out input sections).

Example:

import { getSubject, Subject } from '@deliveryhero/vendor-portal-sdk';

if (getSubject() !== Subject.IMPERSONATOR) {
   // Show or do something for everyone that is not impersonated
}

logger

A collection of different logger functions. Following functions are available.

  • log
  • debug
  • info
  • warn
  • error

All logger functions follow the following interface:

logger.info('message', { 
   context: 'object',
});

Where the first argument is the logging message as a string and the second argument is an optional context object. The context object contains metadata about the log (it can be seen on the log details view in Datadog). The context object can contain everthing a JSON object can contain (e.g. strings, numbers, arrays, objects, etc.).

Fetch Portal FwF Variation

Method Signature:

  getPortalFwfVariation<T extends string>(
      _name: T,
      _fallbackValue: boolean | string,
      _forced: boolean
  ): Promise<VariationMap<T>> {
    ...
  }

This method helps getting information and the value of a Fun With Flags Variation that the webapp portal uses for toggling features.

Example:

  import { getPortalFwfVariation } from '@deliveryhero/vendor-portal-sdk';

  getPortalFwfVariation('use-sidebar-categorisation')
    .then((res) => console.log(res));
{
  "use-sidebar-categorisation": {
    "variation": true,
    "abTest": false,
    "mabTest": false,
    "explanation": {
      "kind": "TARGET"
    },
    "relevantContext": "googleClientId,userId,email",
    "trackInfo": {
      "variationName": true,
      "flagType": "boolean",
      "flagEnabled": true,
      "trackerServices": [
        "FWF_GTM_DATA_LAYER_TRACKER"
      ]
    }
  }
}

Fetch Multiple Portal FwF Variations

Method Signature:

  export function getPortalFwfVariations<T extends string>(
      _flags: T[]
  ): Promise<VariationMap> {
      ...
  }

This method helps getting information and the value of multiple Fun With Flags Variations that the webapp portal uses for toggling features.

Example:

  import { getPortalFwfVariations } from '@deliveryhero/vendor-portal-sdk';

  getPortalFwfVariations(['feature-flag', 'use-new-header'])
    .then((res) => console.log(res));
{
    "feature-flag": {
        "variation": true,
        "abTest": false,
        "mabTest": false,
        "explanation": {
            "kind": "DEFAULT_RULE"
        },
        "relevantContext": "",
        "trackInfo": {
            "variationName": true,
            "flagType": "boolean",
            "flagEnabled": true,
            "trackerServices": [
                "FWF_GTM_DATA_LAYER_TRACKER"
            ]
        }
    },
    "use-new-header": {
        "variation": true,
        "abTest": false,
        "mabTest": false,
        "explanation": {
            "kind": "DEFAULT_RULE"
        },
        "relevantContext": "",
        "trackInfo": {
            "variationName": true,
            "flagType": "boolean",
            "flagEnabled": true,
            "trackerServices": [
                "FWF_GTM_DATA_LAYER_TRACKER"
            ]
        }
    }
}

Fetch Value of a Portal FwF Variation

Method Signature:

  export function getPortalFwfVariationValue<T extends string>(
      _name: T,
      _fallBackValue?: boolean | string,
      _forced?: boolean
  ): Promise<boolean | string> {
      ...
  }

This method helps getting only boolean value of a Fun With Flags Variation that the webapp portal uses for toggling features.

Example:

  import { getPortalFwfVariationValue } from '@deliveryhero/vendor-portal-sdk';

  getPortalFwfVariationValue('use-new-header')
    .then((res) => console.log('use-new-header: ', res));
  use-new-header: true

React helper

Additionally to the SDK, there are some helpers that make working with the SDK in a react component a bit easier.

useSdk()

Hook that returns the whole SDK as an object including all methods.

import React from 'react';
import { useSdk } from '@deliveryhero/vendor-portal-sdk';

function MyComponent() {
   const { getCurrentVendor } = useSdk();
   const currentVendor = getCurrentVendor();
   return <div>{currentVendor.name}</div>;
}

This makes it easy to test and mock the SDK as you can use the

import React from 'react';
import { render } from '@testing-library/react';
import { SdkProvider } from '@deliveryhero/vendor-portal-sdk';
const mockSdk = {
   getCurrentVendor: jest.fn(() => { /* Return fake vendor */ })
};

test('Render component', () => {
   const { container } = createComponent();
   expect(container).toMatchSnapshot();
});

function createComponent() {
   return render(
      <SdkProvider sdk={mockSdk}>
         <MyComponent />
      </SdkProvider>
   );
}

withSdk(Component)

HoC version of useSdk(). Injects the sdk of useSdk() as props.

import React from 'react';
import { withSdk } from '@deliveryhero/vendor-portal-sdk';

export const MyComponent = ({sdk}) => {
   const currentVendor = sdk.getCurrentVendor();
   return <div>{currentVendor.name}</div>;
};
export default withSdk(MyComponent);

For testing you can just inject the mock SDK as props to the unwrapped component (in above case the MyComponent named export).

useCurrentVendor()

Hook that returns the current vendor (when using RestaurantSelectorOptions.SINGLE_VENDOR). Rerenders the component when the vendor changes (uses addCurrentVendorObserver internally).

import React from 'react';
import { useCurrentVendor } from '@deliveryhero/vendor-portal-sdk';

export const MyComponent = () => {
   const currentVendor = useCurrentVendor();
   return <div>{currentVendor.name}</div>;
};

useSelectedVendors()

Hook that returns the selected vendors (when using RestaurantSelectorOptions.MULTIPLE_VENDORS). Rerenders the component when the vendor changes (uses addSelectedVendorObserver internally).

import React from 'react';
import { useSelectedVendors } from '@deliveryhero/vendor-portal-sdk';

export const MyComponent = () => {
   const selectedVendors = useSelectedVendors();
   return <div>{useSelectedVendors.map(vendor => <div>{vendor.name}</div>)}</div>;
};

results matching ""

    No results matching ""