Skip to content

Move SDK file storage to Application Support directory#514

Draft
ajaysubra wants to merge 1 commit intomasterfrom
fix/issue-179-move-files-to-application-support
Draft

Move SDK file storage to Application Support directory#514
ajaysubra wants to merge 1 commit intomasterfrom
fix/issue-179-move-files-to-application-support

Conversation

@ajaysubra
Copy link
Copy Markdown
Contributor

Description

Moves Klaviyo SDK file storage from Library/ to Library/Application Support/com.klaviyo/ to follow Apple's File System Programming Guide best practices. The change includes automatic migration for existing installations with zero user impact.

Fixes #179

Due Diligence

  • I have tested this on a simulator or a physical device.
  • I have added sufficient unit/integration tests of my changes.
  • I have adjusted or added new test cases to team test docs, if applicable.
  • I am confident these changes are compatible with all iOS and XCode versions the SDK currently supports.

Release/Versioning Considerations

  • Patch Contains internal changes or backwards-compatible bug fixes.
  • Minor Contains changes to the public API.
  • Major Contains breaking changes.
  • Contains readme or migration guide changes.
  • This is planned work for an upcoming release.

Changelog / Code Overview

What Changed

Before: SDK files stored at Library/klaviyo-{apiKey}-state.json
After: SDK files stored at Library/Application Support/com.klaviyo/klaviyo-{apiKey}-state.json

Implementation Details

  1. Extended FileClient (Sources/KlaviyoCore/Utils/FileUtils.swift)

    • Added createDirectory method for creating directory hierarchies
    • Added copyItem method for file operations
  2. New Migration Module (Sources/KlaviyoCore/Utils/FileClientMigration.swift)

    • klaviyoApplicationSupportDirectory() - Returns proper Application Support path
    • migrateFilesIfNeeded(apiKey:) - Handles automatic migration
    • Transactional approach: Copy → Verify → Delete (with rollback on failure)
    • Idempotent: safe to run multiple times
  3. Updated State Management (Sources/KlaviyoSwift/StateManagement/KlaviyoState.swift)

    • klaviyoStateFile() now calls migration function
    • Migration runs lazily on SDK initialization
  4. Comprehensive Test Coverage (Tests/KlaviyoSwiftTests/FileClientMigrationTests.swift)

    • 10 new unit tests covering all scenarios
    • Fresh install, successful migration, failures, rollback, idempotency
  5. Test Infrastructure Updates

    • Updated 4 test utility files with new FileClient method mocks

Migration Behavior

New Installations:

  • Files created directly in Application Support directory
  • No migration needed

Existing Installations:

  • On first SDK initialization after update:
    • Creates Application Support directory structure
    • Copies all klaviyo-* files (state.json + legacy .plist files)
    • Verifies state file integrity
    • Removes old files on success
  • Completely transparent to SDK users

Failure Handling:

  • Directory creation fails → Use old location, log error
  • File copy fails → Rollback partial migration, use old location
  • Verification fails → Rollback, use old location
  • All failures logged via LoggerClient
  • SDK continues working in all cases

Test Plan

Unit Tests

✅ All 224 tests passing (including 10 new migration tests)

xcodebuild test -scheme klaviyo-swift-sdk-Package -destination "platform=iOS Simulator,name=iPhone 17 Pro"

New test coverage:

  1. ✅ Directory path construction
  2. ✅ Fresh install scenario (no old files)
  3. ✅ Already migrated scenario (skip migration)
  4. ✅ Successful migration with multiple files
  5. ✅ Migration with only state file (common case)
  6. ✅ Directory creation failure handling
  7. ✅ File copy failure with rollback
  8. ✅ Verification failure with rollback
  9. ✅ Idempotency (safe to run twice)
  10. ✅ Multiple API keys handled independently

Manual Testing Steps

Test 1: Fresh Install

  1. Clean install app using SDK
  2. Initialize SDK with API key
  3. Verify files created at Library/Application Support/com.klaviyo/
  4. Verify no files at Library/klaviyo-*

Test 2: Existing Installation Migration

  1. Use simulator with existing SDK installation (files in Library/)
  2. Update to new SDK version
  3. Launch app and initialize SDK
  4. Verify files migrated to Library/Application Support/com.klaviyo/
  5. Verify old files removed from Library/
  6. Verify SDK functions normally (events, profiles work)

Test 3: Migration Idempotency

  1. After Test 2, restart app
  2. Verify no errors in logs
  3. Verify files remain in Application Support directory
  4. Verify SDK works normally

Verification Commands

# Check file locations in simulator
ls -la ~/Library/Developer/CoreSimulator/.../Library/
ls -la ~/Library/Developer/CoreSimulator/.../Library/Application\ Support/com.klaviyo/

Related Issues/Tickets

  • Fixes File Path #179 (opened May 2024)
  • Issue marked as "evergreen" - improves SDK quality and iOS compliance

}
}

environment.logger.error("Successfully migrated \(migratedFiles.count) file(s) to Application Support")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Success message logged at error level

Medium Severity

The successful migration message is logged via environment.logger.error, which in production calls os_log with .error type. Every successful migration will appear as an error in the device's system logs and Xcode console. Since LoggerClient only exposes an error method, the success log either needs a new log level added or the message needs to be removed entirely to avoid polluting error diagnostics.

Fix in Cursor Fix in Web

if environment.fileClient.fileExists(newStateFile.path) {
// Migration already completed
return newDirectory
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant filesystem calls on every state save

Low Severity

migrateFilesIfNeeded is called on every debounced state save (via klaviyoStateFile), not just at initialization. After migration completes, every save still calls createDirectory (line 30) before the fileExists check (line 40), resulting in two unnecessary filesystem syscalls per save. Reordering the "already migrated" check before the createDirectory call, or caching the resolved directory, would eliminate the repeated I/O on the hot path.

Additional Locations (1)

Fix in Cursor Fix in Web

Fixes #179

This change moves all Klaviyo SDK files from the Library/ directory
to Library/Application Support/com.klaviyo/ following Apple's File
System Programming Guide best practices.

## Changes

- Add automatic file migration on SDK initialization
- Files are migrated transparently with zero user impact
- Migration is transactional with rollback on failure
- Idempotent and safe to run multiple times

## Implementation

- Extended FileClient with createDirectory and copyItem methods
- Added FileClientMigration module with migration logic
- Updated KlaviyoState to use migrated directory
- Added comprehensive test coverage (10 new tests)

## Migration Behavior

New installations: Files created directly in new location
Existing installations: Automatic migration on first init after update
Migration failure: Falls back to old location, SDK continues working

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@ajaysubra ajaysubra force-pushed the fix/issue-179-move-files-to-application-support branch from 037d6a8 to d41e8ab Compare February 20, 2026 04:39
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

return oldDirectory
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration permanently stuck if destination file pre-exists

Low Severity

FileManager.copyItem throws (error code 516) when the destination file already exists. The migration loop doesn't check for or remove a pre-existing destination before calling copyItem. If a previous migration partially failed AND rollbackMigration also partially failed (leaving orphaned files in the new directory), all subsequent migration attempts will permanently fail on those orphaned files, since rollbackMigration only cleans up files from the current migratedFiles list, not leftover files from prior runs. The SDK falls back to the old directory gracefully, but migration never succeeds.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

File Path

1 participant