If you haven’t already, download Web Discover SDK. Unzip the file, then drag and drop the web discover sdk folder into your Xcode project. Make sure Copy items if needed, and Create folder references are checked, along with the appropriate targets next to Add to targets.

To allow for customization of the web discover app, we inject javascript and modify index.html.

Injecting vouchrConfig

First we will inject the vouchrConfig dictionary shown in /config/config.js:

var vouchrConfig = {
  apiKey: 'YOUR_API_KEY',
  networkKey: 'YOUR_NETWORK_KEY',
  baseApiUrl: 'https://api.vouchrsdk.com/api',
  webRevealUrl: 'https://partnerapp.com/reveal'
};

To accomplish this, we write a method that takes property values as parameters, and returns the vouchrConfig dictionary as a string.

- (NSString *)vouchrConfigStringWithApiKey:(NSString *)apiKey networkKey:(NSString *)networkKey baseApiUrl:(NSString *)baseApiUrl webRevealUrl:(NSString *)webRevealUrl {
    return [NSString stringWithFormat:@"{ apiKey: '%@', networkKey: '%@', baseApiUrl: '%@', webRevealUrl: '%@'}", apiKey, networkKey, baseApiUrl, webRevealUrl];
}
func vouchrConfigString(withApiKey apiKey: String, networkKey: String, baseApiUrl: String, webRevealUrl: String) -> String {
    return "{ apiKey: '\(apiKey)', networkKey: '\(networkKey)', baseApiUrl: '\(baseApiUrl)', webRevealUrl: '\(webRevealUrl)'}"
}

We will use this method when launching the WKWebView. Next we will edit index.html, and write a method to inject the creation of a vouchrApp.

Setup Callbacks in index.html

Open index.html, and replace the return statements in the VouchrApp constructor with window.webkit.messageHandlers.<#name#>.postMessage('#event_tag#') statements. This sets up callbacks, which will be further described in the Receiving Callbacks section below. Note that the delaration of vouchrApp, and calling the `render()’ function have been modified from the original.

var vouchrApp; 
vouchrApp = new window.VouchrApp({
    vouchrConfig,
    idToken,
    active: function () {
        window.webkit.messageHandlers.vouchr.postMessage('active')
    },
    summary: function () {
        window.webkit.messageHandlers.vouchr.postMessage('summary')
    },
    complete: function (voucherId) {
        window.webkit.messageHandlers.vouchr.postMessage('complete('.concat(voucherId, ')'))
    },
    error: function () {
        window.webkit.messageHandlers.vouchr.postMessage('error')
    },
    cancel: function () {
        window.webkit.messageHandlers.vouchr.postMessage('cancel')
    });
vouchrApp.render(document.getElementById('vouchrAppRoot'));

Injecting VouchrApp Creation and Rendering

In order to inject the code above, which constructs a VouchrApp and renders it on screen, we write a method that take in the config and userToken as parameters and returns a string representation of the JavaScript. Copy the code above, and format it into a string by removing whitespaces, etc. Add it to a method like the following. Note the property keys for vouchrConfig, and idToken.

- (NSString *)vouchrAppSetupStringWithConfig:(NSString *)config userToken:(NSString *)token {
    return [NSString stringWithFormat:@"var vouchrApp;"
                "vouchrApp = new window.VouchrApp({vouchrConfig: %@, idToken: '%@',"
                "active: function () {"
                    "window.webkit.messageHandlers.vouchr.postMessage('active') },"
                "summary: function () {" 
                    "window.webkit.messageHandlers.vouchr.postMessage('summary') },"
                "complete: function (voucherId) {"
                    "window.webkit.messageHandlers.vouchr.postMessage('complete('.concat(voucherId, ')')) },"
                "error: function () {"
                    "window.webkit.messageHandlers.vouchr.postMessage('error') },"
                "cancel: function () {" 
                    "window.webkit.messageHandlers.vouchr.postMessage('cancel') }});"
                "vouchrApp.render(document.getElementById('vouchrAppRoot'));", config, token];
}
func vouchrAppSetupString(withConfig config: String, userToken token: String) -> String {
    return """
            var vouchrApp; 
            vouchrApp = new window.VouchrApp({vouchrConfig: \(config), idToken: '\(token)',
            active: function () {
                window.webkit.messageHandlers.vouchr.postMessage('active')
            },
            summary: function () {
                window.webkit.messageHandlers.vouchr.postMessage('summary')
            },
            complete: function (voucherId) {
                window.webkit.messageHandlers.vouchr.postMessage('complete('.concat(voucherId, ')'))
            },
            error: function () {
                window.webkit.messageHandlers.vouchr.postMessage('error')
            },
            cancel: function () {
                window.webkit.messageHandlers.vouchr.postMessage('cancel')
            });
            vouchrApp.render(document.getElementById('vouchrAppRoot'));
          """
}

Now that we have the methods to inject JavaScript, we no longer need to use config.js, and we can modify index.html by removing the code that creates a VouchrApp and renders it on screen. Open index.html, locate the tags in the snippet below, and delete the lines in-between.

...<div id="vouchrAppRoot"></div>

<!-- DELETE CODE IN-BETWEEN -->

<script src="./static/js/runtime~main.d653cc00.js"></script>...

Launching WKWebView

We are now ready to create and launch Web Discover in a WKWebView. We will use the methods created in Injecting vouchrConfig and Injecting VouchrApp Creation and Rendering, which create JavaScript strings for vouchrConfig, and vouchrApp. The following sample code initializes and loads a WKWebView with your custom Web Discover app.

- (void)setupWebView { 
    WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
    WKUserContentController *userContentController = [[WKUserContentController alloc] init];
    
    /* To receive callbacks, register the <#name#> from window.webkit.messageHandlers.<#name#>.postMessage('#event_tag#'), here "vouchr" is used as an example */
    [userContentController addScriptMessageHandler:self name:@"vouchr"];
    configuration.userContentController = userContentController;
    
    NSString *vouchrConfig = [self vouchrConfigString]; //use the method created in "Injecting vouchrConfig" above
    NSString *vouchrAppSetup = [self vouchrAppSetupStringWithConfig:vouchrConfig userToken:self.userToken];
    WKUserScript *vouchrAppSetupScript = [[WKUserScript alloc] initWithSource:vouchrAppSetup injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO]; 
    [configuration.userContentController addUserScript:vouchrAppSetupScript]; //inject JS here

    self.webView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:configuration];
    self.webView.navigationDelegate = self;
    [self.view addSubview:self.webView];
    
    /* subdirectory is the folder containing index.html, an example is given; yours could be different */
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"html" subdirectory:@"web-discover-sdk"];
    [self.webView loadFileURL:url allowingReadAccessToURL:url]; //loads the webview
}
func setupWebView() {
    let configuration = WKWebViewConfiguration()
    let userContentController = WKUserContentController()

    // To receive callbacks, register the <#name#> from window.webkit.messageHandlers.<#name#>.postMessage('#event_tag#'), here "vouchr" is used as an example
    userContentController.add(self, name: "vouchr")
    configuration.userContentController = userContentController

    let vouchrConfig = vouchrConfigString() //use the method created in "Injecting vouchrConfig" above
    let vouchrAppSetup = vouchrAppSetupString(withConfig: vouchrConfig, userToken: userToken)
    let vouchrAppSetupScript = WKUserScript(source: vouchrAppSetup, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
    configuration.userContentController.addUserScript(vouchrAppSetupScript) //inject JS here

    self.webView = WKWebView(frame: view.frame, configuration: configuration)
    self.webView.navigationDelegate = self
    self.view.addSubview(webView)

    // subdirectory is the parent folder of index.html, an example is given; yours could be different
    let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "web-discover-sdk")
    self.webView.loadFileURL(url, allowingReadAccessTo: url) //loads the webview
}

Receiving Callbacks

To receive the callbacks we setup in Setup Callbacks in index.html, ensure your view controller conforms to the WKScriptMessageHandler protocol, which has one required method, userContentController:didReceiveScriptMessage, shown in the sample code below. Recall that we added window.webkit.messageHandlers.<#name#>.postMessage('#event_tag#') to each function. The <#name#> corresponds to message.name, and the #event_tag# corresponds to message.body.

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSString *messageHandlerName = @"vouchr"; //the example name given above
    if ([message.name isEqualToString:messageHandlerName]) {
        if ([message.body isEqualToString:@"active"]) {
            //screen is active
        } else if ([message.body isEqualToString:@"summary"]) {
            //setup and launch summary screen
        } else if ([message.body containsString:@"complete"]) {
            //e.g., get voucherId
        } else if ([message.body isEqualToString:@"error"]) {
            //handle error
        } else if ([message.body isEqualToString:@"cancel"]) {
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    }
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    let messageHandlerName = "vouchr" //the example name given above
    if (message.name == messageHandlerName) {
        if (message.body == "active") {
            //screen is active
        } else if (message.body == "summary") {
            //setup and launch summary screen
        } else if message.body.contains("complete") {
            //e.g., get voucherId
        } else if (message.body == "error") {
            //handle error
        } else if (message.body == "cancel") {
            dismiss(animated: true)
        }
    }
}