+157
docs/research/2025-12-19-ios-share-extension-minimal-app.md
+157
docs/research/2025-12-19-ios-share-extension-minimal-app.md
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
# research: minimal iOS app with share extension
2
+
3
+
**date**: 2025-12-19
4
+
**question**: how to build a minimal iOS app with share extension for uploading audio to plyr.fm API, for someone with no iOS experience
5
+
6
+
## summary
7
+
8
+
**yes, it's doable.** native Swift is the best approach - simpler than cross-platform for this use case. you need a minimal "host app" (Apple requires it) but it can just be login + settings. the share extension does the real work. estimated 4-8 weeks for someone learning Swift as they go.
9
+
10
+
## the reality check
11
+
12
+
| requirement | detail |
13
+
|-------------|--------|
14
+
| cost | $99/year Apple Developer account + Mac |
15
+
| timeline | 4-8 weeks (learning + building) |
16
+
| complexity | medium - Swift is approachable |
17
+
| app store approval | achievable if main app has real UI |
18
+
19
+
## architecture
20
+
21
+
```
22
+
plyr-ios/
23
+
├── PlyrApp/ # main app target (~200 lines)
24
+
│ ├── AuthView.swift # OAuth login
25
+
│ ├── SettingsView.swift # preferences
26
+
│ └── ProfileView.swift # account info
27
+
│
28
+
├── PlyrShareExtension/ # share extension (~300 lines)
29
+
│ ├── ShareViewController.swift
30
+
│ └── AudioUploadManager.swift
31
+
│
32
+
└── PlyrShared/ # shared code
33
+
├── APIClient.swift # HTTP requests
34
+
├── KeychainManager.swift # token storage
35
+
└── Constants.swift
36
+
```
37
+
38
+
## how it works
39
+
40
+
1. **user installs app** → opens once → logs in via OAuth
41
+
2. **token stored** in iOS Keychain (shared between app + extension)
42
+
3. **user records voice memo** → taps share → sees "plyr.fm"
43
+
4. **share extension** reads token from Keychain, uploads audio to API
44
+
5. **done** - no need to open main app again
45
+
46
+
## key constraints
47
+
48
+
| constraint | value | implication |
49
+
|------------|-------|-------------|
50
+
| memory limit | ~120 MB | stream file, don't load into memory |
51
+
| time limit | ~30 seconds | fine for most audio, use background upload for large files |
52
+
| extension can't do OAuth | - | main app handles login, extension reads stored token |
53
+
54
+
## authentication flow
55
+
56
+
```
57
+
Main App:
58
+
1. User taps "Log in with Bluesky"
59
+
2. OAuth flow via browser/webview
60
+
3. Receive token, store in shared Keychain
61
+
62
+
Share Extension:
63
+
1. Check Keychain for token
64
+
2. If missing → "Open app to log in" button
65
+
3. If present → upload directly to plyr.fm API
66
+
```
67
+
68
+
## two implementation paths
69
+
70
+
### path A: direct upload (simpler, 4-6 weeks)
71
+
72
+
- extension uploads file directly via URLSession
73
+
- shows progress UI in extension
74
+
- good for files under ~20MB
75
+
- risk: 30-second timeout on slow connections
76
+
77
+
### path B: background upload (robust, 6-8 weeks)
78
+
79
+
- extension saves file to shared container
80
+
- hands off to background URLSession
81
+
- main app can show upload queue
82
+
- handles large files, retries on failure
83
+
- professional quality
84
+
85
+
**recommendation**: start with path A, upgrade to B if needed.
86
+
87
+
## app store approval
88
+
89
+
Apple requires the main app to have "independent value" - can't be empty shell. minimum viable:
90
+
- login/logout UI
91
+
- settings screen
92
+
- profile/account view
93
+
- maybe upload history
94
+
95
+
this is enough to pass review. many apps do exactly this pattern.
96
+
97
+
## what you need to learn
98
+
99
+
1. **Swift basics** - 1-2 weeks of tutorials
100
+
2. **SwiftUI** - for building UI (modern, easier than UIKit)
101
+
3. **URLSession** - for HTTP requests
102
+
4. **Keychain** - for secure token storage
103
+
5. **App Groups** - for sharing data between app + extension
104
+
105
+
## example code: share extension upload
106
+
107
+
```swift
108
+
class ShareViewController: SLComposeServiceViewController {
109
+
override func didSelectPost() {
110
+
guard let item = extensionContext?.inputItems.first as? NSExtensionItem,
111
+
let attachment = item.attachments?.first else { return }
112
+
113
+
attachment.loadFileRepresentation(forTypeIdentifier: "public.audio") { url, error in
114
+
guard let url = url else { return }
115
+
116
+
// Get auth token from shared Keychain
117
+
let token = KeychainManager.shared.getToken()
118
+
119
+
// Upload to plyr.fm API
120
+
APIClient.shared.uploadTrack(fileURL: url, token: token) { result in
121
+
self.extensionContext?.completeRequest(returningItems: nil)
122
+
}
123
+
}
124
+
}
125
+
}
126
+
```
127
+
128
+
## plyr.fm specific considerations
129
+
130
+
- **OAuth**: plyr.fm uses ATProto OAuth - need to handle in main app
131
+
- **API endpoint**: `POST /api/tracks/upload` (or similar)
132
+
- **token refresh**: share extension should handle expired tokens gracefully
133
+
- **metadata**: could add title/artist fields in share extension UI
134
+
135
+
## alternative: iOS Shortcuts
136
+
137
+
if native app feels too heavy, you could create a Shortcut that:
138
+
1. user selects audio file
139
+
2. shortcut calls plyr.fm upload API
140
+
3. user shares shortcut via iCloud link
141
+
142
+
**pros**: no app store, no $99/year
143
+
**cons**: users must manually install shortcut, less discoverable, clunkier UX
144
+
145
+
## open questions
146
+
147
+
- does plyr.fm API support multipart file upload from iOS?
148
+
- what metadata should share extension collect? (title, tags, etc.)
149
+
- should extension show upload progress or dismiss immediately?
150
+
- worth building Android version too? (share target works there)
151
+
152
+
## learning resources
153
+
154
+
- [Apple: App Extension Programming Guide](https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Share.html)
155
+
- [AppCoda: Building Share Extension](https://www.appcoda.com/ios8-share-extension-swift/)
156
+
- [Hacking with Swift: 100 Days of SwiftUI](https://www.hackingwithswift.com/100/swiftui) (free)
157
+
- [Stanford CS193p: iOS Development](https://cs193p.sites.stanford.edu/) (free)
+63
docs/research/2025-12-19-web-share-target-ios-pwa.md
+63
docs/research/2025-12-19-web-share-target-ios-pwa.md
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
# research: Web Share Target API for iOS PWAs
2
+
3
+
**date**: 2025-12-19
4
+
**question**: can a PWA receive shared audio files from the iOS share sheet (like SoundCloud does)?
5
+
6
+
## summary
7
+
8
+
**No, iOS Safari does not support Web Share Target API.** This is a known limitation with an open WebKit bug (#194593) since February 2019 - no timeline for implementation. Android Chrome fully supports it. The SoundCloud iOS share integration you saw works because SoundCloud has a native iOS app, not a PWA.
9
+
10
+
## findings
11
+
12
+
### iOS Safari limitations
13
+
14
+
- Web Share Target API (receiving files) - **NOT supported**
15
+
- Web Share API (sending/sharing out) - supported
16
+
- WebKit bug #194593 tracks this, last activity May 2025, no assignee
17
+
- Apple mandates WebKit for all iOS browsers, so no workaround via Chrome/Firefox
18
+
- This is why plyr.fm can't appear in the iOS share sheet as a destination
19
+
20
+
### Android support
21
+
22
+
- Chrome fully supports Web Share Target since ~2019
23
+
- manifest.json `share_target` field enables receiving shared files
24
+
- Would work for Android users installing plyr.fm PWA
25
+
26
+
### current plyr.fm state
27
+
28
+
- `frontend/static/manifest.webmanifest` - basic PWA config, no share_target
29
+
- `frontend/src/lib/components/ShareButton.svelte` - clipboard copy only
30
+
- no `navigator.share()` usage (even though iOS supports sharing OUT)
31
+
32
+
### workaround options
33
+
34
+
1. **native iOS app** - only real solution for iOS share sheet integration
35
+
2. **Web Share API for outbound** - can add "share to..." button that opens iOS share sheet
36
+
3. **improved in-app UX** - better upload flow, drag-drop on desktop
37
+
4. **PWABuilder wrapper** - publish to App Store, gains URL scheme support
38
+
39
+
## code references
40
+
41
+
- `frontend/static/manifest.webmanifest` - would add `share_target` here for Android
42
+
- `frontend/src/lib/components/ShareButton.svelte:8-15` - current clipboard-only implementation
43
+
44
+
## potential quick wins
45
+
46
+
even without iOS share target support:
47
+
48
+
1. **add navigator.share() for outbound sharing** - let users share tracks TO other apps via native share sheet
49
+
2. **add share_target for Android users** - Android PWA installs would get share sheet integration
50
+
3. **improve upload UX** - streamline the in-app upload flow for mobile
51
+
52
+
## open questions
53
+
54
+
- is Android share target support worth implementing given iOS is primary user base?
55
+
- would a lightweight native iOS app (just for share extension) be worth maintaining?
56
+
- any appetite for PWABuilder/App Store distribution?
57
+
58
+
## sources
59
+
60
+
- [WebKit Bug #194593](https://bugs.webkit.org/show_bug.cgi?id=194593)
61
+
- [MDN: share_target](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/share_target)
62
+
- [web.dev: OS Integration](https://web.dev/learn/pwa/os-integration)
63
+
- [firt.dev: iOS PWA Compatibility](https://firt.dev/notes/pwa-ios/)