Proximity is a social app where you can only connect with people you're physically near. The make-or-break mechanic is the interaction moment: silhouette β wave β mutual reveal β chat.
SimulatedAdapter that stands in for
the hardware, so the app behaves identically.
| Capability | Simulator | Real iPhone |
|---|---|---|
| UI, navigation, chat | β | β |
| Auth (Apple / Google) | β | β |
| Backend + WebSocket live loop | β | β |
| Reveal flow (wave β mutual β chat) | β | β |
| Simulated encounters / crowd | β | β |
| BLE broadcast / discovery | β οΈ limited | β |
| UWB (Nearby Interaction) | β | β |
| NFC tap | β | β |
Install these before starting. On an M-series Mac, everything runs natively on Apple Silicon.
Install from the Mac App Store, or run:
xcode-select --install
# If you need a specific version, use Xcodes:
brew install --cask xcodes
You need Xcode 15 or later for Swift 5.9+ concurrency and SwiftData. Xcode 16 recommended.
Check if you have it:
node --version # want v18+
npm --version
If not, install via Homebrew (native arm64 build):
brew install node
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Here's what you're working with:
Nearfield_Friends/
βββ Proximity/ # iOS app (SwiftUI)
β βββ App/ # entry point, AppState wiring
β βββ Models/ # Encounter, Connection, DistanceTierβ¦
β βββ Services/
β β βββ Proximity/ # PresenceEngine + adapters + SimulatedAdapter
β β βββ Network/ # APIClient, RealtimeClient (WebSocket)
β β βββ Auth/ # Apple/Google sign-in, profile import
β β βββ Security/ # tokens, E2E crypto
β βββ ViewModels/ # Presence, Chat, Reveal
β βββ Views/ # SwiftUI screens + DebugMenu
βββ ProximityUITests/ # automated UI tests
βββ Proximity.xcodeproj/ # Xcode project (generated)
βββ scripts/
β βββ generate_xcodeproj.py # regenerates the .xcodeproj
βββ server/ # reference backend (Node/TS)
β βββ src/index.ts
βββ docs/
βββ backend-api-spec.md # API contract
βββ TESTING.md # testing notes
βββ TESTING_GUIDE.html # this file
Proximity.xcodeproj is now included in the repo (generated by
scripts/generate_xcodeproj.py). It contains the app target, the UI test target, and a shared
scheme β so you can open it directly in Xcode and run xcodebuild test with no manual setup.
If you ever add/remove Swift files, re-run the generator to keep the project in sync.
The Swift app in DEBUG mode points at a local server on localhost:8080. Start it first.
Open Terminal and run:
cd ~/path/to/Nearfield_Friends/server
npm install
npm run dev
You should see:
Proximity reference server on :8080
curl http://localhost:8080/v1/nearby?geohash=abc&tier=1 β you should get [] back.
The Proximity.xcodeproj is already generated. Open it:
open Proximity.xcodeproj
or double-click it in Finder. It includes the app target, the ProximityUITests target, and a
shared scheme, so everything is ready to go.
The entry point is ProximityApp.swift (marked @main).
python3 scripts/generate_xcodeproj.py
Then re-open the project in Xcode.
com.yourname.proximity.For the simulator you only strictly need these for the real-hardware build, but add them now so the project is ready:
The simulator ignores UWB/NFC but will use simulated location.
Add these to Info.plist so permissions work:
NFCReaderUsageDescription "Proximity uses NFC to connect when you tap phones."
NSBluetoothAlwaysUsageDescription "Proximity uses Bluetooth to detect nearby friends."
NSLocationWhenInUseUsageDescription "Proximity uses your location to find people nearby."
APIClient points at http://localhost:8080SimulatedAdapter is injected into the engine (no hardware)localhost works. If you see connection errors, make sure the server from
Step 4 is still running, then use the debug menu's "Reconnect socket".
This is the heart of the product. You'll simulate a full encounter with no hardware.
Tap Continue with Google (or Apple). The reference server accepts any token, so this succeeds immediately. You'll land on the main screen.
Tap the big circular "Tap to be Present" button. It turns green and reads "I'm Here". This opens the live socket.
Tap Wave on the silhouette. It changes to "Waved β" β you're now waiting for the other person.
Option A β two simulators (recommended, see next section): the server relays the mutual wave and the reveal triggers automatically.
Option B β single simulator: the reveal needs the other side to wave. Use the debug menu's "Simulate a crowd" to generate more silhouettes, or drive the mutual wave programmatically via the simulated adapter.
When both sides wave, RevealView presents full-screen: two avatars converge. Tap
"Say hello" to open the E2E-encrypted chat.
Type a message and send. In a two-simulator setup, the other side receives it decrypted in real time over the WebSocket.
This tests the real-time mutual flow end-to-end across two "people".
mutual to both sockets.The ProximityUITests target drives the happy path automatically.
Or from the terminal:
cd ~/path/to/Nearfield_Friends
xcodebuild test \
-project Proximity.xcodeproj \
-scheme Proximity \
-destination 'platform=iOS Simulator,name=iPhone 15' \
-only-testing:ProximityUITests/ProximityFlowUITests
ProximityUITests target and its shared scheme are already part of
Proximity.xcodeproj, so βU and the xcodebuild test command work
out of the box.
When you're ready to test the actual UWB / BLE / NFC radios:
AppState.isSimulated = false.APIClient.baseURL to your Mac's LAN IP or a
deployed server).UWBAdapter, BLEAdapter,
BeaconAdapter take over.β Confirm the server terminal shows :8080 and is still running.
β Use the debug menu's "Reconnect socket".
β Try curl http://localhost:8080/v1/nearby?geohash=abc&tier=1 in Terminal.
β Make sure all folders under Proximity/ are added to the target
(not just referenced).
β Confirm ProximityApp.swift is the @main entry and only one
@main exists.
β For the simulator you can often use "Sign to Run Locally" / no team.
β Use a unique bundle identifier.
β This project is native arm64 β no Rosetta needed.
β If a tool was installed via an Intel package, reinstall via brew
(arm64) or use the Apple Silicon build.
β The simulator shares the Mac's network, so localhost resolves to
your Mac. If you moved the server to another machine, update APIClient.local() to that IP.
β A reveal needs both sides to wave. Use the two-simulator setup (Step 8) for the full end-to-end test, or drive the mutual wave programmatically.