Bluesky app fork with some witchin' additions 💫
1# BlueskyClip
2
3An 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.
4
5## What It Does
6
7BlueskyClip 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:
8
91. Loads the starter pack web page in a WKWebView
102. Allows users to browse the starter pack content
113. Presents the App Store overlay when the user decides to join
124. Passes the starter pack URI to the main app via shared UserDefaults
13
14## Architecture
15
16### Native iOS Implementation
17
18The App Clip is a standalone iOS target with its own minimal Swift implementation:
19
20- **AppDelegate.swift**: Standard app delegate that sets up the view controller and handles URL routing (both direct URL opens and universal links)
21- **ViewController.swift**: Main view controller that manages the WKWebView, detects starter pack URLs, and communicates with the web layer
22
23### Communication Flow
24
25```
26User taps starter pack link
27 ↓
28iOS presents BlueskyClip App Clip
29 ↓
30WKWebView loads bsky.app with ?clip=true parameter
31 ↓
32Web app detects clip mode and sends actions via postMessage
33 ↓
34ViewController receives messages and:
35 - Presents App Store overlay (action: "present")
36 - Stores starter pack URI in shared UserDefaults (action: "store")
37 ↓
38User downloads main app
39 ↓
40Main app reads starterPackUri from shared UserDefaults
41 ↓
42Main app displays starter pack onboarding flow
43```
44
45### Key Implementation Details
46
47**URL Detection** (`isStarterPackUrl`):
48- Matches `bsky.app/start/*` and `bsky.app/starter-pack/*` paths (4 path components)
49- Matches short links `go.bsky.app/*` (2 path components)
50
51**WebView Communication** (`WKScriptMessageHandler`):
52- Listens for messages on the "onMessage" channel
53- Handles two action types:
54 - `present`: Shows the App Store overlay using `SKOverlay`
55 - `store`: Writes JSON data to shared UserDefaults with the specified key
56
57**Data Sharing**:
58- Uses UserDefaults suite `group.app.bsky` (App Group)
59- Primary key: `starterPackUri` - stores the starter pack URL
60- The main app reads this value on launch via `SharedPrefs.getString('starterPackUri')` (see `src/components/hooks/useStarterPackEntry.native.ts`)
61
62## Configuration
63
64### Build Configuration
65
66The App Clip target is automatically configured via Expo config plugins located in `/plugins/starterPackAppClipExtension/`:
67
68- **withStarterPackAppClip.js**: Main plugin that orchestrates all configuration
69- **withXcodeTarget.js**: Creates the App Clip target in Xcode with proper build settings
70- **withAppEntitlements.js**: Configures main app entitlements for App Clip association
71- **withClipEntitlements.js**: Sets up App Clip entitlements (App Groups, parent app identifier, associated domains)
72- **withClipInfoPlist.js**: Generates the Info.plist for the App Clip target
73- **withFiles.js**: Copies Swift source files and assets from `modules/BlueskyClip/` to the iOS build directory
74
75### Entitlements
76
77**Main App** (`app.entitlements`):
78- `com.apple.security.application-groups`: `group.app.bsky`
79- `com.apple.developer.associated-appclip-app-identifiers`: Links to the App Clip bundle ID
80
81**App Clip** (`BlueskyClip.entitlements`):
82- `com.apple.security.application-groups`: `group.app.bsky` (for data sharing)
83- `com.apple.developer.parent-application-identifiers`: Links to the main app bundle ID
84- `com.apple.developer.associated-domains`: Inherits from main app config (for universal links)
85
86### Build Settings
87
88- Deployment target: iOS 15.1+
89- Bundle ID: `[main-app-bundle-id].AppClip`
90- Product type: `com.apple.product-type.application.on-demand-install-capable`
91- Development team: `B3LX46C5HS`
92- Device family: iPhone only (1)
93
94## Platform Support
95
96- **iOS**: Full support via native App Clip
97- **Android**: Not applicable (no App Clip equivalent)
98- **Web**: Not applicable (web uses standard starter pack landing pages)
99
100## Integration with Main App
101
102The main app detects App Clip-originated starter packs through `useStarterPackEntry` hook:
103
104**Native** (`src/components/hooks/useStarterPackEntry.native.ts`):
105- Reads `starterPackUri` from `SharedPrefs` (App Group)
106- Clears the value after reading to prevent re-use
107- Sets active starter pack in app state
108
109**Web** (`src/components/hooks/useStarterPackEntry.ts`):
110- Detects `?clip=true` URL parameter
111- Extracts starter pack URI from URL
112- Sets active starter pack with `isClip: true` flag
113
114## Files
115
116```
117modules/BlueskyClip/
118├── AppDelegate.swift # App lifecycle and URL handling
119├── ViewController.swift # WebView management and message handling
120└── Images.xcassets/ # App Clip icon assets
121 ├── AppIcon.appiconset/
122 │ ├── App-Icon-1024x1024@1x.png
123 │ └── Contents.json
124 └── Contents.json
125```
126
127## Development Notes
128
129- The App Clip is built as part of the main Xcode project when running `yarn prebuild`
130- Source files are copied during the prebuild process, not directly referenced
131- Changes to Swift files require running `yarn prebuild` to take effect
132- The App Clip shares the same version number as the main app
133- App Clips have a 15MB size limit (enforced by Apple)
134- Users can convert an App Clip session into a full app install without losing data (via shared App Group)