Network tracking and interception

📘

Network tracking and interception SDK support

Network tracking and interception is supported in iOS SDK version 2.2.4 and up. For information on updating your SDK, see iOS integration.

Track network request

To manually track network requests:

let event = InterceptedTaskData(
    start: 1234,
    url: "https://www.example.com",
    duration: 100,
    method: "POST",
    requestHeaders: ["sample":"header"],
    responseHeaders: ["sample":"header"],
    httpProtocol: "http/1.1",
    initiator: "sample",
    status: "ok",
    statusCode: 200,
    cached: false,
    requestBody: "{\"example\":\"body\"}",
    responseBody: "{\"example\":\"body\"}"
)

Smartlook.instance.track(interceptedEvent: event)

Network request parameters

Network request parametersDescription
startUnix timestamp marking the start of request
durationDuration of the request
urlRequest url
methodHTTP method
httpProtocolProtocol used to execute the request (e.g. "http/1.1")
initiatorThe technology/framework used to execute the request (e.g. "OkHttp")
statusRequest was successful or failed
statusCodeHTTP status code
cached = TRUEShown if the response body was obtained from cache
requestBodyString representation of the request body. Maximum allowed number of characters is 10000 (every character beyond this limit is dropped)
responseBodyString representation of response body. Maximum allowed number of characters is 10000 (every character beyond this limit is dropped)
requestHeadersMap of request headers
responseHeadersMap of response headers

Network interceptor

A network interceptor lets you monitor and track REST requests as they flow between a client application and a server.

Default network interception

Interceptors can be used without any configuration:

URLSession
    .shared
    .dataTask(with: URLRequest(url: url)) { data, response, error in
         // Handle response
    }
    .intercept()
    .resume()

What data is captured

Due to Smartlook Privacy by design, not all data is captured.

The following data is captured by default:

  • All basic data: start, duration, url, method, protocol, initiator, status, statusCode, cached

The following headers are considered safe:

  • A-IM, Accept, Accept-Charset, Accept-Datetime, Accept-Encoding, Accept-Language, Access-Control-Request-Method, Access-Control-Request-Headers, Cache-Control, Connection, Content-Encoding, Content-Length, Content-MD5, Content-Type, Cookie, Date, Expect, Forwarded, From, Host, HTTP2-Settings, If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Max-Forwards, Origin, Pragma, Prefer, Range, Referrer, TE, Trailer, Transfer-Encoding, User-Agent, Upgrade, Via, Warning

The following data isn't captured:

  • requestBody and responseBody because they can contain sensitive data

Non-binary body interceptor

NonBinaryBodyInterceptor replaces requestBody and responseBody with String representations of the original bodies if the content-type is one of the following:

  • text/plain
  • text/csv
  • application/xml,
  • text/xml,
  • application/json,
  • application/ld+json,
  • text/x-markdown
URLSession
    .shared
    .dataTask(with: URLRequest(url: url)) { data, response, error in
        // Handle response
    }
    .intercept(interceptors: [NonBinaryBodyInterceptor()])
    .resume()

Headers interceptor

You can decide which headers to capture with HeadersInterceptor:

URLSession
    .sharedh
    .dataTask(with: URLRequest(url: url)) { data, response, error in
        // Handle response
    }
    .intercept(interceptors: [HeadersInterceptor(allowedHeaders: ["Example-Header", "Accept.*"])])
    .resume()

As you can see in the example, HeadersInterceptor lets you define set of allowed header names. Regular expressions can be used too, meaning that headers like Accept-Charset, Accept-Encoding, and Accept-Language can be captured.

Mask URL interceptor

You can filter sensitive parts of URLs with MaskUrlInterceptor:

let mask = InterceptorMask(regex: "(secret=)[^&]+(&*)", mask: "hidden")

URLSession
    .shared
    .dataTask(with: URLRequest(url: url)) { data, response, error in
        // Handle response
    }
    .intercept(interceptors: [MaskUrlInterceptor(mask: mask)])
    .resume()

MaskUrlInterceptor.Mask has two parameters:

  • regexPattern/ regex—Regular expression used to define parts of URL that should be masked. It can be defined as a String pattern or Regex.
  • replaceWith—A String used to mask/replace matched URL part.

An example URL with masking using the code example:

// URL before masking
https://www.example.com/sample?secret=token&test=param

// URL after masking
https://www.example.com/sample?secret=<hidden>&test=param

Mask body interceptor

You can mask sensitive parts of the request and response bodies using MaskBodyInterceptor:

let mask = InterceptorMask(regex: "(secret=)[^&]+(&*)", mask: "hidden")

URLSession
    .shared
    .dataTask(with: URLRequest(url: url)) { data, response, error in
        // Handle response
    }
    .intercept(interceptors: [MaskBodyInterceptor(mask: mask)])
    .resume()

MaskBodyInterceptor.Mask has two parameters:

  • regexPattern/ regex—Regular expression used to define parts of the body that should be masked. It can be defined as a String pattern or Regex.
  • replaceWith—A String used to mask/replace matched body parts.

An example body with masking using the code example:

// Body before masking
{
    "sample": "json",
    "sensitive": "password"
}

// Body after masking
{
    "sample": "json",
    "sensitive": <hidden>
}

Custom network interceptor

If none of the above interceptors suit your needs, you can define a custom interceptor:

URLSession
    .shared
    .dataTask(with: URLRequest(url: url)) { data, response, error in
        // Handle response
    }
    .intercept(interceptors: [CustomInterceptor()])
    .resume()
    
                                               
class CustomInterceptor: NetworkInterceptor {
    func onIntercept(task: URLSessionTask, responseData: Data?, intercepted: InterceptedRequestEvent?) -> InterceptedRequestEvent? {
        // Process original intercepted or create new.
    }
}

Function onIntercept has the following parameters:

  • taskURLSessionTask contains the request and response
  • responseData—If you used URLSessionDatatask, this parameter contains data from the response
  • intercepted—Contains models from previous interceptors. If this is first interceptor it contains the data that is intercepted by default.

Interceptor chaining

Multiple interceptors can be used to process the intercepted request:

URLSession
    .shared
    .dataTask(with: URLRequest(url: url)) { data, response, error in
        // Handle response
    }
    .intercept(interceptors: [
        NonBinaryBodyInterceptor(),
        MaskUrlInterceptor(mask: mask),
        CustomInterceptor()
    ])
    .resume()

📘

Interceptor order

When there are multiple interceptors, interceptors are used in the order they are entered. In the above example, InterceptorA is the first and InterceptorC is the last.