Quick tip: Communicate with your remote API!

Options
aidan
aidan Posts: 32 🔥

Camera Kit comes with the ability to communicate with a private hosted API. This is hugely beneficial if you want to access data or files from your own servers. Here's a quick tip on how to get it working!

1) Set up your API by following the tutorial here. Note: you will NOT be able to edit your API after you submit it so you will have to start over if you make any mistakes.
https://docs.snap.com/camera-kit/quick-start/integrate-sdk/integrate-sdk-app/guides/communicating-between-lenses-and-app#remote-apis-beta

2) When you go to submit a Lens with your API make sure to:

  • Submit it to a Lens Folder under your Camera Kit account
  • Submit it as Available But Not Published
  • Add to your Lens Group so its available in your app

If you try to test your Lens now you will likely get this error
[error] RemoteServiceModule: Wrong api visibility 100 0

This error is confusing as there's only one visibility setting in the API which is PRIVATE but it actually has nothing to do with that. Instead we have to implement this undocumented part of the code base which can be found in the sample as CatFactLensRemoteApi.swift. We will now edit this code to make it work for us!

3) Create a new Swift file like MyAppLensRemoteApi.swift

import Foundation
import SCSDKCameraKit

// Example implementation of [LensRemoteApiService] which receives requests from lenses that use the
// [Remote Service Module](https://docs.snap.com/lens-studio/references/guides/lens-features/remote-apis/remote-service-module)
// feature. The remote API spec ID is provided for demo and testing purposes -
// CameraKit user applications are expected to define their own specs for any remote API that they are interested to
// communicate with. Please reach out to CameraKit support team at https://docs.snap.com/snap-kit/support
// to find out more on how to define and use this feature.
class MyAppRemoteApiServiceProvider: NSObject, LensRemoteApiServiceProvider {

    var supportedApiSpecIds: Set<String> = ["your-api-ID"]

    func remoteApiService(for lens: Lens) -> LensRemoteApiService {
        return MyAppRemoteApiService()
    }
}

class MyAppRemoteApiService: NSObject, LensRemoteApiService {

    private enum Constants {
        static let scheme = "https"
        static let host = "host-domain-without-http-https"
    }

    private let urlSession: URLSession = .shared

    func processRequest(
        _ request: LensRemoteApiRequest,
        responseHandler: @escaping (LensRemoteApiServiceCallStatus, LensRemoteApiResponseProtocol) -> Void
    ) -> LensRemoteApiServiceCall {
        guard let url = url(request: request) else {
            return IgnoredRemoteApiServiceCall()
        }

        let task = urlSession.dataTask(with: url) { data, urlResponse, error in
            let apiResponse = LensRemoteApiResponse(
                request: request,
                status: error != nil ? .badRequest : .success,
                metadata: [:],
                body: data)

            responseHandler(.answered, apiResponse)
        }

        task.resume()

        return URLRequestRemoteApiServiceCall(task: task)
    }

    private func url(request: LensRemoteApiRequest) -> URL? {
        var components = URLComponents()
        components.host = Constants.host
        components.path = "/" + request.endpointId
        components.scheme = Constants.scheme
        return components.url
    }
}

You may or may not need to add this to the same file:

class URLRequestRemoteApiServiceCall: NSObject, LensRemoteApiServiceCall {

    let task: URLSessionDataTask

    let status: LensRemoteApiServiceCallStatus = .ongoing

    init(task: URLSessionDataTask) {
        self.task = task
        super.init()
    }

    func cancelRequest() {
        task.cancel()
    }

}

class IgnoredRemoteApiServiceCall: NSObject, LensRemoteApiServiceCall {
    let status: LensRemoteApiServiceCallStatus = .ignored

    func cancelRequest() {
        // no-op
    }
}

4) Now let's pass our new RemoteAPiService into CameraKit! If you don't already have a custom CameraController now is the time to make one and pass it to remoteApiServiceProviders:

class CustomizedCameraController: CameraController {
    ...
    override func configureDataProvider() -> DataProviderComponent {
        DataProviderComponent(
            deviceMotion: nil, userData: UserDataProvider(), lensHint: nil, location: nil,
            mediaPicker: lensMediaProvider, remoteApiServiceProviders: [MyAppRemoteApiServiceProvider()])
    }
}

That's it, if everything was set up correctly you should start receiving API responses within your Lens! Thank you to @Roy for your post about the api visibility error, I never would have thought to check the data providers. Hope this saves all of you some time!

Comments

  • aidan
    aidan Posts: 32 🔥
    Options

    Extra Tip: If you're like me and ran into Auth issues (guess Remote Services API setup does not work!) you can still pass your header through in the Swift file by editing this section of code:

    var requestWithHeaders = URLRequest(url: url)
    requestWithHeaders.addValue(YourApiKey, forHTTPHeaderField: "x-app-secret")
    
            let task = urlSession.dataTask(with: requestWithHeaders) { data, urlResponse, error in
                let apiResponse = LensRemoteApiResponse(
                    request: request,
                    status: error != nil ? .badRequest : .success,
                    metadata: [:],
                    body: data)
    
                responseHandler(.answered, apiResponse)
            }