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