Documentation Index

Fetch the complete documentation index at: https://docs.newstore.com/llms.txt

Use this file to discover all available pages before exploring further.

Used Newie, our AI search tool in the docs? Take a 2-minute survey to rate your experience!

Adding webview customizations using external authentication

Prev Next

When adding a webview to extend the capabilities of Associate App, external services or APIs used in this webview may require authentication. For this scenario, two different integrations are necessary.

Important

Do not hard-code credentials in the code of a webview.

Retrieving external authentication information

The Associate App offers a mechanism to retrieve an external authentication token after the user authenticated in the app. For that, use the Associate App configuration api to set the configuration for post_login_callback_urls.

For an example, see how you can use this property to set up the callback URL for loyalty rewards integration and the associated identity provider here.

{
  "external_integration": {
    "post_login_callback_urls": [
      {
        "identity_provider": "external-integration",
        "url": "https://integration.site/auth/callback"
      },
      {
        "identity_provider": "customer-loyalty",
        "url": "https://customer.loyalty.callback.url.com"
      }
    ]
  }
}

The API configured here will be invoked after every successful login in Associate App with a payload containing a NewStore identity auth token and device identifier.

{
  "device_id": "",
  "identity": "Bearer {NEWSTORE-JWT-TOKEN}"
}

In this API, first verify the NewStore token, then create a valid token for your third-party service. The endpoint is expected to return authentication identifier which will be handled as string and stored in the local storage of the app. NewStore does not care about the format, it can even be serialized data. An example implementation can be found here.

{
  "external_identity": "{TOKEN-VALID-FOR-EXTERNAL-PROVIDER}"
}

Note

It is recommended to set the token expiration time to 2 to 3 hours.

Using data in a webview component

After setting up a webview customization (Managing Associate App customizations ) for one of the offered locations, the given url will be loaded with (context-aware) data attached as hash. The data can be accessed using Javascript and we recommend having a fallback for local development:

function getContext() {
  const hash = window.location.hash.slice(1)
  if (!hash) {
    // fallback for local development
    return { contextProps: { formData: { email_optin: 'true' } } }
  }
  return JSON.parse(atob(decodeURIComponent(hash)))
}

const context = getContext()

Every data schema passed into a webview contain three default properties:

  • {associateId}: The ID of the associate currently using Associate App,

  • {storeId}: The ID of the store where the associate is currently operating in,

  • {cartId}: The ID of the current cart, if one was created.

The remaining schema of the data depends on the location. For customer profile extended attributes, the schema contains all extended attributes of the customer profile in contextProps.formData as key-value pairs.

{
  "contextProps": {
    "formData": {
      "emailOptin": "true",
      "favoriteColor": "Blue",
      "associateId": "1db2a42d-e872-432a-a87f-ed0964efc8d2",
      "storeId": "d4acdd7f-b6ef-4c4a-b229-250d60e745fb",
      "cartId": "d816a750-bcdf-4858-bca4-adca283be8b3",
    }
  },
"auth": {
    "accessToken": "newstore-platform-access-token"
  },
  "externalIdentities": [{
    "identityProvider": "external-integration",
    "identity": "secret-token-for-external-integration",
  },
  {
    "identityProvider": "customer-loyalty",
    "identity": "secret-token-for-customer-loyalty",
  }],
}

The More menu webview customization contains the following context properties.

{
  contextProps: {
    formData: {
      storeInfo: {
        label: string
        physicalAddress: {
          addressLine1: string
          addressLine2?: string
          province?: string
          state?: string
          zipCode?: string
          city?: string
          countryCode: string
          latitude?: number
          longitude?: number
        }
        shippingAddress?: {
          addressLine1: string
          addressLine2?: string
          province?: string
          state?: string
          zipCode?: string
          city?: string
          countryCode: string
          latitude?: number
          longitude?: number
        }
        divisionName?: string
        managerId?: string
        imageUrl?: string
        phoneNumber?: string
        activeStatus: boolean,
        supportedShippingMethods: Array<
          | 'traditional_carrier'
          | 'same_day_delivery'
          | 'in_store_pick_up'
          | 'in_store_handover'
        >
        giftWrapping: boolean
        pricebook?: string
        deliveryZipCodes: string[]
        shippingProviderInfo: {
          [key: string]: any
        }
        businessHours: Array<{
          fromTime: string
          toTime: string
          weekday: number
          earliestPickUp?: string
          latestPickUp?: string
        }>
        timezone: string
        taxId?: string
        taxIncluded: boolean
        queuePrioritization: Array<{
          priority: number
          shippingType:
              | 'traditional_carrier'
              | 'same_day_delivery'
              | 'in_store_pick_up'
              | 'DHL_PAKET'
          displayPriorityType?: 'priority' | 'normal'
        }>
        catalog?: string
        locale?: string
        storeId: string
        displayPriceUnitType: 'net' | 'gross'
      }
      userInfo: {
        id: string
        email: string
        firstName: string
        lastName: string
        telephoneNumber: string
        storeId: string
        imageUrl: string
        createdAt: Date
        updatedAt: Date
        isActive: boolean
        printerLocationId: string | null
        printerLocation?: {
            uuid: string
            name: string
        }
      }
      associateId: string
      storeId: string
      cartId: string
    }
  }
}

Webview customization examples

Webview: VanillaJS

<html lang="en">

<head>
  <title>Test App</title>
  <meta name="viewport" content="user-scalable=no, width=device-width">
  <script type="application/javascript">
    function getContext() {
      const hash = window.location.hash.slice(1)
      if (!hash) {
        // fallback for local development
        return { contextProps: { formData: { email_optin: 'true' } } }
      }
      return JSON.parse(atob(decodeURIComponent(hash)))
    }

    const context = getContext()
  </script>
  <style>
    label,
    input {
      display: block;
    }
  </style>
</head>

<body>
  <div id="extended-attributes"></div>
  <script type="application/javascript">
    if (context && context.contextProps) {
      const translation = {
        email_optin: 'Wants to receive emails from us'
      }

      Object.entries(context.contextProps.formData).map(function (entry) {
        const key = entry[0];
        const value = entry[1];
        const label = document.createElement("label");
        const input = document.createElement("input");
        label.innerText = translation[key] || key
        input.type = 'text'
        input.name = key
        input.value = value
        label.append(input)
        document.getElementById("extended-attributes").append(label);
      });
    }
  </script>
</body>

</html>

Webview: ReactJS

<html lang="en">

<head>
  <title>React Example</title>
  <meta name="viewport" content="user-scalable=no, width=device-width">
  <script type="application/javascript">
    function getContext() {
      const hash = window.location.hash.slice(1)
      if (!hash) {
        // fallback for local development
        return { contextProps: { formData: { loyalty_id: '1234567890' } } }
      }
      return JSON.parse(atob(decodeURIComponent(hash)))
    }

    const context = getContext()
  </script>

  <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <style>
    label,
    input {
      display: block;
    }
  </style>
</head>

<body>
  <div id="extended-attributes"></div>

  <script type="text/babel">
    if (context && context.contextProps) {
      const domContainer = document.querySelector('#extended-attributes');
      const root = ReactDOM.createRoot(domContainer);
      const translation = {
        loyalty_id: 'Loyalty ID'
      }

      const LabelledInput = function (props) {
        return (
          <label>
            {props.name}
            <input type="text" name={props.name} value={props.value} onChange={() => null} />
          </label>
        )
      }

      const Form = () => {
        return Object.entries(context.contextProps.formData).map(([name, value]) => (
          <LabelledInput key={name} name={translation[name] || name} value={value} />
        ))
      }

      root.render(<Form />);
    }
  </script>
</body>

</html>

Webview: Using external APIs

<html lang="en">

<head>
  <title>React Example</title>
  <meta name="viewport" content="user-scalable=no, width=device-width">
  <script type="application/javascript">
    function getContext() {
      const hash = window.location.hash.slice(1)
      if (!hash) {
        // fallback for local development
        return { contextProps: { formData: { 'External Customer ID': '2' } } }
      }
      return JSON.parse(atob(decodeURIComponent(hash)))
    }

    const context = getContext()
  </script>

  <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
  <script src="https://unpkg.com/@tanstack/react-query@4/build/umd/index.production.js"></script>
  <script src="https://unpkg.com/axios@0.27.2/dist/axios.min.js"></script>
  <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

  <style>
    label,
    input {
      display: block;
      font-family: Tahoma, serif;
      width: 100%;
    }

    label {
      color: #aaaaaa;
      margin-bottom: 1rem;
      font-size: 0.8rem;
    }

    input {
      font-size: 1rem;
      border: 1px solid #aaa;
      border-radius: 6px;
      padding: 8px;
    }
  </style>
</head>

<body>
  <div id="extended-attributes"></div>

  <script type="text/babel">
    if (context && context.contextProps) {
      const domContainer = document.querySelector('#extended-attributes');
      const root = ReactDOM.createRoot(domContainer);
      const queryClient = new window.ReactQuery.QueryClient();
      const QueryClientProvider = window.ReactQuery.QueryClientProvider;
      const useQuery = window.ReactQuery.useQuery;

      const external_user_id = context.contextProps.formData["External Customer ID"];