Axle works with Enode to provide access to a wide array of EVs, chargers, heat pumps, and batteries. Axle will call the Enode API directly on your behalf to read from and control assets. We provide a pre-built integration to help you gather credentials from your users, access assets, and confer the ability to control them to Axle. Enode host their own onboarding flow - LinkUI. This guide will demonstrate how to use Axle Components and LinkUI to onboard and connect an asset. If you’re developing in another platform, get in touch to discuss how we can provide a native integration.

Step 1: Render the Axle Enode component

This is a helper component, which will automatically generate and return correctly scoped Enode tokens. The end user will see a loading indicator, and you’ll need to listen to messages to correctly show the Native Enode SDK.
Example component view
import SwiftUI
import WebKit

struct WebView: UIViewRepresentable {
    let url: String

    func makeUIView(context: Context) -> WKWebView {
        let webView = WKWebView()
        return webView
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        guard let url = URL(string: url) else { return }
        let request = URLRequest(url: url)
        webView.load(request)
    }
}

struct AxleEnodeIntegration: View {
    var body: some View {
        NavigationView {
            WebView(url: "https://app.axle.energy/form/enode/vehicle")
        }
    }
}

Step 2: Listen for the Enode token from the component

You’ll need to listen for the Enode token once the component has authenticated and fetched from Axle. The Enode component automatically sends this token using WKWebView message handlers on iOS. We’ll first register the message handlers in the WebView. This example is using SwiftUI, but it’s also easy to do this in UIKit by implementing the WKScriptMessageHandler delegate.
Register message handler in the WebView
// In EnodeWebView struct

// Two way communication with parent view
@Binding var messageHandler: ((String, Any?) -> Void)?

func makeUIView(context: Context) -> WKWebView {
    let webView = WKWebView()

    // Register a message listener in the webview
    let userContentController = webView.configuration.userContentController
    userContentController.add(context.coordinator, name: "axleEnode")

    return webView
}

// Called automatically by SwiftUI when instantiating the view
func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

class Coordinator: NSObject, WKScriptMessageHandler {
    var parent: WebView

    init(_ parent: WebView) {
        self.parent = parent
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        parent.messageHandler?(message.name, message.body)
    }
}


Adding a JS listener
struct AxleEnodeIntegration: View {
    @State private var messageHandler: ((String, Any?) -> Void)?

    var body: some View {
        NavigationView {
            WebView(url: "https://app.axle.energy/form/enode/vehicle", messageHandler: $messageHandler)
        }
        .onAppear {
            setupMessageHandler()
        }
    }

     private func setupMessageHandler() {
          messageHandler = { messageName, messageBody in
              DispatchQueue.main.async {
                  switch messageName {
                  case "handoff":
                      handleSuccess(data: messageBody)

                  default:
                      print("Unknown message: \(messageName)")
                  }
              }
          }
      }
}

Step 3: Handle the Enode token and trigger LinkSDK

Once you receive the Enode token from the Axle component, you’ll need to present the LinkSDK to complete the asset connection. First, import LinkKit and add the necessary state variables.
Import LinkKit and add state variables
import SwiftUI
import LinkKit

struct AxleEnodeIntegration: View {
    @State private var isLinkKitPresented = false
    @State private var linkToken: String? = nil
    @State private var messageHandler: ((String, Any?) -> Void)?

    // ... rest of the view
}
Next, update your message handler to extract the link token from the response and trigger the LinkSDK:
Handle token and trigger LinkSDK
private func setupMessageHandler() {
    messageHandler = { messageName, messageBody in
        DispatchQueue.main.async {
            switch messageName {
            case "handoff":
                // Extract link token from the response
                if let responseData = messageBody as? [String: Any],
                   let token = responseData["linkToken"] as? String {
                    linkToken = token
                    isLinkKitPresented = true
                }
            default:
                print("Unknown message: \(messageName)")
            }
        }
    }
}
Finally, add the LinkKit sheet to your view and handle the results:
Add LinkKit sheet
var body: some View {
    NavigationView {
        WebView(url: "https://app.axle.energy/form/enode/vehicle", messageHandler: $messageHandler)
    }
    .onAppear {
        setupMessageHandler()
    }
    .linkKitSheet(isPresented: $isLinkKitPresented, linkToken: linkToken) { linkResult in
        switch linkResult {
        case .success(let success):
            handleLinkSuccess(success)
        case .failure(let error):
            handleLinkError(error)
        }
    }
}
Add the success and error handlers:
Handle LinkSDK results
private func handleLinkSuccess(_ success: LinkSuccessMetadata) {
    print("LinkKit success: \(success)")
    // Move to the next stage in your onboarding!
}

private func handleLinkError(_ error: LinkError) {
    print("LinkKit error: \(error)")

    // Handle LinkKit error
    // Show error message to user or retry flow
}