Passer au contenu principal

Reverse Engineering Lidl Fun Trips

Cette page est une archive : les informations qu’elle contient peuvent ne plus être à jour.

    Apache Cordova
    AngularJS
    Ionic

    L'appli est une simple webapp qui fait une serie d'appels a un backend rest.

    Url du site:  https://lidlfuntrips.be/

    Je suis une fénéant et j'aime postman. Voici un fichier qui contient toutes les query interessantes a charger dans postman.

    J'utilise l'env var { {bearer} }. Il suffit de faire un call a login pour recup le bearer et mettre a jour la var d'env, j'ai la flemme de le faire pour vous. Je le ferai un jour peut être.

    Et vu que j'ai pas de manière d'heberger le fichier de manière sure fiable sur la longeur je vous le file ici, copiez collez et et ROTFL  sur mon sujet

    {
    	"id": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
    	"name": "Lidle",
    	"description": "",
    	"order": [
    		"7f95e59e-5c21-9ce7-361e-b12f1c9bd0f4",
    		"89d86de1-4ef4-5af1-cad4-429e3ca18c67",
    		"e46ed2b5-7747-bf0d-5cd1-2498397e470e",
    		"e525b5d4-6ce9-3442-bb37-f02b9c95d50b",
    		"a7eb7921-622c-491c-b08f-26b6e8f098e4"
    	],
    	"folders": [],
    	"folders_order": [],
    	"timestamp": 1526791518755,
    	"owner": 0,
    	"public": false,
    	"requests": [
    		{
    			"id": "7f95e59e-5c21-9ce7-361e-b12f1c9bd0f4",
    			"headers": "Content-Type: application/json\nAuthorization: Basic Og==\n",
    			"headerData": [
    				{
    					"key": "Content-Type",
    					"value": "application/json",
    					"description": "",
    					"enabled": true
    				},
    				{
    					"key": "Authorization",
    					"value": "Basic Og==",
    					"description": "",
    					"enabled": true
    				}
    			],
    			"url": "https://wallet-service.qup.eu/v1/users/login",
    			"queryParams": [],
    			"pathVariables": {},
    			"pathVariableData": [],
    			"preRequestScript": null,
    			"method": "POST",
    			"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
    			"data": [],
    			"dataMode": "raw",
    			"name": "Login",
    			"description": "",
    			"descriptionFormat": "html",
    			"time": 1526791551026,
    			"version": 2,
    			"responses": [],
    			"tests": null,
    			"currentHelper": "normal",
    			"helperAttributes": {},
    			"rawModeData": "{\n\t\"device_id\": 1234567890,\n\t\"plateform\": \"android\",\n\t\"grant_type\": \"password\",\n\t\"client_id\": \"lidl-fr_FR-android@ws.apps.queueup.eu\",\n\t\"client_secret\": \"elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs\",\n\t\"username\": \"chamaloriz@gmail.com\",\n\t\"password\": \"Radislav250399\"\n}"
    		},
    		{
    			"id": "89d86de1-4ef4-5af1-cad4-429e3ca18c67",
    			"headers": "Authorization: Bearer NSx5kamSYNYCBsmgCipO49wqzzQ2ZRwHmZvukKMC\n",
    			"headerData": [
    				{
    					"key": "Authorization",
    					"value": "Bearer NSx5kamSYNYCBsmgCipO49wqzzQ2ZRwHmZvukKMC",
    					"description": "",
    					"enabled": true
    				}
    			],
    			"url": "https://wallet-service.qup.eu/v1/me/messages",
    			"queryParams": [],
    			"pathVariables": {},
    			"pathVariableData": [],
    			"preRequestScript": null,
    			"method": "GET",
    			"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
    			"data": null,
    			"dataMode": "params",
    			"name": "Get Message",
    			"description": "",
    			"descriptionFormat": "html",
    			"time": 1526791538316,
    			"version": 2,
    			"responses": [],
    			"tests": null,
    			"currentHelper": "normal",
    			"helperAttributes": {}
    		},
    		{
    			"id": "a7eb7921-622c-491c-b08f-26b6e8f098e4",
    			"headers": "Authorization: Bearer {{bearer}}\nContent-Type: application/json\n",
    			"headerData": [
    				{
    					"key": "Authorization",
    					"value": "Bearer {{bearer}}",
    					"description": "",
    					"enabled": true
    				},
    				{
    					"key": "Content-Type",
    					"value": "application/json",
    					"description": "",
    					"enabled": true
    				}
    			],
    			"url": "https://wallet-service.qup.eu/v1/me/vouchers/add",
    			"queryParams": [],
    			"pathVariables": {},
    			"pathVariableData": [],
    			"preRequestScript": null,
    			"method": "POST",
    			"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
    			"data": [],
    			"dataMode": "raw",
    			"name": "Post Vaucher",
    			"description": "",
    			"descriptionFormat": "html",
    			"time": 1526792030344,
    			"version": 2,
    			"responses": [],
    			"tests": null,
    			"currentHelper": "normal",
    			"helperAttributes": {},
    			"rawModeData": "{\r\n\"code\":\"LIA25636QZ\"\r\n}"
    		},
    		{
    			"id": "e46ed2b5-7747-bf0d-5cd1-2498397e470e",
    			"headers": "Authorization: Bearer MqyGajuFyVAUfJgNTfdLK0AOsWqBTCc11CGLP1yH\n",
    			"headerData": [
    				{
    					"key": "Authorization",
    					"value": "Bearer MqyGajuFyVAUfJgNTfdLK0AOsWqBTCc11CGLP1yH",
    					"description": "",
    					"enabled": true
    				}
    			],
    			"url": "https://wallet-service.qup.eu/v1/me",
    			"queryParams": [],
    			"pathVariables": {},
    			"pathVariableData": [],
    			"preRequestScript": null,
    			"method": "GET",
    			"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
    			"data": null,
    			"dataMode": "params",
    			"name": "Get Profile",
    			"description": "",
    			"descriptionFormat": "html",
    			"time": 1526791618943,
    			"version": 2,
    			"responses": [],
    			"tests": null,
    			"currentHelper": "normal",
    			"helperAttributes": {}
    		},
    		{
    			"id": "e525b5d4-6ce9-3442-bb37-f02b9c95d50b",
    			"headers": "Authorization: Bearer {{bearer}}\n",
    			"headerData": [
    				{
    					"key": "Authorization",
    					"value": "Bearer {{bearer}}",
    					"description": "",
    					"enabled": true
    				}
    			],
    			"url": "https://wallet-service.qup.eu/v1/me/vouchers",
    			"queryParams": [],
    			"pathVariables": {},
    			"pathVariableData": [],
    			"preRequestScript": null,
    			"method": "GET",
    			"collectionId": "0ac5fb09-d46b-5855-aa1e-8397ee0386f6",
    			"data": null,
    			"dataMode": "params",
    			"name": "Get encoded voucher",
    			"description": "",
    			"descriptionFormat": "html",
    			"time": 1526791836364,
    			"version": 2,
    			"responses": [],
    			"tests": null,
    			"currentHelper": "normal",
    			"helperAttributes": {}
    		}
    	]
    }

    Authentification des requêtes via le header HTTP Authorization Bearer. Chaque requête doit contenir le token.

    Le token peut être obtenu en faisait un POST sur /users/login (UserProvider.Login())

    Query

          curl -X POST \
      https://wallet-service.qup.eu/v1/users/login \
      -H 'content-type: application/json' \
      -d '{
    	"device_id": 1234567890,
    	"plateform": "android",
    	"grant_type": "password",
    	"client_id": "lidl-fr_FR-android@ws.apps.queueup.eu",
    	"client_secret": "elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs",
    	"username": "toto",
    	"password": "1234"
    }'

    Réponse

    {
    	"access_token": "zlsTbxIbDpdUr27vmrJQSgaUPj4NcdcU7d3XwdgI",
    	"token_type": "Bearer",
    	"expires_in": 30758400
    }

    A noter que le ContentPageProvider utilise un autre mécanisme d'authentification, cfr doc du provider.

      Le mot de passe doit faire minimum 6 caractères, contenir une majuscule, minuscule et nombre
      L'adresse mail doit répondre au format (désolé pour le regex du pauvre) [A-Zaz0-9]@[A-Zaz0-9].[A-Zaz0-9]

      Query

      curl -X POST \
        https://wallet-service.qup.eu/v1/users/register \
        -H 'cache-control: no-cache' \
        -H 'content-type: application/json' \
        -d '{
      	"title": "f",
      	"firs_name": "AZERTYU",
      	"suffix": "AZERTY",
      	"last_name": "AZERTYU2",
      	"name": "AZERTYU AZERTYU",
      	"postalcode": "4000",
      	"email": "jeanhkjhkjhkjh@hotmail.com",
      	"password": "azerty1234$A",
      	"device_id": 1234567890,
      	"plateform": "android",
      	"grant_type": "password",
      	"client_id": "lidl-fr_FR-android@ws.apps.queueup.eu",
      	"client_secret": "elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs"
      }'

      Response

      {
      	"access_token": "kVSU7NPBrYP3meb4XtvQfq3JwbfH7P0htnmGph4J",
      	"token_type": "Bearer",
      	"expires_in": 30758400
      }

      Query

      curl -X POST \
        https://wallet-service.qup.eu/v1/me/vouchers/add \
        -H 'authorization: Bearer kVSU7NPBrYP3meb4XtvQfq3JwbfH7P0htnmGph4J' \
        -H 'content-type: application/json' \
        -d '{
      "code":"LIA25636QZ"
      }'

      Response (erreur - inexistant)

      {
      	"internal_code": 105,
      	"success": false,
      	"errors": [
      		""
      	]
      }

      Response (erreur - déja encodé)

      {
      	"internal_code": 101,
      	"success": false,
      	"errors": [
      		""
      	]
      }

      Responses (succes)

      {
      	"internal_code": 0,
      	"success": true,
      	"data": {
      		"voucher": "XKN5GWWDCE",
      		"order_id": null,
      		"used": false
      	}
      }

      Query

      curl -X GET \
        https://wallet-service.qup.eu/v1/me/vouchers \
        -H 'authorization: Bearer Ad1GI7gG39DKJiijewuNUn019RZclLewSTFVolJt'

      Response

      {
      	"internal_code": 0,
      	"success": true,
      	"data": [
      		{
      			"voucher": "D6G279P2KB",
      			"used": false,
      			"order_id": null
      		},
      		{
      			"voucher": "3Z9LAXDM12",
      			"used": false,
      			"order_id": null
      		},
      		// Une liste quoi...
      	]
      }

      Query

      curl -X GET \
        https://wallet-service.qup.eu/v1/me/ \
        -H 'authorization: Bearer Ad1GI7gG39DKJiijewuNUn019RZclLewSTFVolJt'

      Response

      {
      	"internal_code": 0,
      	"success": true,
      	"data": {
      		"name": "AZERTYU AZERTYU",
      		"email": "jeanhkjhkjhkjh@hotmail.com",
      		"postalcode": "4000",
      		"created_at": "2018-05-18 16:04:56GMT+0000",
      		"temp_password": null,
      		"locale": "en_US",
      		"first_name": "",
      		"suffix": "AZERTY",
      		"last_name": "AZERTYU2",
      		"title": "f",
      		"telephone": null
      	}
      }

      Query

      curl -X GET \
        https://wallet-service.qup.eu/v1/me/messages \
        -H 'authorization: Bearer NSx5kamSYNYCBsmgCipO49wqzzQ2ZRwHmZvukKMC'

      Response

      {
      	"internal_code": 0,
      	"success": true,
      	"data": [
      		{
      			"code": "cVR4ckSGLNc:APA91bFqUUafMNVfIphHlBaisTVBQQCtM8TSck5jOhaS8LZanKaxj-8tKM7B-AH2pZj_RqgqFw2CzlHnxkGKEH3vFoxdhyh9nF4uKMqUWaxAcBI4HTgG_8rdi5FZZT4ZKsA9KnxIX5Tx",
      			"title": "Namens Lidl: Fijne Pinksteren!",
      			"message": "De la part de Lidl Funtrips: joyeuse fête de la Pentecôte!",
      			"read": false,
      			"created_at": "2018-05-19 10:55:59GMT+0000"
      		},
      		{
      			"code": "cVR4ckSGLNc:APA91bFqUUafMNVfIphHlBaisTVBQQCtM8TSck5jOhaS8LZanKaxj-8tKM7B-AH2pZj_RqgqFw2CzlHnxkGKEH3vFoxdhyh9nF4uKMqUWaxAcBI4HTgG_8rdi5FZZT4ZKsA9KnxIX5Tx",
      			"title": "€ 10 korting bij Bobbejaanland met Pinksteren!",
      			"message": "Une réduction de 10 € chez Bobbejaanland à la Pentecôte! ",
      			"read": false,
      			"created_at": "2018-05-16 13:09:23GMT+0000"
      		},
      		{
      			"code": "cVR4ckSGLNc:APA91bFqUUafMNVfIphHlBaisTVBQQCtM8TSck5jOhaS8LZanKaxj-8tKM7B-AH2pZj_RqgqFw2CzlHnxkGKEH3vFoxdhyh9nF4uKMqUWaxAcBI4HTgG_8rdi5FZZT4ZKsA9KnxIX5Tx",
      			"title": "Ga met Hemelvaart de baby-olifantjes bezoeken in Planckendael!",
      			"message": "Allez visiter les bébés éléphants à Planckendael pendant l'Ascension!",
      			"read": false,
      			"created_at": "2018-05-09 14:55:05GMT+0000"
      		}
      	]
      }
      Endpoint Method /me GET, PUT, DELETE /me/messages GET, PUT /me/vouchers GET /me/vouchers/add POST /me/messages GET, PUT /users/login POST /users/register POST /users/forgot POST /content GET /content/{key} GET

      Tout se trouve dans /assets/www/build/

      Fichier Role vendor.js OSEF, angular js polyfills.js OSEF aussi swtoolbox.js OSEF toujours main.js Toute la logique est ici 0.js ScanPageModule 1.js ProfilePageModule 2.js PassForgotPageModule 3.js ManualCodePageModule 4.js LoginPageModule 5.js PrivacyPageModule 6.js PassForgotFeedbackPageModule 7.js MessagesPageModule 8.js IntroPageModule 9.js DeleteProfileModalModule 10.js ContactPageModule

      Quelques remarques:

        Les modules ne contiennent pas de logique autre que UI, ils utilisent des objet provider defini dans main qui eux contiennent la logique, servent de facade, repository, etc…
        Les pages utilisent les forms de AngularJS, il y a un peu de RE a faire sur les pages pour avoir les objet envoyé.
        AngularJS utilise beaucoup l'injection de de dépendances, tout est injecté via le constructeur.
        Chaque module ne contient qu'une page.

        ApiService

        Methode Doc getLocalizedClientId() Interface pour AppSettings, utilisé pour build les request getClientSecret() Interface pour AppSettings, utilisé pour build les request

        ApiGateway

        Toutes les requêtes REST passent par ce service.

        Expose POST, GET, PUT, etc. Toutes les méthodes appellent derrière request(). Cette méthode construit la request rest et set le header Authorization avec le bon token, c'est a peut près tout ce qu'il y a d’intéressant ici.

        LocaleService

        Retourne nl_NL ou fr_FR, utilise pour build le client_id

        ProjectAppSettings

        Have Fun, tout est assez clair.

          ProjectAppSettings.ENDPOINT = {
              TEST: {
                  BASE_URL: 'https://wallet-service.test.qup.eu/v1/',
                  CLIENT_ID: {
                      'IOS': 'lidl-<locale>-android@ws.apps.queueup.eu',
                      'ANDROID': 'lidl-<locale>-ios@ws.apps.queueup.eu'
                  },
                  CLIENT_SECRET: {
                      'IOS': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs',
                      'ANDROID': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs'
                  },
                  SHOP_URL: 'https://lidlfuntrips.qup.uat1.aanzee.cc'
              },
              LIVE: {
                  BASE_URL: 'https://wallet-service.qup.eu/v1/',
                  CLIENT_ID: {
                      'IOS': 'lidl-<locale>-android@ws.apps.queueup.eu',
                      'ANDROID': 'lidl-<locale>-ios@ws.apps.queueup.eu'
                  },
                  CLIENT_SECRET: {
                      'IOS': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs',
                      'ANDROID': 'elAsNeAD1j1hvPwfr5t0iLBxq2mXLZ0M8CWmabzs'
                  },
                  SHOP_URL: 'https://lidlfuntrips.be'
              }
          };
          ProjectAppSettings.SERVER = ProjectAppSettings.ENDPOINT.LIVE;
          ProjectAppSettings.PROJECT = 'lidl';
          ProjectAppSettings.VOUCHER_MASK = [/[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/, /[a-zA-Z0-9]/];
          ProjectAppSettings.HAS_NAME_SPLIT = true;
          ProjectAppSettings.API_ENDPOINT = ProjectAppSettings.SERVER.BASE_URL;
          ProjectAppSettings.SHOP_URL = ProjectAppSettings.SERVER.SHOP_URL;
          ProjectAppSettings.ANDROID_SENDER_ID = '430671127661';
          ProjectAppSettings.VOUCHER_VALUE = 2;
          ProjectAppSettings.CLIENT_ID = {
              'IOS': ProjectAppSettings.SERVER.CLIENT_ID.IOS,
              'ANDROID': ProjectAppSettings.SERVER.CLIENT_ID.ANDROID
          };
          ProjectAppSettings.CLIENT_SECRET = {
              'IOS': ProjectAppSettings.SERVER.CLIENT_SECRET.IOS,
              'ANDROID': ProjectAppSettings.SERVER.CLIENT_SECRET.ANDROID
          };
          ProjectAppSettings.GA_ID = 'UA-2367280-26';
          ProjectAppSettings.VALUTA = 'EUR';
          ProjectAppSettings.isContentAccessTokenRequired = true;
          ProjectAppSettings.scanditLicenceKey = {
              ios: 'AVwrDYiBHE1gFCB1uhW4ICsgS+avHh6XEnHDO5hQsMErS7UolXsALqohduCGfldXnU/BAxojgEKDfl8zF2pvsxJXazqWVxJW2GIkv4hhqnStawfmiWDcO9xDh8IObnMk8wagkxkXNeTfB1LQ/x0SWoIvRAWtw1RFUbigMVJsbSLIH7ERDf8nMHIiCor+4Mi8KPJ2pj9GtjYklSjesjSk2d9Td3kw/86f16aFSPvL6qmFTsyK04PSo+zeV9rvYIfu2aigvQC0V30mGjnwrB5Cjg6fCVSyBoqKCpl90G+tXo/s6C3T5kPAUpVkAD7V1OZs43W0AJhzG82qNO1Jdh8G8ZmywQhFMWI/i80no4fU+qGNDzvrJr8X59Q3izDbTdCCZ20P1iXQCNXScJn/vpiuAjQyEPSrHybJ/69pDm1oYYP6KxHg+Hyjn2KtukMqJxpXSlcduwB7YrcSnVJSU/YLULUZANDKxt15FCKUpNwg/bBss45yryRh5eAv6LOSGyBZ6FAxwcxSQ+Em5DGqX8dN72dd9m8wHZfco9qIOSl3SiY3GZWivSqK/Wvca+hJ60CtFGuaf34JpKYIELfInznSTGC90VxSMpqRkbch0eCKh/mA4DcdmzuZKDz1jxbnFuWfCSK3y6T42gck+r/KrAixdtbo1inRD+ILKqmc7paZzBa1UUkSx6PvqB1vLa1smqTWMii1hOASKyVj7Nju9TxjqFuidfQcuhvl9f1d5njr62zW0jGB7PqWfKealcvLBReSqgcnghbRQQuSje/xTe8Ou2Ssc/sQNpKafaGjdqC3ucezwzTukj8=',
              android: 'AXBrj7WBIUINN103cBKCgjQPNEdxOLC/3F3tyddJn1zdaZMasEGF6v0vUVobRJq5zmrnrnpbXF1UbIrgPUMKaoZB628XZHMOhHMoNud1xQtNTrdQD3pauIp28UvaTId/PSOuGGAE6gchxM+MvXFTU+4+OzmZM5kgo8Gx+ob2Ok6TZ7VBmk0XTVCtU6ZbHs9Cax9b/njJLjvDHRK5PErlFGjtSq7/gWN6IyjPGqcYEhPeepz+MpCXluOgjHlU1JUh3laLUjLYnMP5i10gMW5nWwkzv9DZfwbeLh3+2Sg90EPWlyBsqekHRXuze7Ams74cmYh3rsraTCucGTKyCgyqKFzLzb5vR7u7tbsq3vTxLQ87xfjRzx3CNUY37TW9NBNHUYWWfc2BgL7b/jOneWxrkFC1MjLahWn44d6mkEM6bxI6UAW5zjk1T48g7D6ALo15sNrDFJ8aohS+qUSXd6Z73uRJ6F9yBbNCBeMZfrgh8eyMyq0Cb1JaXdMDBq+h+maLZr3ySbi+sCZtoaGbrUd5+7IVk7UmkhKiYQntl+gzIoI089hY0qGYLy6hvbzpS1NbiJn6F9QwjMV4w7FxH242jqv5+kbx/5T80ecKxIHdjXEeZYQ+5+aCfYSfmOucDxGVZJVGjh4J+7EJT0Zl0dts++pwXW2vIT/wKQfM+BM7pt8KAeltegl/+33OgIPDsptJ+XKiOA48Wlhb7tly8j78yAsLdzW39Xt/Btw9UTvJdrUqp8YrzeUGCXup4te6YCB+Vn0W302o/Z4GSSvyoIQNHwlxyc2qW4lFS4+wJHhes8ZX4ZjdJGo=',
          };
          ProjectAppSettings.availableLocales = [{
                  iso: 'nl',
                  locale: 'nl_NL',
              }, {
                  iso: 'fr',
                  locale: 'fr_FR',
              }];
          ProjectAppSettings.defaultLanguage = 'fr';
          ProjectAppSettings.hasIntroImageBackground = false;
          ProjectAppSettings.contentPages = {
              contact: 'contact',
              privacy: 'privacy',
          };

        AppSettings

        Interface sur ProjectAppSettings

        Providers

        UserProvider

        Method Doc login(post_data) POST sur /users/login + LoginPage form data register(post_data) POST sur /users/register + LoginPage register form data forgot(post_data) POST sur /users/forgot + LoginPage form data getProfile() GET sur /user updateProfile(post_data) PUT sur /user + user object deleteProfile(post_data) DELETE sur /user + user object saveExpirationDate()   

        VoucherProvider

        Method Doc getVoucher() GET sur /me/vouchers addVoucher(code) POST sur /me/vouchers/add

        MessageProvider

        Method Doc       getMessage() GET sur /me/messages markAsRead() PUT sur /me/messages/{messageCode} convertServerToPushNotification();    incrementNotificationBadge();    getNotificationBadge();    markNotificationsRead();   

        ContentPageProvider

        Récupére du contenu depuis le webservice mais utilise un autre système d'authentification si isContentAccessTokenRequired est a true (ce qui est le cas).

        getAccessToken() POST sur /client/access_token avec un grant_type 'client_credentials' pour récupérer un token qui sera ensuite utilisé dans getContentPageForKey(key) qui GET sur /content/{key}

        Autres providers dans main.js

        Method Doc StorageProvider Abstraction local storage PushNotificationProvider Hum… If you don't get the role from service class you're damn to hell GAProvider Google Analytics

        Trucs moins interessants

        Modules dans main.js

          AppModule
          SharedModule
          ComponentsModule
          ServiceModule
          ProvidersModule
          PipesModule

          Autres classes dans main.js

            WebPopup
            QRScanner
            MyMissingTranslationHandler
            CustomDate
            VoucherAmount
            ScrollShadow
            SelectSwitchComponent
            CacheRequest
            MyApp