fix: remove CocoaPods references breaking build #26

Merged
koda merged 2 commits from koda/remove-cocoapods-refs into main 2026-03-22 18:37:44 +00:00
2 changed files with 101 additions and 75 deletions

101
CLAUDE.md Normal file
View file

@ -0,0 +1,101 @@
# Payfrit Beacon iOS
Native Swift iOS app for provisioning iBeacon hardware at restaurant tables. Repo: `payfrit-beacon-ios` on Forgejo.
## Purpose
Utility app for restaurant setup staff to:
1. Scan for nearby BLE iBeacons (iBeacon + BLE + QR code)
2. Check UUID against known bulk manufacturer defaults (ban list)
3. Assign table names to beacons (smart-incremented)
4. Provision beacon hardware via BLE GATT (DX-Smart, BlueCharm, KBeacon)
5. Save beacon + auto-create service point records via API
## Environment
- **Server**: dev.payfrit.com (DEBUG) / biz.payfrit.com (RELEASE)
- **API Base**: `https://dev.payfrit.com/api` (dev) / `https://biz.payfrit.com/api` (prod)
- **Config**: `APIConfig.swift` — uses `#if DEBUG` compiler flag
- **OTP**: Dev server uses magic OTP `123456` (no Twilio)
## Database (via API — no direct SQL)
| Table | PK | Key Columns |
|-------|----|-------------|
| Beacons | `ID` | `UUID`, `Name`, `BusinessID`, `IsActive` |
| ServicePoints | `ID` | `BusinessID`, `Name`, `TypeID`, `Code`, `SortOrder`, `IsActive`, `BeaconID` |
| Businesses | `ID` | `Name`, `ParentBusinessID` |
**Note**: `POST /api/beacons/save.php` auto-creates a ServicePoint when saving a beacon.
## API Endpoints Used
All endpoints are `.php`. Auth = `X-User-Token` header.
| Method | Endpoint | Auth | Purpose |
|--------|----------|------|---------|
| POST | /auth/loginOTP.php | No | Send OTP to phone |
| POST | /auth/verifyLoginOTP.php | No | Verify OTP, get token |
| POST | /businesses/list.php | Yes | List user's businesses |
| POST | /servicepoints/list.php | Yes | List service points for business |
| POST | /servicepoints/save.php | Yes | Create/update service point |
| POST | /servicepoints/delete.php | Yes | Delete service point |
| POST | /beacon-sharding/allocate_business_namespace.php | Yes | Allocate UUID+Major shard |
| POST | /beacon-sharding/get_beacon_config.php | Yes | Get complete beacon config |
| POST | /beacon-sharding/allocate_servicepoint_minor.php | Yes | Auto-assign minor value |
| POST | /beacon-sharding/resolve_business.php | Yes | Resolve business by UUID+Major |
| POST | /beacons/list.php | Yes | List beacons for a business |
| POST | /beacons/lookupByMac.php | Yes | Lookup beacon by MAC address |
| POST | /beacons/wipe.php | Yes | Decommission a beacon |
| GET | /users/profile.php | Yes | Get user profile |
## Build & Deploy
Build in Xcode targeting iOS 17+. No local testing — deploy to device for BLE testing.
## Project Structure
```
PayfritBeacon/
├── App/
│ ├── AppPrefs.swift UserDefaults keys
│ ├── AppState.swift ObservableObject — auth state, nav, business selection
│ └── PayfritBeaconApp.swift App entry point
├── Models/
│ ├── BeaconConfig.swift Provisioning config (UUID, major, minor, txPower, etc.)
│ ├── BeaconType.swift Enum: DXSmart, BlueCharm, KBeacon, Unknown
│ ├── Business.swift Business model
│ └── ServicePoint.swift Service point model
├── Provisioners/
│ ├── ProvisionerProtocol.swift Protocol — all provisioners implement this
│ ├── DXSmartProvisioner.swift DX-Smart CP28 GATT provisioner (24-step write sequence)
│ ├── BlueCharmProvisioner.swift BlueCharm BC037 provisioner
│ ├── KBeaconProvisioner.swift KBeacon provisioner
│ ├── FallbackProvisioner.swift Unknown device fallback
│ └── ProvisionError.swift Shared error types
├── Services/
│ ├── APIClient.swift Actor-based REST client, all API calls
│ ├── APIConfig.swift Base URLs, timeouts, dev/prod toggle
│ ├── BLEManager.swift CoreBluetooth scanning + beacon type detection
│ └── SecureStorage.swift Keychain-based token storage
├── Utils/
│ ├── BeaconBanList.swift Known bad UUID prefixes (factory defaults)
│ ├── BeaconShardPool.swift 64 Payfrit shard UUIDs
│ └── UUIDFormatting.swift UUID string formatting extensions
└── Views/
├── BusinessListView.swift Business selection screen
├── DevBanner.swift Orange "DEV" banner overlay
├── LoginView.swift OTP login screen
├── QRScannerView.swift Camera-based QR code scanner for beacon pairing
├── RootView.swift Root navigation
└── ScanView.swift Main scan + provision UI
```
## Key Architecture Notes
- **Modular provisioners**: Each beacon manufacturer has its own provisioner conforming to `ProvisionerProtocol`. No more monolithic `BeaconProvisioner.swift`.
- **Actor-based API**: `APIClient` is a Swift actor (thread-safe by design).
- **Secure storage**: Auth tokens stored in Keychain via `SecureStorage`, not UserDefaults.
- **BLE scanning**: `BLEManager` handles CoreBluetooth device discovery and beacon type identification.
- **DX-Smart protocol**: 24-step write sequence including frames 3-6 disable (full Android parity).
- **QR scanner**: Camera-based QR code scanning for beacon pairing workflows.

View file

@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
103E16DE41E63F1AD9A7BAEC /* UUIDFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B4971261F86B2A4D7579277 /* UUIDFormatting.swift */; }; 103E16DE41E63F1AD9A7BAEC /* UUIDFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B4971261F86B2A4D7579277 /* UUIDFormatting.swift */; };
281CC856DD918C4CFA00EB67 /* ServicePointListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1775119CBC98A753AE26D84 /* ServicePointListView.swift */; }; 281CC856DD918C4CFA00EB67 /* ServicePointListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1775119CBC98A753AE26D84 /* ServicePointListView.swift */; };
7D757E1341A0143A2E9EBDF4 /* Pods_PayfritBeacon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F6819A0F2BD2E9D84E7EDB4 /* Pods_PayfritBeacon.framework */; };
D01000000001 /* PayfritBeaconApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02000000001 /* PayfritBeaconApp.swift */; }; D01000000001 /* PayfritBeaconApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02000000001 /* PayfritBeaconApp.swift */; };
D01000000002 /* Api.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02000000002 /* Api.swift */; }; D01000000002 /* Api.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02000000002 /* Api.swift */; };
D01000000003 /* BeaconBanList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02000000003 /* BeaconBanList.swift */; }; D01000000003 /* BeaconBanList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02000000003 /* BeaconBanList.swift */; };
@ -30,13 +29,9 @@
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1AD023D1003AAD57ED3DBEAA /* Pods-PayfritBeacon.debug-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PayfritBeacon.debug-dev.xcconfig"; path = "Target Support Files/Pods-PayfritBeacon/Pods-PayfritBeacon.debug-dev.xcconfig"; sourceTree = "<group>"; };
2B4971261F86B2A4D7579277 /* UUIDFormatting.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UUIDFormatting.swift; sourceTree = "<group>"; }; 2B4971261F86B2A4D7579277 /* UUIDFormatting.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = UUIDFormatting.swift; sourceTree = "<group>"; };
7F6819A0F2BD2E9D84E7EDB4 /* Pods_PayfritBeacon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PayfritBeacon.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8710F334FDFE10F0625EB86D /* BLEBeaconScanner.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BLEBeaconScanner.swift; sourceTree = "<group>"; }; 8710F334FDFE10F0625EB86D /* BLEBeaconScanner.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BLEBeaconScanner.swift; sourceTree = "<group>"; };
964B63D1857877BBEE73F1D1 /* BeaconShardPool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BeaconShardPool.swift; sourceTree = "<group>"; }; 964B63D1857877BBEE73F1D1 /* BeaconShardPool.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BeaconShardPool.swift; sourceTree = "<group>"; };
AFF542870BA2862FAB429A86 /* Pods-PayfritBeacon.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PayfritBeacon.debug.xcconfig"; path = "Target Support Files/Pods-PayfritBeacon/Pods-PayfritBeacon.debug.xcconfig"; sourceTree = "<group>"; };
B22C79E28AA521E6347E7F93 /* Pods-PayfritBeacon.release-dev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PayfritBeacon.release-dev.xcconfig"; path = "Target Support Files/Pods-PayfritBeacon/Pods-PayfritBeacon.release-dev.xcconfig"; sourceTree = "<group>"; };
C03000000001 /* PayfritBeacon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PayfritBeacon.app; sourceTree = BUILT_PRODUCTS_DIR; }; C03000000001 /* PayfritBeacon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PayfritBeacon.app; sourceTree = BUILT_PRODUCTS_DIR; };
C7C275738594E225BE7D5740 /* BeaconProvisioner.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BeaconProvisioner.swift; sourceTree = "<group>"; }; C7C275738594E225BE7D5740 /* BeaconProvisioner.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BeaconProvisioner.swift; sourceTree = "<group>"; };
D02000000001 /* PayfritBeaconApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayfritBeaconApp.swift; sourceTree = "<group>"; }; D02000000001 /* PayfritBeaconApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayfritBeaconApp.swift; sourceTree = "<group>"; };
@ -55,7 +50,6 @@
D02000000081 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; D02000000081 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E1775119CBC98A753AE26D84 /* ServicePointListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ServicePointListView.swift; sourceTree = "<group>"; }; E1775119CBC98A753AE26D84 /* ServicePointListView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ServicePointListView.swift; sourceTree = "<group>"; };
F100AA6D1E41596FCB1C1A39 /* DebugLog.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DebugLog.swift; path = PayfritBeacon/DebugLog.swift; sourceTree = "<group>"; }; F100AA6D1E41596FCB1C1A39 /* DebugLog.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = DebugLog.swift; path = PayfritBeacon/DebugLog.swift; sourceTree = "<group>"; };
F1B84CA677516C50F6BE2294 /* Pods-PayfritBeacon.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PayfritBeacon.release.xcconfig"; path = "Target Support Files/Pods-PayfritBeacon/Pods-PayfritBeacon.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -63,31 +57,17 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
7D757E1341A0143A2E9EBDF4 /* Pods_PayfritBeacon.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
04996117E2F5D5BB2D86CD46 /* Pods */ = {
isa = PBXGroup;
children = (
AFF542870BA2862FAB429A86 /* Pods-PayfritBeacon.debug.xcconfig */,
F1B84CA677516C50F6BE2294 /* Pods-PayfritBeacon.release.xcconfig */,
1AD023D1003AAD57ED3DBEAA /* Pods-PayfritBeacon.debug-dev.xcconfig */,
B22C79E28AA521E6347E7F93 /* Pods-PayfritBeacon.release-dev.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
C05000000001 = { C05000000001 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D05000000002 /* PayfritBeacon */, D05000000002 /* PayfritBeacon */,
C05000000009 /* Products */, C05000000009 /* Products */,
04996117E2F5D5BB2D86CD46 /* Pods */,
EEC06FED6BE78CF9357F3158 /* Frameworks */,
F100AA6D1E41596FCB1C1A39 /* DebugLog.swift */, F100AA6D1E41596FCB1C1A39 /* DebugLog.swift */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
@ -126,14 +106,6 @@
path = PayfritBeacon; path = PayfritBeacon;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EEC06FED6BE78CF9357F3158 /* Frameworks */ = {
isa = PBXGroup;
children = (
7F6819A0F2BD2E9D84E7EDB4 /* Pods_PayfritBeacon.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -141,11 +113,9 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = C08000000003 /* Build configuration list for PBXNativeTarget "PayfritBeacon" */; buildConfigurationList = C08000000003 /* Build configuration list for PBXNativeTarget "PayfritBeacon" */;
buildPhases = ( buildPhases = (
744B96DEA84C89E13D29B8B7 /* [CP] Check Pods Manifest.lock */,
C07000000001 /* Sources */, C07000000001 /* Sources */,
C04000000001 /* Frameworks */, C04000000001 /* Frameworks */,
C09000000001 /* Resources */, C09000000001 /* Resources */,
66702B40BAEAF5430876D7CE /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -202,47 +172,6 @@
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
66702B40BAEAF5430876D7CE /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-PayfritBeacon/Pods-PayfritBeacon-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-PayfritBeacon/Pods-PayfritBeacon-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PayfritBeacon/Pods-PayfritBeacon-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
744B96DEA84C89E13D29B8B7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-PayfritBeacon-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
C07000000001 /* Sources */ = { C07000000001 /* Sources */ = {
@ -284,7 +213,6 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
064DDD2A9238EC6900250593 /* Release-Dev */ = { 064DDD2A9238EC6900250593 /* Release-Dev */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = B22C79E28AA521E6347E7F93 /* Pods-PayfritBeacon.release-dev.xcconfig */;
buildSettings = { buildSettings = {
APP_DISPLAY_NAME = "Payfrit Beacon BETA"; APP_DISPLAY_NAME = "Payfrit Beacon BETA";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@ -436,7 +364,6 @@
}; };
B0D496FEA252D8DDA33F57A0 /* Debug-Dev */ = { B0D496FEA252D8DDA33F57A0 /* Debug-Dev */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 1AD023D1003AAD57ED3DBEAA /* Pods-PayfritBeacon.debug-dev.xcconfig */;
buildSettings = { buildSettings = {
APP_DISPLAY_NAME = "Payfrit Beacon BETA"; APP_DISPLAY_NAME = "Payfrit Beacon BETA";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@ -588,7 +515,6 @@
}; };
C0B000000003 /* Debug */ = { C0B000000003 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = AFF542870BA2862FAB429A86 /* Pods-PayfritBeacon.debug.xcconfig */;
buildSettings = { buildSettings = {
APP_DISPLAY_NAME = "Payfrit Beacon"; APP_DISPLAY_NAME = "Payfrit Beacon";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
@ -623,7 +549,6 @@
}; };
C0B000000004 /* Release */ = { C0B000000004 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = F1B84CA677516C50F6BE2294 /* Pods-PayfritBeacon.release.xcconfig */;
buildSettings = { buildSettings = {
APP_DISPLAY_NAME = "Payfrit Beacon"; APP_DISPLAY_NAME = "Payfrit Beacon";
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;