feat: preserve original device source identity during import#146
feat: preserve original device source identity during import#146tobigs wants to merge 2 commits intoashtom:mainfrom
Conversation
SwiftLint is not always available in all development environments. Gracefully skip linting with a warning instead of failing the build.
Parse the device string from Apple Health export XML to extract hardware model, software version, and device name. This preserves the original data source identity when importing records into HealthKit, rather than attributing everything to the import app. Adds DeviceStringParser with full test coverage.
There was a problem hiding this comment.
Pull request overview
Enhances Apple Health export.xml import so imported HealthKit samples retain original device identity (e.g., iPhone vs Apple Watch) by parsing the export’s serialized <Device> string and attaching an HKDevice to saved samples.
Changes:
- Add
DeviceStringParserto extractHKDevice-relevant fields from Apple Health export device strings. - Update
Importerto attach anHKDeviceand additional metadata when saving imported samples (including workouts). - Add comprehensive tests for
DeviceStringParserand make SwiftLint build phase optional when SwiftLint isn’t installed.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| HKImportTests/DeviceStringParserTests.swift | Adds unit tests covering realistic, partial, and invalid device strings. |
| HKImport/Importer.swift | Captures source/device attributes from XML and attaches an HKDevice + metadata when creating samples. |
| HKImport/DeviceStringParser.swift | Implements parsing for serialized HKDevice strings from Apple Health exports. |
| HKImport.xcodeproj/project.pbxproj | Adds the new parser file to build sources and makes SwiftLint step non-blocking if missing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let device = HKDevice( | ||
| name: item.sourceName, | ||
| manufacturer: parsed?.manufacturer, | ||
| model: parsed?.model, | ||
| hardwareVersion: parsed?.hardwareVersion, | ||
| firmwareVersion: nil, | ||
| softwareVersion: parsed?.softwareVersion, | ||
| localIdentifier: nil, | ||
| udiDeviceIdentifier: nil | ||
| ) |
There was a problem hiding this comment.
HKDevice is being created with name: item.sourceName even though the export’s <Device> string includes a name: field (and the PR goal is to preserve the original device identity). This will typically attribute the device name to the original source/app rather than the actual hardware (e.g., Apple Watch vs iPhone). Consider using the parsed device name (e.g., parsed?.name) as the primary HKDevice name (fallback to sourceName only when the device string has no name), and avoid constructing a device at all (pass device: nil) when there’s no parsed device info.
| let device = HKDevice( | |
| name: item.sourceName, | |
| manufacturer: parsed?.manufacturer, | |
| model: parsed?.model, | |
| hardwareVersion: parsed?.hardwareVersion, | |
| firmwareVersion: nil, | |
| softwareVersion: parsed?.softwareVersion, | |
| localIdentifier: nil, | |
| udiDeviceIdentifier: nil | |
| ) | |
| let device: HKDevice? = parsed.map { parsed in | |
| HKDevice( | |
| name: parsed.name ?? item.sourceName, | |
| manufacturer: parsed.manufacturer, | |
| model: parsed.model, | |
| hardwareVersion: parsed.hardwareVersion, | |
| firmwareVersion: nil, | |
| softwareVersion: parsed.softwareVersion, | |
| localIdentifier: nil, | |
| udiDeviceIdentifier: nil | |
| ) | |
| } |
| if let deviceString = item.deviceString { | ||
| metadata["HKImportOriginalDeviceString"] = deviceString | ||
| } |
There was a problem hiding this comment.
Saving the raw deviceString into HealthKit metadata can persist fields like UDIDeviceIdentifier (present in real exports). That can be sensitive/identifying data and isn’t necessary to achieve device attribution. Consider either not storing the full device string at all, or sanitizing it (e.g., strip UDID/localIdentifier/creation date) and/or store only the parsed, non-identifying components you need.
| if let deviceString = item.deviceString { | |
| metadata["HKImportOriginalDeviceString"] = deviceString | |
| } | |
| if let manufacturer = parsed?.manufacturer { | |
| metadata["HKImportOriginalDeviceManufacturer"] = manufacturer | |
| } | |
| if let model = parsed?.model { | |
| metadata["HKImportOriginalDeviceModel"] = model | |
| } | |
| if let hardwareVersion = parsed?.hardwareVersion { | |
| metadata["HKImportOriginalDeviceHardwareVersion"] = hardwareVersion | |
| } | |
| if let softwareVersion = parsed?.softwareVersion { | |
| metadata["HKImportOriginalDeviceSoftwareVersion"] = softwareVersion | |
| } |
Problem
When importing Apple Health data via
export.xml, all records are attributed to the import app itself. The original device source identity (e.g., Apple Watch, iPhone) is lost, making it impossible to distinguish which device originally recorded the data.Solution
Parse the
<Device>string from the Apple Health export XML to extract hardware model, software version, and device name. Attach the originalHKDeviceto each importedHKSample, preserving the source identity.What's included
DeviceStringParser— parses the<<DeviceName>:...>format intoHKDeviceproperties (hardware model, software version, name)Importerupdate — uses the parsed device info when saving samples to HealthKitDeviceStringParser