Getting started

Overview

After following the instructions below, your html will look approximately like this:

<html>

<head>
  ...
  <link rel="stylesheet" type="text/css" href="path-to-domain-name/3.0/css/appsmiles-sdk.css" />
  <link rel="stylesheet" type="text/css" href="path-to-domain-name/3.0/css/appsmiles-icons.css" />
  ...
</head>

<body>
  ...
  <div id="apm-sticker"></div>
  ...

  ...
  <script>
    var apmConfig = {...} // Required
  </script>
  <!-- or <script src="path/to/apm-config.js"></script> -->

  <script>
    var apmTranslations = {...} // Optional
  </script>
  <!-- or <script src="path/to/apm-translations.js"></script> -->

  <script src="path-to-domain-name/3.0/js/appsmiles-sdk.js"></script>
  ...
</body>

</html>

1. Style

The SDK comes with it's own stylesheet (1st line) and icon font (2nd line). All of the class and ids are prefixed with apm- in order to prevent interference in the global scope.

<link rel="stylesheet" type="text/css" href="path-to-domain-name/3.0/css/appsmiles-sdk.css" />
<link rel="stylesheet" type="text/css" href="path-to-domain-name/3.0/css/appsmiles-icons.css" />

Don't hesitate to contact us for style changes you would like to do.

If you wish to change the style yourself, read the instructions below:

SCSS methodologies

Our CSS is compiled from SCSS and is organized through the use of two methodologies: ITCSS and BEM.

  • ITCSS Architecture. This allows to have a clearer hierarchy and css priority (the lower the CSS is, the higher its priority) and is useful to prevent the use of !important. It consists in organizing the files in the following order:

    • 1-settings

    • 2-tools

    • 3-generic

    • 4-elements

    • 5-objects

    • 6-components

    • 7-themes

    • 8-utilities (Trumps)

  • CSS class names follow the naming convention of BEM (Block__Element--Modifier). This convention may seem obscure at the beginning, but it allows for consistent class names.

    • .apm-page__button--state-success

    • .apm-page: Block

    • __button: Element

    • --state-success: Modifier

  • Each HTML element will often have a general class name (shared by multiple elements on different pages) and a unique class (so that we can modify the style of this element only). This allows for both refactoring and detail work.

    • <img class="apm-gift__image apm-media__image" src="..."/>

      • apm-media__image is a general class name and apm-gift__image is the unique class.

SCSS variables

Our SCSS was written in a way so that the SDK can be customized by duplicating and modifying the file 1-settings/s-base.scss. This file is a mastersheet of SCSS variables which are used in all of the other SCSS files. It is thus possible to change the look and feel of the SDK only by modifying this file. It is always possible to do fine-grain work by editing the SCSS lower in the hierarchy.

These SCSS variables also follow the BEM naming convention. This is an example of how SCSS variables are organized in 1-settings/s-base.scss:

  • Generic variables (located on top, reused by variables below within s-base.scss).

    • ex: $font-family--default: Arial;

  • Object variables

    • ex: $button--default--font-family: $font-family--default;

  • Component variables

    • ex: $welcome__button-connect--font-family: $button--default--font-family;

This hierarchy in the variables creates a cascade of reference, but allows to modify multiple elements related to each other, by changing just one variable.

Examples

  • $font-family--header will change the font for all headers

  • $color--primary, since it is used by other SCSS variables or by the style directly, will modify button colors, the tab background colors, etc ...

2. Configuration (apmConfig)

In order for the SDK to work properly, you will need to add the following Javascript variable before loading it. It is possible to externalize this variable into it's own JS file, as long as it is called before the SDK.

<script>
      var apmConfig = {    
        // Required
        apiBaseUrl: "[Required] {string} URL of the appsmiles API, modify to point to sandbox or prod environment",
        clientSignature: "[Required] {string} Signature created with HmacSHA256(timestampString, partnerSecretString).toString().substring(0, 15).toUpperCase();",
        clientSignatureTimestamp: "[Required] {number} Timestamp used for client signature creation",
        defaultSticker: "[Required] {string} Absolute or relative url of the image to use for the default sticker",
        lang: "[Required] {string} Language in which the SDK should be. 2 letter country code.",
        mediaBaseUrl: "[Required] {string} URL of the appsmiles media folder, modify to point to sandbox or prod environment",
        openFrom: "[Required] {string} Direction from which the SDK should open (N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW, C)",
        partnerId: "[Required] {string} ID of the partner in the appsmiles back-office",
        ribbonDirection: "[Required] {string} Direction towards which the notification ribbon should extend (N, E, S, W)",
        closeIconPosition: "[Required] {string} Position of the close icon. Same values than openFrom. Keep empty field (default) to hide."
        // Conditional (scenario specific)
        partnerClientId: "[Conditional] {string} ID of the user according to the integrator's database",
        segments: "[Conditional] {JSON} JSON of the user's segment",
        showBalanceInSticker: "[Conditional] {boolean} Whether to show the user's balance in the sticker",
        // Optional
        loginPageUrl: "[Optional] {string} Url of the sign-in/sign-up page",
        hideStickerIfOptOut: "[Optional] {boolean} Hide sticker if user chooses to opt out. Default is false. Consider to integrate a way for the user to opt back in if you choose to set it to true",
        hideStickerIfSdkOff: "[Optional] {boolean} Hide sticker if SDK is set to OFF or if non-functional. Default is false",
        timeoutDuration: "[Optional] {number} Number of seconds to wait before http requests enter timeout",
        notificationDuration: "[Optional] {number} Duration in ms of the notification (including animation)",
        generosityBufferPolicy: "[Optional] {number} Policy as to how to handle triggerActions when user is not connected. -1: always discard, -2: keep after userClientConnect is called (while waiting for server response or RGPD opt-in), 0: always keep, x > 0: keep only actions in the last x seconds after user connected.",
        stickerNotificationBadgePolicy: "[Optional] {number} Policy as to when to show the notification badge on the sticker. -1: never show, 0: always show, 1: show when new content available"
        // Development
        openAndLoad: "[Dev] {string} Have modal open on a specific screen on refresh (welcome, challenge, menu, gifts, gift, burns, burn, earns, levels, pages-mentions, pages-onboarding-anonymous, pages-onboarding-connected)",
        overrideProgTTL: "[Dev] {number} Override progTTL (automatic refresh interval), in seconds",
        alwaysNewAction: "[Dev] {boolean} Always get a new action to suggest to the user (otherwise, only new action if user completed the current one)",
        clearApmCacheOnRefresh: "[Dev] {boolean} Clear localStorage and sessionStorage for any apm keys",
        resetOptInOnRefresh: "[Dev] {boolean} Reset opt in on refresh to see onboarding with buttons everytime",
        enableLogs: "[Dev] {boolean} Enable logs, overrides the appsmiles debugMode setting",
        logLevel: "[Dev] {string} Set log level (verbose, debug, info, warn, error)",
        forceCookies: "[Dev] {boolean} NOT IMPLEMENTED YET. Force use of cookies instead of WebStorage (if SDK should appear across sub domains)",
        forceRefresh: "[Dev] {boolean} Bypass the waiting time (default: 5 minutes) between API checks on page reload",
        forceDeeplink: "[Dev] {string} Force the deeplink button to appear and to redirect to link in variable",
      }
</script>

How to generate clientSignature

In order to enforce a higher degree of security. Our SDK uses JSON Web Tokens (JWT) to encrypt requests sent to our server. In order to do this, we need a signature. However, since Javascript code is completely visible to the client, the signature must be generated by your server.

!!! This signature must be generated server-side, in the language your server uses (e.g. PHP, AngularJS, Ruby). You then add the generated string in the apmConfig.clientSignature object

Signature type: HMAC SHA-256. Arguments: timestamp (current time) and the partnerSecret key that we will have provided to you. Post-processing: Keep first 15 characters and convert to uppercase.

Javascript example:

HmacSHA256( // using CryptoJS library
  timestamp: string, // ! Very important for it to be a STRING
  partnerSecret: string)
  .toString()
  .substring(0, 15)
  .toUpperCase();

3. Translations (apmTranslations)

Our SDK comes with a default French translation. If you wish to change it, or add more languages, you can use the global variable apmTranslations so that the SDK can access them, and use apmConfig.lang to point to the correct translation.

See the default translations.js file provided.

Note: If keys are missing, error logs will be printed in the browser's console, indicating which ones are missing.

4. The SDK

Insert the link to the SDK that we have provided to you here.

<script src="path-to-domain-name/{version}/appsmiles-sdk.js"></script>

5. HTML anchor

The SDK will look for a div with the id 'apm-sticker' like so : <div id="apm-sticker"></div>. Place this anchor where you wish the button to appear.

Be sure to configure $modal--open-direction and $notif-direction in the SCSS to match the anchor's placement.

6. The SDK methods (apmApi)

If all of the above was configured properly, you should now see the SDK sticker and have access to the global variable apmApi.

userClientConnect

Connects the user. Should be called right after you've logged in/registered a user. Do not call this method on each page. The SDK keeps the user connected to appsmiles across webpages through the use of localStorage.

apmApi.userClientConnect(
  email: string, 
  partnerClientId: string, 
  firstName: string, 
  lastName: string, 
  segments: JSON //  e.g. {"a": "0", "b": "c"} 
  // Note: for `segments`, you MUST use double quotes, 
  //       single quotes are invalid JSON characters in PHP 
  //       (language which our API uses)
  dataConsentCode?: -1 | 0 | 1 // NOT_SET = -1, ACCEPT = 0, REFUSE = 1
  // Optional parameter that allows to set directly RGPD consent
)

Return value

  1. Error

    1. If invalid arguments

    2. If already connected

    3. If user has not accepted RGPD yet (will connect once it is)

  2. userObject

    {
    user: userObject
    }

Example

Connecting user John Doe with his email 'john.doe@mail.com' associated with the ID/token you gave him. The segments here must match with the definition appsmiles has, in this example case: {s = sexe: m = male, p = prime: 0 = false}

apmApi.userClientConnect('john.doe@mail.com', '1326793', 'John', 'Doe', {"s": "m", "p": "0"}, -1);

Example

Connecting user John Doe with his email 'john.doe@mail.com' associated with the ID/token you gave him. The segments here must match with the definition appsmiles has, in this example case: {s = sexe: m = male, p = prime: 0 = false}

apmApi.userClientConnect('john.doe@mail.com', '1326793', 'John', 'Doe', {"s": "m", "p": "0"}, -1);

createUserWithActions

Warning: This method is specific to certain scenarios. Contact us to know if you should use it.

Creates a user and rewards him with the given actions (validated by the appsmiles server). Note 1: Does not connect the user. Note 2: If user already exists, assigns the actions (does not create duplicate).

If the action uses 'rules and filters', you have to add the properties object in each action with the corresponding properties.

apmApi.createUserWithActions(
  actions: Array<{tagID: string, properties?: {[code: string]: string}}>, // array of objects with the property 'tagID'
  email: string,
  partnerClientId: string,
  firstName: string,
  lastName: string,
  segments: any
)

Return value

  1. Error (see error message)

  2. earnObject (last earn in list) and userObject

    {
    earn: earnObject,
    user: userObject
    }

Example

var actions = [
  {tagID: "tag1"},
  {tagID: "tag2"},
  {tagID: "tag3"},
];
apmApi.createUserWithActions(actions, "sdkweb@appsmiles.fr", "987654321", "John", "Doe", undefined);

Example with properties

var actions = [
  {tagID: "writeReview", properties: {city: "paris", establishment: "restaurant"}},
  {tagID: "writeReview", properties: {city: "paris", establishment: "hotel"}}
  {tagID: "writeReview", properties: {city: "brussels", establishment: "hotel"}},
];
apmApi.createUserWithActions(actions, "sdkweb@appsmiles.fr", "987654321", "John", "Doe", undefined);

userProperties

Only necessary when using our filter functionality.

Call these methods to update user properties. These user properties are stored permanently on a device (using localStorage). The keys and values must match with the filters established with appsmiles.

apmApi.addUserProperties(
  userProperties: {[s: string]: string}
);
apmApi.removeUserProperties(
  userPropertyKeys: string[]
);

Return value The current userProperties

{
  key1: "value1",
  key2: "value2"
}

Example :

var userProperties = apmApi.addUserProperties({"location": "33130", "bankCardSaved": "true", "gender": "male", "age": "27"});
console.log(userProperties); // {location: "33130", bankCardSaved: "true", gender: "male", age: "27"}
userProperties = apmApi.removeUserProperties(["location", "gender"]);
console.log(userProperties); // {bankCardSaved: "true", age: "27"}
userProperties = apmApi.removeUserProperties();
console.log(userProperties); // {}

triggerAction

Tells our server that the user accomplished the given action after the SDK checks whether the user is allowed to do the action (frequency, filters, etc...).

The tags must match with the tagging plan established with appsmiles.

/**
 * Tells the appsmiles server that the user accomplished an action (trigger)
 * which awards points if the conditions are met.
 * DYNAMIC TAGS: you can have the tagId in its dynamic form 
 * (e.g. display_product#$#display_product#$#categoryId=12#$#productId=387)
 *
 * @param {string} tagId
 * @param {{[s: string]: string}} properties Optional, both keys and values MUST BE strings
 * @returns
 *  {
 *    generosityValue: "5",
 *    userBalance: "340",
 *    totalEarn: "390",
 *    status: "1",
 *    earn: {
 *      earnID: "113865",
 *      earnValue: "5",
 *      partnerLabel: "Demo Partner",
 *      earnDate: "01/03/2018",
 *      earnLabel: "Action syst\u00e9matique"
 *    },
 *    gift: { giftID: "245", value: "20" }
 *  };
 */
apmApi.triggerAction(
  tagId: string,
  properties?: {[s: string]: string}
);

Return value: 1. Error (see error message) 2. earnObject and userObject

{
  earn: earnObject,
  user: userObject
}

Example :

apmApi.triggerAction("writeReview"); // -> Write a review on a business
apmApi.triggerAction("writeReview", {establishment: "restaurant"}); // -> Write a review on a restaurant
apmApi.triggerAction("writeReview", {establishment: "restaurant", location: "75000"}); // -> Write a review on a restaurant in Paris

logOut

Logs a user out. This must be called when a user is logged out of your website. The SDK will revert to it's non-connected state.

Example :

apmApi.logOut();

getUser

Retrieves a user.

Special fields:

  • deviceStatus ("-1": Not set, "0": Animated, "1": Anonymous, "2": Opt out)

  • connectionStatus. ("0": Not connected, "1": Connected)

Example :

apmApi.getUser();
/*
returns 
{
  "partnerClientId":"12345",
  "userToken":"c56aff3faca3ee9259600081eebbf83d1d0ba5a7",
  "email":"test@appsmiles.fr",
  "firstName":"Tester",
  "mobileNumber": undefined,
  "gender":"0",
  "birthdate": undefined,
  "town": undefined,
  "status":"4", // the level of the user
  "segments": {"a": "1"}
  "infos": undefined,
  "userBalance":"28685", // totalEarn - points spent for gifts
  "userObsoleteBalance":"-1",
  "totalEarn":"28775"
  "deviceStatus":"0",
  "connectionStatus":"1",
}
*/

hideSticker

Hide SDK without stopping the service: triggerActions are sent and notifications are stored

apmApi.hideSticker();

showSticker

Show SDK (if previously hidden)

apmApi.showSticker();

disableNotifications

Disable SDK notifications (earn notifications are stored for later display)

apmApi.disableNotifications();

enableNotifications

Enable SDK notifications and show stored notifications

apmApi.enableNotifications();

open

Open modal (on specified screen if parameter is given, or last loaded screen if not)

apmApi.open(screenName?: "challenge" | "menu" | "gifts" | "burns" | "earns" | "levels" | "pages-mentions" | "pages-onboarding-anonymous" | "pages-onboarding-connected");

close

Close modal

apmApi.close();

Models

userObject

class userObject {
  partnerClientId: string | undefined = "1234321"; 
  userToken: string | undefined = "9abaa65989758d59fa560f61112a418850f32603"; 
  email: string | undefined = "a@b.com"; 
  firstName: string | undefined = "John"; 
  lastName: string | undefined = "Doe"; 
  mobileNumber: string | undefined = "0612345678"; 
  gender: string | undefined = "0"; 
  birthdate: string | undefined = ""; 
  town: string | undefined = ""; 
  status: string | undefined = "1"; // level 
  segments: string | undefined = ""; 
  infos: string | undefined;
  deviceStatus: string | undefined = "0"; // -1: NOT_SET, 0: OK, 1: ANONYMOUS (control group), 2: OPT_OUT
  userBalance: string | undefined = "40";
  userObsoleteBalance: string | undefined;
  totalEarn: string | undefined = "240";
}

earnObject

class earnObject {
  earnID: string | undefined = "137189";
  earnValue: string | undefined = "40";
  partnerLabel: string | undefined = "nom de la compagnie";
  earnDate: string | undefined = "12/09/2018";
  earnLabel: string | undefined = "En créant votre compte";
}

Notice

  • Since the SDK uses WebStorage (localStorage & sessionStorage), please avoid clearing either storages. Clearing them will wipe user data, thus making the user look offline on our SDK, until userClientConnect is called again.

Browser support

Supported on all browsers except IE9 and below.

Desktop:

Mobile:

Stats calculated with data from December 2018 on gs.statcounter.com

More

For any questions, bugs or requests, please contact us on Slack or via email.

Last updated