📘

How You Could Do It

There are many ways you may choose to integrate the DataGuard API to your apps. We show you below how you can embed our widget so you can begin collecting compliant permissions immediately

Integration using a WebView

The easiest integration of DataGuard into an iOS app is achieved when loading our widget into a WKWebView.

Prior to doing this you will need a template set up, with a templateId, and a process that uses the DataGuard API to generate a user access token. The templateId and the access token will be required for further steps.

Including the widget in a WKWebView

The Web Content to Display

First, we need to add the content that the WKWebView is going to render. To do this, add a file named index.html to your xcode project root. The contents of this file should be as follows:

<head>
    <meta name="viewport" content="width=device-width">
</head>
<body>
    <div id="widget-container"></div>
    <script src="https://scripts.consentric.io/progressive-consent.min.js"></script>
    <script>
        window.addEventListener("load", () => {
					window
          	.webkit
            .messageHandlers
            .loadedMessageHandler
            .postMessage(["Loaded", "true" 	])
        });
        const init = ({ templateId, token }) => {
            ProgressiveWidget.load({
                id: 'widget-container',
                templateId,
                token,
                events: {
                    onSuccess: (response, submission) => {
                        response.json().then((json) => {
                            window
                              .webkit
                              .messageHandlers
                              .widgetMessageHandler
                              .postMessage(["Success", JSON.stringify(json)])
                        })
                    },
                    onFailure: (error, request) => {
                        window
                          .webkit
                          .messageHandlers
                          .widgetMessageHandler
                          .postMessage(
                            [
                              "Failure",
                              JSON.stringify(({request, error}))
                            ])
                    },
                },
                consentricLogo: false,
            });
        }
    </script>
    <style>
        .mld-pc-minimise, .mld-pc-button--cancel {
            display: none;
        }
        .mld-pc-app-show {
            position: relative;
            border: none;
            box-shadow: none;
            max-height: 100%;
            min-height: 100%;
            padding: 0;
        }
        .mld-pc-app__header {
            padding: 0;
        }
    </style>
</body>

The snippet above may be customised, for example, to edit the CSS styles to better fit the look and feel of the surrounding app.

Setting up a ViewController

Next, this will need to be loaded into the WKWebView. You can instantiate a WKWebView in the way that best fits your app architecture. As an example, we have chosen a fairly standard ViewController to represent programmatically rendering the widget. This could then be pushed and popped by a NavigationController.

Our Example ViewController looks like this:

import UIKit
import WebKit

class DataGuardViewController: UIViewController, WKUIDelegate {
    
    var webView: WKWebView!
    
    override func loadView() {
        let widgetMessageHandler = WidgetMessageHandler()
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration
      		.preferences
      		.javaScriptEnabled = true
        webConfiguration
      		.userContentController
      		.add(widgetMessageHandler, name: "widgetMessageHandler")
        webConfiguration
      		.userContentController
      		.add(self, name: "loadedMessageHandler")
        webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.uiDelegate = self
        view = webView
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let htmlFile = Bundle.main.path(forResource: "index", ofType: "html")
        let html = try? String(
            contentsOfFile: htmlFile!,
            encoding: String.Encoding.utf8
        	)
        webView.loadHTMLString(html!, baseURL: nil)
    }
    
    func handleInit(result: Any?, error: Error?) {
        print(result)
        print(error)
    }
    
    func initWidget(templateId: String, token: String) {
        webView
            .evaluateJavaScript(
                """
                    init({
                        templateId:'\(templateId)',
                        token:'\(token)'
                    });
                """,
                completionHandler: handleInit(result:error:))
    }
}

extension ManagePreferencesViewController: WKScriptMessageHandler {
       func userContentController(
          _ userContentController: WKUserContentController,
          didReceive message: WKScriptMessage
        ) {
            //Get variables for widget here
           if (message.name == "loadedMessageHandler") {
               initWidget(
                 templateId: "{{INSERT TEMPLATE ID HERE}}", 
                 token: "{{INSERT ACCESS TOKEN HERE}}"
               )
           }
       }
}

This is a simple ViewController class that instantiates a WKWebView, and uses the index.html file that was created earlier as the content to be rendered. On top of this, it extends the WKScriptMessageHandler class, allowing a message to be posted back when the DOM in the WKWebView has loaded.

This allows the initialization of the widget code with the correct templateId and user access token as mentioned above. How these variables are injected here are left open for interpretation. Dependency Injection, being passed through by the NavigationController into a constructor of the ViewController, API calls, and so on may all be valid methods to obtain the values required at this point.

Capturing and using the data from the Widget

We've seen above an extension of the ViewController class lets us know when the page has loaded and we can render the widget. Depending on your specific implementation you may want to handle the success or failure of submitting the Widget and the resultant state. The Widget provides two callback functions which can be hooked into for a success case or a failure case. The snippet below shows an example of a separate class set up to listen to both kinds of message when using the index.html file structure provided.

import WebKit
import Foundation
class WidgetMessageHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController,
                               didReceive message: WKScriptMessage) {
        if (message.name == "widgetMessageHandler") {
            let json = message.body as! [String]
            switch (json[0]) {
                case "Success":
                    //Handle success
                    break;
                case "Failure":
                    //Handle failure
                    break;
                default:
                    //Throw error
                print("Parse Error")
                    break;
            }
        }
    }
}

The message structure follows a convention of an array of strings containing two elements. The first is a message indicating the outcome of the action and the second is metadata surrounding that action. As shown in the class above our messages will either have a "Success" or a "Failure" indicator. The content of the second element will vary depending on the templates used but will take the general form of stringified JSON. An example of this is shown below.

{
  "preferences": 0,
  "permissions": [
    {
      "id": "9VrZeEV93aR_bM2Bvzrfm3b",
      "applicationId": "4wcBMyS6c8Y",
      "citizenId": "PkqTeBUyCJH",
      "externalRef": "cia",
      "changes": [
        {
          "optionType": "option",
          "optionId": "3mgii2",
          "justification": "consent",
          "state": "DENIED",
          "permissionStatementId": "sjLfySwEAAP",
          "obtainedAt": "2020-04-24T15:26:11.289Z",
          "validFrom": "2020-04-24T15:26:11.289Z"
        },
        {
          "optionType": "option",
          "optionId": "DeLij3",
          "justification": "consent",
          "state": "DENIED",
          "permissionStatementId": "sjLfySwEAAP",
          "obtainedAt": "2020-04-24T15:26:11.289Z",
          "validFrom": "2020-04-24T15:26:11.289Z"
        },
        {
          "optionType": "option",
          "optionId": "FJjyD7",
          "justification": "consent",
          "state": "DENIED",
          "permissionStatementId": "sjLfySwEAAP",
          "obtainedAt": "2020-04-24T15:26:11.289Z",
          "validFrom": "2020-04-24T15:26:11.289Z"
        },
        {
          "optionType": "option",
          "optionId": "tM4zd4",
          "justification": "consent",
          "state": "GRANTED",
          "permissionStatementId": "sjLfySwEAAP",
          "obtainedAt": "2020-04-24T15:26:11.289Z",
          "validFrom": "2020-04-24T15:26:11.289Z",
          "validUntil": "2021-04-24T15:26:11.289Z"
        }
      ],
      "channel": "progressive",
      "privacyPolicyId": "uN2mttuNqDS",
      "privacyPolicyRef": "policyRef-01",
      "recordedAt": "2020-04-24T15:26:11.289Z",
      "recordedBy": "DataGuard|PkqTeBUyCJH",
      "recordedByName": "cia",
      "recordedBySpecifiedByClient": false,
      "recordedClient": "Authorisation Service",
      "recordedClientName": "Access Token",
      "revertable": true,
      "obtainedAt": "2020-04-24T15:26:11.289Z"
    }
  ]
}