Enterprise Features
Tracelet comes equipped with several high-security features designed for enterprise use cases such as military tracking, HIPAA-compliant patient monitoring, and cryptographically verified logistics.
1. Data Encryption (SQLCipher)
When Tracelet records a location in the background, it temporarily stores it in an internal SQLite database before syncing it to your server. If a device is stolen, physical access could potentially allow an attacker to read these historical routes.
Tracelet solves this by offering At-Rest Database Encryption using SQLCipher.
How Data Encryption Works
- When you enable the
encryptDatabaseflag, the underlying Rust SQLite database is instantly encrypted with AES-256 via SQLCipher. - By default, Tracelet automatically generates a secure 256-bit key and stores it safely in the platform’s native secure storage (Android Keystore / iOS Keychain). You don’t have to manage the key manually!
- All data written to the disk becomes completely unreadable without the key.
- Existing unencrypted databases are automatically migrated to encrypted when the flag is enabled.
Implementation
To enable encryption using the platform-managed secure key (Recommended):
final config = Config.balanced(
security: tl.SecurityConfig(
encryptDatabase: true, // This MUST be true to enable encryption
),
);Custom Key Management (Advanced)
If your app prefers to manage its own key material (e.g., using flutter_secure_storage), you can provide a custom encryptionKey. Note that encryptDatabase must still be true.
// Generate a secure random key
final customKey = await tl.Tracelet.generateEncryptionKey();
await secureStorage.write(key: 'my_db_key', value: customKey);
// Pass it to Tracelet
final config = Config.balanced(
security: tl.SecurityConfig(
encryptDatabase: true,
encryptionKey: await secureStorage.read(key: 'my_db_key'),
),
);2. Audit Trail Management & Verification
In sensitive logistics or law enforcement, you must be able to mathematically prove that a location record was not tampered with, spoofed, or altered after the fact. Tracelet provides an Audit Trail feature to create a cryptographic chain of custody.
How Audit Trail Management Works
When AuditConfig.enabled is set to true, Tracelet does not just save latitude and longitude. It creates a “blockchain-like” record for every location. The underlying Rust engine ensures this process is atomic and foolproof:
- The Genesis Block: When the engine first starts on a new device, it calculates a genesis hash:
SHA-256("tracelet:genesis:{device_id}"). - Canonical String Generation: For each location, Tracelet generates a strict, deterministic string format combining the previous hash and the new location’s exact data.
- Hashing: This canonical string is hashed (using SHA-256 by default), producing the
hashfor the current location. - The Chain: This hash becomes the
previous_hashfor the next location, creating an unbroken chain. If an attacker modifies even a single coordinate of a past location in the database, the hash of that location will change, breaking the entire chain of subsequent hashes.
Configuration
final config = Config.highAccuracy(
audit: tl.AuditConfig(
enabled: true,
hashAlgorithm: tl.HashAlgorithm.sha256,
),
);The JSON Payload
When Tracelet syncs these locations to your backend, the JSON payload for each location will directly contain the audit fields.
{
"device": "tracelet-example",
"locationCount": 1,
"locations": [
{
"uuid": "loc-1234-abcd",
"timestamp": "2024-01-01T00:00:00Z",
"is_moving": true,
"coords": {
"latitude": 37.774900,
"longitude": -122.419400,
"accuracy": 10.0,
"speed": 15.5,
"heading": 90.0,
"altitude": 100.0
},
"audit_hash": "a1b2c3d4e5f6...",
"audit_previous_hash": "f6e5d4c3b2a1...",
"audit_chain_index": 42
}
]
}Server-Side Verification (How to Verify the Code)
To verify the integrity of the data on your server, you must recreate the exact canonical string that the Tracelet Rust core uses and compare the resulting hash against the audit_hash field in the payload.
1. Build the Canonical String
The string MUST be formatted exactly like this, separated by the pipe | character:
{audit_previous_hash}|TRACELET_AUDIT|{audit_chain_index}|{uuid}|{latitude}|{longitude}|{timestamp}|{accuracy}|{speed}|{heading}|{altitude}|{is_moving}
Important Formatting Rules (Based on Tracelet’s Rust Core):
latitudeandlongitude: Formatted to exactly 6 decimal places (e.g.,37.774900).accuracy,speed,heading,altitude: Formatted to exactly 2 decimal places (e.g.,10.00).is_moving: Formatted as1(true) or0(false).
Example Canonical String:
f6e5d4c3b2a1...|TRACELET_AUDIT|42|loc-1234-abcd|37.774900|-122.419400|2024-01-01T00:00:00Z|10.00|15.50|90.00|100.00|12. Hash and Compare
Pass the constructed canonical string through a standard SHA-256 hashing function and convert it to a lowercase hexadecimal string.
- If your calculated hash matches the
audit_hashfield: The location is mathematically proven to be untampered. - If it does not match: The location data has been altered. You should immediately flag this record and investigate the device.
3. Location Accuracy Profiles (HF / LF)
When reviewing the Tracelet Example App, you will notice buttons for LF Accuracy and HF Accuracy. These map directly to Tracelet’s pre-built Configuration Profiles.
- LF (Low Frequency / Low Accuracy): This maps to the
Config.lowPower()profile. It restricts the engine to use Cell-Tower and Wi-Fi triangulation only (no GPS). It is highly battery efficient but provides a wider accuracy radius. - HF (High Frequency / High Accuracy): This maps to the
Config.highAccuracy()profile. It forces the physical GPS chip on, providing 1-5 meter accuracy at the cost of higher battery drain.
4. Carbon Footprint Estimator & Trip Reports
Tracelet includes a built-in CarbonEstimator utility. It fuses activity recognition (detecting if the user is driving, walking, or cycling) with precise geographic distance to calculate the estimated CO₂ emissions for a trip. This is crucial for sustainability reporting, carbon-offset programs, or ESG (Environmental, Social, and Governance) compliance.
How it works
- You instantiate a
CarbonEstimator. - When the user starts moving (
isMoving == true), you callstartTrip(). - You feed real-time activity changes (e.g., switching from walking to driving) and locations into the estimator.
- When the user stops moving (
isMoving == false), you callendTrip()to generate aTripCarbonSummary.
Implementation Example
final carbonEstimator = tl.CarbonEstimator();
// 1. Listen for motion changes to start and end trips
Tracelet.onMotionChange((loc) {
if (loc.isMoving) {
carbonEstimator.startTrip();
} else {
final summary = carbonEstimator.endTrip();
if (summary != null) {
print('Trip ended: \${summary.totalCarbonGrams}g CO2');
print('Total Distance: \${summary.totalDistanceMeters}m');
print('Dominant Mode: \${summary.dominantMode}'); // e.g. "driving"
}
}
});
// 2. Feed Activity Recognition data to detect the transport mode
Tracelet.onActivityChange((evt) {
// Activity names map to emissions factors internally (e.g., driving vs walking)
carbonEstimator.setActivity(evt.activity.name);
});
// 3. Feed location coordinates to accumulate distance accurately
Tracelet.onLocation((loc) {
carbonEstimator.onLocationReceived(
loc.coords.latitude,
loc.coords.longitude,
);
});The resulting TripCarbonSummary can then be synced to your server or displayed directly to the user to encourage eco-friendly transport choices.
5. Remote Configuration
Fetched and applied natively on both iOS and Android since 3.6.2. Earlier versions accepted remoteConfigUrl but never fetched it — the native side silently fell back to the local config.
For fleets and large deployments you often need to change tracking behavior — accuracy, sync cadence, geofence radius, battery budget — without shipping an app update. Tracelet’s Remote Configuration lets the SDK pull a config document from your own HTTPS endpoint and apply it on top of the local config at runtime.
How Remote Configuration Works
- You set
remoteConfigUrl(and optional headers) in yourAppConfig. - On
ready(), the SDK first applies the last successfully fetched config from an on-device cache — so a relaunch resumes on your latest published settings instantly and offline, before any network call, and without blockingready(). - It then fetches a fresh copy from your endpoint in the background and applies it through the same runtime path as
setConfig()— restarting the active tracking pipeline only when a tracking-relevant key actually changed. - It keeps the config fresh by re-fetching on the
remoteConfigRefreshIntervalcadence (in minutes).
Only HTTPS URLs are honored — an http:// URL is rejected and logged, because the configuration controls tracking behavior and must not be tamperable in transit.
The Endpoint Contract
Your endpoint answers a GET with a JSON object shaped like a Tracelet config map — either flat, or the nested { "geo": {…}, "app": {…}, "http": {…} } form that Config.toMap() produces. Only the keys you include are overridden; every other setting keeps its local value.
{
"geo": { "distanceFilter": 25.0, "desiredAccuracy": 0 },
"app": { "heartbeatInterval": 120 },
"http": { "autoSync": true }
}Configuration
final config = Config.balanced().copyWith(
app: tl.AppConfig(
remoteConfigUrl: 'https://config.mycompany.com/tracelet/fleet-a.json',
remoteConfigHeaders: {'Authorization': 'Bearer $token'},
remoteConfigTimeout: 15000, // milliseconds
remoteConfigRefreshInterval: 60, // minutes
),
);| Field | Type | Default | Description |
|---|---|---|---|
remoteConfigUrl | String? | null | HTTPS endpoint returning the JSON config map. null disables remote config. |
remoteConfigHeaders | Map<String, String>? | null | Headers sent with the fetch (e.g. auth token). |
remoteConfigTimeout | int | 60000 | Fetch timeout in milliseconds. |
remoteConfigRefreshInterval | int | 1440 | Minutes between background refreshes (floored at 15 minutes natively). 0 fetches once and never repeats. |
Applied remote values are merged into the device’s persisted config, and remote overrides win over local ones. Only publish values you intend every device fetching that URL to adopt — scope per-fleet or per-cohort by pointing each build at a different URL.
Observing applied remote config from Dart
Remote config is fetched and applied on the native side. Since 3.6.10, every time a remote override is applied — both the cached copy restored at ready() and each background refresh — the SDK surfaces it back to Dart:
Tracelet.activeConfig(and thereforetracelet_doctorand the Dart-side battery-budget engine) now reflects the fetched values, instead of only the last config you set locally.- Subscribe to react when a server-driven change lands:
// Callback — fires after a remote override is applied.
// Tracelet.activeConfig already reflects the new values when this fires.
Tracelet.onRemoteConfig((Config config) {
print('Remote battery budget: ${config.geo.batteryBudgetPerHour}%/hr');
});
// …or listen to the broadcast stream directly.
final sub = Tracelet.remoteConfigStream.listen((config) {
// react to the merged, now-active config
});Before 3.6.10, remote values were applied to native tracking but were not mirrored into Tracelet.activeConfig, so diagnostics could still show the last locally-set value even though tracking already used the remote one.