ff731849b0
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Eric Bailey <git@esb.lol>
135 lines
5.5 KiB
Markdown
135 lines
5.5 KiB
Markdown
# BlueskyClip
|
|
|
|
An iOS App Clip implementation for Bluesky starter packs. App Clips are lightweight app experiences that allow users to preview and join Bluesky through starter packs without installing the full app.
|
|
|
|
## What It Does
|
|
|
|
BlueskyClip provides a minimal, on-demand iOS app experience for viewing and joining Bluesky starter packs. When a user encounters a starter pack link (e.g., `bsky.app/start/...` or `go.bsky.app/...`), iOS can present the App Clip instead of requiring a full app install. The App Clip:
|
|
|
|
1. Loads the starter pack web page in a WKWebView
|
|
2. Allows users to browse the starter pack content
|
|
3. Presents the App Store overlay when the user decides to join
|
|
4. Passes the starter pack URI to the main app via shared UserDefaults
|
|
|
|
## Architecture
|
|
|
|
### Native iOS Implementation
|
|
|
|
The App Clip is a standalone iOS target with its own minimal Swift implementation:
|
|
|
|
- **AppDelegate.swift**: Standard app delegate that sets up the view controller and handles URL routing (both direct URL opens and universal links)
|
|
- **ViewController.swift**: Main view controller that manages the WKWebView, detects starter pack URLs, and communicates with the web layer
|
|
|
|
### Communication Flow
|
|
|
|
```
|
|
User taps starter pack link
|
|
↓
|
|
iOS presents BlueskyClip App Clip
|
|
↓
|
|
WKWebView loads bsky.app with ?clip=true parameter
|
|
↓
|
|
Web app detects clip mode and sends actions via postMessage
|
|
↓
|
|
ViewController receives messages and:
|
|
- Presents App Store overlay (action: "present")
|
|
- Stores starter pack URI in shared UserDefaults (action: "store")
|
|
↓
|
|
User downloads main app
|
|
↓
|
|
Main app reads starterPackUri from shared UserDefaults
|
|
↓
|
|
Main app displays starter pack onboarding flow
|
|
```
|
|
|
|
### Key Implementation Details
|
|
|
|
**URL Detection** (`isStarterPackUrl`):
|
|
- Matches `bsky.app/start/*` and `bsky.app/starter-pack/*` paths (4 path components)
|
|
- Matches short links `go.bsky.app/*` (2 path components)
|
|
|
|
**WebView Communication** (`WKScriptMessageHandler`):
|
|
- Listens for messages on the "onMessage" channel
|
|
- Handles two action types:
|
|
- `present`: Shows the App Store overlay using `SKOverlay`
|
|
- `store`: Writes JSON data to shared UserDefaults with the specified key
|
|
|
|
**Data Sharing**:
|
|
- Uses UserDefaults suite `group.app.bsky` (App Group)
|
|
- Primary key: `starterPackUri` - stores the starter pack URL
|
|
- The main app reads this value on launch via `SharedPrefs.getString('starterPackUri')` (see `src/components/hooks/useStarterPackEntry.native.ts`)
|
|
|
|
## Configuration
|
|
|
|
### Build Configuration
|
|
|
|
The App Clip target is automatically configured via Expo config plugins located in `/plugins/starterPackAppClipExtension/`:
|
|
|
|
- **withStarterPackAppClip.js**: Main plugin that orchestrates all configuration
|
|
- **withXcodeTarget.js**: Creates the App Clip target in Xcode with proper build settings
|
|
- **withAppEntitlements.js**: Configures main app entitlements for App Clip association
|
|
- **withClipEntitlements.js**: Sets up App Clip entitlements (App Groups, parent app identifier, associated domains)
|
|
- **withClipInfoPlist.js**: Generates the Info.plist for the App Clip target
|
|
- **withFiles.js**: Copies Swift source files and assets from `modules/BlueskyClip/` to the iOS build directory
|
|
|
|
### Entitlements
|
|
|
|
**Main App** (`app.entitlements`):
|
|
- `com.apple.security.application-groups`: `group.app.bsky`
|
|
- `com.apple.developer.associated-appclip-app-identifiers`: Links to the App Clip bundle ID
|
|
|
|
**App Clip** (`BlueskyClip.entitlements`):
|
|
- `com.apple.security.application-groups`: `group.app.bsky` (for data sharing)
|
|
- `com.apple.developer.parent-application-identifiers`: Links to the main app bundle ID
|
|
- `com.apple.developer.associated-domains`: Inherits from main app config (for universal links)
|
|
|
|
### Build Settings
|
|
|
|
- Deployment target: iOS 15.1+
|
|
- Bundle ID: `[main-app-bundle-id].AppClip`
|
|
- Product type: `com.apple.product-type.application.on-demand-install-capable`
|
|
- Development team: `B3LX46C5HS`
|
|
- Device family: iPhone only (1)
|
|
|
|
## Platform Support
|
|
|
|
- **iOS**: Full support via native App Clip
|
|
- **Android**: Not applicable (no App Clip equivalent)
|
|
- **Web**: Not applicable (web uses standard starter pack landing pages)
|
|
|
|
## Integration with Main App
|
|
|
|
The main app detects App Clip-originated starter packs through `useStarterPackEntry` hook:
|
|
|
|
**Native** (`src/components/hooks/useStarterPackEntry.native.ts`):
|
|
- Reads `starterPackUri` from `SharedPrefs` (App Group)
|
|
- Clears the value after reading to prevent re-use
|
|
- Sets active starter pack in app state
|
|
|
|
**Web** (`src/components/hooks/useStarterPackEntry.ts`):
|
|
- Detects `?clip=true` URL parameter
|
|
- Extracts starter pack URI from URL
|
|
- Sets active starter pack with `isClip: true` flag
|
|
|
|
## Files
|
|
|
|
```
|
|
modules/BlueskyClip/
|
|
├── AppDelegate.swift # App lifecycle and URL handling
|
|
├── ViewController.swift # WebView management and message handling
|
|
└── Images.xcassets/ # App Clip icon assets
|
|
├── AppIcon.appiconset/
|
|
│ ├── App-Icon-1024x1024@1x.png
|
|
│ └── Contents.json
|
|
└── Contents.json
|
|
```
|
|
|
|
## Development Notes
|
|
|
|
- The App Clip is built as part of the main Xcode project when running `pnpm prebuild`
|
|
- Source files are copied during the prebuild process, not directly referenced
|
|
- Changes to Swift files require running `pnpm prebuild` to take effect
|
|
- The App Clip shares the same version number as the main app
|
|
- App Clips have a 15MB size limit (enforced by Apple)
|
|
- Users can convert an App Clip session into a full app install without losing data (via shared App Group)
|