Proxying License Server
Increased control over network requests for Enterprise Plan customers
Context
Argmax SDK requires a valid and active license to unlock licensed software functionality on each end-user device. Licenses are created and refreshed automatically when Argmax SDK is initialized like so:
import Argmax
await ArgmaxSDK.with(ArgmaxConfig(apiKey: "ax_***"))
In specific:
- New license creation requests are sent to
https://api.argmaxinc.com/v1/license-token/create
- License refresh requests are sent to
https://api.argmaxinc.com/v1/license-token/refresh
Offline usage for Argmax SDK is possible for at most 30 days without a successful license refresh. End-user devices must create a license during first use. After that, Argmax SDK will attempt to refresh the license every 30 minutes. Refresh attempts are allowed to fail (e.g. due to no internet connection) until the license expires in 30 days due to unsuccessful refresh attempts.
Benefits of Proxying
Proxying enables Enterprises that use Argmax SDK in their application to route all outbound network traffic originating from Argmax through their own proxy layer, rather than allowing Argmax to communicate with Argmax API endpoints directly. This design eliminates Argmax's need to “call home”.
Proxying enables:
- Programmatic auditability for compliance
- Control over network traffic, simplifying firewall and network policy configuration
Basic Example
Step 0: Ensure you are on Enterprise Plan
Before getting started, note that Argmax SDK will only allow proxying for Enterprise Plan customers. You can verify your subscription type with the following snippet:
import Argmax
let subscriptionType = await ArgmaxSDK.licenseInfo().subscriptionType
If you are not on the Enterprise Plan yet but need proxying, please email customer@argmaxinc.com to request an upgrade.
Step 1: Implement your custom APIRequestHandler
Argmax SDK defines the APIRequestHandler
protocol. Extend this protocol to implement your custom logic for proxying. Below is an example CustomAPIRequestHandler
that prints the requests and responses for inspection:
import Argmax
import Foundation
final class CustomAPIRequestHandler: APIRequestHandler {
private let session: URLSession
init(session: URLSession = .shared) {
self.session = session
}
func sendRequest(_ request: ApiRequest) async throws -> (Data, HTTPURLResponse) {
let urlRequest = try request.constructURLRequest()
print("\n[>>>] Sending request:")
print(" URL: \(urlRequest.url?.absoluteString ?? "nil")")
print(" Method: \(urlRequest.httpMethod ?? "nil")")
print(" Headers: \(urlRequest.allHTTPHeaderFields?.description ?? "nil")")
if let body = urlRequest.httpBody, let bodyString = String(data: body, encoding: .utf8) {
print(" Body: \(bodyString)")
}
let (data, response) = try await session.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
throw NSError(domain: "Invalid response", code: 1, userInfo: nil)
}
print("\n[<<<] Received response:")
print(" Status: \(httpResponse.statusCode)")
print(" Headers: \(httpResponse.allHeaderFields.description)")
if let responseString = String(data: data, encoding: .utf8) {
print(" Body: \(responseString)")
}
return (data, httpResponse)
}
}
Step 2: Register your CustomAPIRequestHandler
import Argmax
...
let config = ArgmaxConfig(apiKey: "ax_***")
config.setAPIRequestHandler(CustomRequestHandler())
await ArgmaxSDK.with(config)
An example output when using Argmax SDK with this CustomAPIRequestHandler
registered is as follows:
[>>>] Sending request:
URL: https://api.argmaxinc.com/v1/license-token/create
Method: POST
Headers: ["Content-Type": "application/json", "User-Agent": "argmax-sdk-swift/1.7.10", "AX-API-KEY": "ax_****", "Content-Length": "158"]
Body: {"device_sku":"Mac15,9","app_bundle_id":"com.argmax.sdk","os_version":"15.6.1","fwk_version":"1.7.10","license_id":"****"}
[<<<] Received response:
Status: 201
Headers: [AnyHashable("x-frame-options"): DENY, AnyHashable("Content-Length"): 889, AnyHashable("content-security-policy"): style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; default-src 'none'; frame-ancestors 'none', AnyHashable("cross-origin-opener-policy"): same-origin, AnyHashable("Date"): Thu, DD MM YYYY HH:MM:SS GMT, AnyHashable("referrer-policy"): same-origin, AnyHashable("Content-Type"): application/json, AnyHashable("x-content-type-options"): nosniff, AnyHashable("Server"): gunicorn]
Body: {"access_token": "****", "refresh_token": "****"}
where ****
s were manually inserted to deidentify this example.