Skip to content

Commit a33a400

Browse files
authored
feat(connections): import and export connections on iOS (#1738)
* feat(connections): import and export connections on iOS * chore(connections): extract localizable strings for iOS import/export * refactor(connections): single tap target in import list, clean up export temp file * fix(connections): import TableProImport in fileExporter document and export tests after merge * fix(connections): route merged export tests through ConnectionImportDecoder
1 parent 956bcac commit a33a400

57 files changed

Lines changed: 2266 additions & 662 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Import connections on iPhone. Open a .tablepro file from Files or AirDrop, or use Import Connections in the list menu. Encrypted files prompt for the passphrase, and you choose how to handle duplicates.
13+
- Export connections on iPhone from the list menu and share the .tablepro file. Passwords are left out by default; include them by setting a passphrase that encrypts the file.
14+
1015
### Changed
1116

1217
- Drag-selecting many columns in a wide result set scrolls smoothly instead of lagging; the selection overlay and row highlight now look up column positions from a cache that refreshes when columns are added, removed, or reordered.

Packages/TableProCore/Package.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ let package = Package(
1212
.library(name: "TableProCoreTypes", targets: ["TableProCoreTypes"]),
1313
.library(name: "TableProPluginKit", targets: ["TableProPluginKit"]),
1414
.library(name: "TableProModels", targets: ["TableProModels"]),
15+
.library(name: "TableProImport", targets: ["TableProImport"]),
1516
.library(name: "TableProDatabase", targets: ["TableProDatabase"]),
1617
.library(name: "TableProQuery", targets: ["TableProQuery"]),
1718
.library(name: "TableProSync", targets: ["TableProSync"]),
@@ -35,6 +36,11 @@ let package = Package(
3536
dependencies: ["TableProPluginKit", "TableProCoreTypes"],
3637
path: "Sources/TableProModels"
3738
),
39+
.target(
40+
name: "TableProImport",
41+
dependencies: [],
42+
path: "Sources/TableProImport"
43+
),
3844
.target(
3945
name: "TableProDatabase",
4046
dependencies: ["TableProModels", "TableProCoreTypes"],
@@ -65,6 +71,11 @@ let package = Package(
6571
dependencies: ["TableProModels", "TableProPluginKit"],
6672
path: "Tests/TableProModelsTests"
6773
),
74+
.testTarget(
75+
name: "TableProImportTests",
76+
dependencies: ["TableProImport"],
77+
path: "Tests/TableProImportTests"
78+
),
6879
.testTarget(
6980
name: "TableProDatabaseTests",
7081
dependencies: ["TableProDatabase", "TableProModels"],

TablePro/Core/Services/Export/ConnectionExportCrypto.swift renamed to Packages/TableProCore/Sources/TableProImport/ConnectionExportCrypto.swift

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
1-
//
2-
// ConnectionExportCrypto.swift
3-
// TablePro
4-
//
5-
// AES-256-GCM encryption for connection export files with PBKDF2 key derivation.
6-
//
7-
81
import CommonCrypto
92
import CryptoKit
103
import Foundation
114

12-
enum ConnectionExportCryptoError: LocalizedError {
5+
public enum ConnectionExportCryptoError: LocalizedError {
136
case invalidPassphrase
147
case corruptData
158
case unsupportedVersion(UInt8)
169

17-
var errorDescription: String? {
10+
public var errorDescription: String? {
1811
switch self {
1912
case .invalidPassphrase:
2013
return String(localized: "Incorrect passphrase")
@@ -26,22 +19,21 @@ enum ConnectionExportCryptoError: LocalizedError {
2619
}
2720
}
2821

29-
enum ConnectionExportCrypto {
30-
private static let magic = Data("TPRO".utf8) // 4 bytes
22+
public enum ConnectionExportCrypto {
23+
private static let magic = Data("TPRO".utf8)
3124
private static let currentVersion: UInt8 = 1
3225
private static let saltLength = 32
3326
private static let nonceLength = 12
3427
private static let pbkdf2Iterations: UInt32 = 600_000
35-
private static let keyLength = 32 // AES-256
28+
private static let keyLength = 32
3629

37-
// Header: magic (4) + version (1) + salt (32) + nonce (12) = 49 bytes
3830
private static let headerLength = 4 + 1 + saltLength + nonceLength
3931

40-
static func isEncrypted(_ data: Data) -> Bool {
32+
public static func isEncrypted(_ data: Data) -> Bool {
4133
data.count > headerLength && data.prefix(4) == magic
4234
}
4335

44-
static func encrypt(data: Data, passphrase: String) throws -> Data {
36+
public static func encrypt(data: Data, passphrase: String) throws -> Data {
4537
var salt = Data(count: saltLength)
4638
let saltStatus = salt.withUnsafeMutableBytes { buffer -> OSStatus in
4739
guard let baseAddress = buffer.baseAddress else { return errSecParam }
@@ -65,7 +57,7 @@ enum ConnectionExportCrypto {
6557
return result
6658
}
6759

68-
static func decrypt(data: Data, passphrase: String) throws -> Data {
60+
public static func decrypt(data: Data, passphrase: String) throws -> Data {
6961
guard data.count > headerLength else {
7062
throw ConnectionExportCryptoError.corruptData
7163
}

0 commit comments

Comments
 (0)