Skip to content

Commit 8d84328

Browse files
Enable launch unlimited access (#31)
1 parent 04ef448 commit 8d84328

17 files changed

Lines changed: 79 additions & 49 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ Every link expires. Every file is deleted. By design.
5151

5252
| Tier | File size | Retention | Uploads | Sync |
5353
| ---- | --------- | --------- | ------- | ---- |
54-
| Free | 100 MB per file | Up to 24 hours | 3 per day | Local history |
54+
| Free launch access | 2 GB per file | Up to 30 days | Unlimited | Local history |
5555
| Pro | 2 GB per file | Up to 30 days | Unlimited | iCloud metadata sync |
5656

57-
The backend is the source of truth for caps and retention. Beta allowances are
58-
controlled by a secure allowlist, never by a global "free equals pro" default.
57+
The backend is the source of truth for caps and retention. For launch, Free
58+
uses Pro upload ceilings while keeping account-level sync features paid.
5959

6060
## How it works
6161

apple/FastSharedApp/Scenes/PaywallView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ struct PaywallView: View {
215215
case .cloudSyncRequested:
216216
return "Pro mirrors your link history to your private iCloud — no extra setup."
217217
case .longRetentionRequested:
218-
return "Pro supports up to 30-day retention. Free tops out at 24 hours."
218+
return "Launch access supports up to 30-day retention. Pro adds sync, support, and Lifetime Family Sharing."
219219
case .largeFileRequested:
220220
return "Pro raises the cap to 2 GB per file and keeps the same one-gesture flow."
221221
case .serverForced:

apple/FastSharedApp/Screenshot/AppStoreScreenshotMode.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ enum AppStoreScreenshotScene: String, CaseIterable {
2828
case .history:
2929
return "Keep control after you send."
3030
case .pro:
31-
return "Go Pro when you need more room."
31+
return "Go Pro when you want sync."
3232
}
3333
}
3434

@@ -37,13 +37,13 @@ enum AppStoreScreenshotScene: String, CaseIterable {
3737
case .shareFlow:
3838
return "FastShared turns a file into a temporary short link and copies it automatically."
3939
case .retention:
40-
return "Default 24-hour links keep cleanup automatic. Pro unlocks up to 30 days."
40+
return "Default 24-hour links keep cleanup automatic. Launch access supports up to 30 days."
4141
case .progress:
4242
return "Large files continue cleanly with visible progress and a ready-to-paste link."
4343
case .history:
4444
return "Find recent links, copy again, share, or revoke before expiration."
4545
case .pro:
46-
return "Unlimited daily uploads, 2 GB files, longer retention, and iCloud sync."
46+
return "Sync link history across devices, get priority support, and keep Lifetime in the family."
4747
}
4848
}
4949
}

apple/Packages/FastSharedCore/Sources/FastSharedCore/Subscription/TierCaps.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ public struct TierCaps: Sendable, Codable, Equatable {
1919
self.allowsCloudSync = allowsCloudSync
2020
}
2121

22+
/// Launch Free defaults — unlimited uploads, 2 GB per file, 30 days retention.
23+
/// iCloud sync stays Pro-only so paid entitlements still map to account-level features.
2224
public static let free = TierCaps(
23-
dailyUploadLimit: 3,
24-
maxFileSizeBytes: 100 * 1024 * 1024,
25-
maxRetentionSeconds: 86_400,
25+
dailyUploadLimit: nil,
26+
maxFileSizeBytes: 2 * 1024 * 1024 * 1024,
27+
maxRetentionSeconds: 2_592_000,
2628
allowsCloudSync: false
2729
)
2830

apple/Packages/FastSharedCore/Tests/FastSharedCoreTests/UploadServiceTests.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,12 @@ final class UploadServiceTests: XCTestCase {
220220
background: background,
221221
orchestrator: orchestrator,
222222
usageTracker: tracker,
223-
subscriptionStore: nil) // nil → Free fallback
223+
subscriptionStore: CustomCapSubscriptionStore(caps: TierCaps(
224+
dailyUploadLimit: 3,
225+
maxFileSizeBytes: 2 * 1024 * 1024 * 1024,
226+
maxRetentionSeconds: 30 * 86_400,
227+
allowsCloudSync: false
228+
)))
224229

225230
let fileURL = try Self.writeTempFile(bytes: 32)
226231
do {
@@ -238,8 +243,8 @@ final class UploadServiceTests: XCTestCase {
238243
}
239244

240245
func test_enqueue_onFileTooLargeForFree_throwsSubscriptionGate() async throws {
241-
// 100 MB cap + 1-byte file > cap (using small file + lowered cap via
242-
// a custom FreeCapsSubscriptionStore).
246+
// Use a tiny custom cap so a 64-byte fixture blows it without creating
247+
// a huge test file.
243248
let tracker = UsageTracker(clock: UsageTrackerTests.FakeClock("2026-04-19"),
244249
defaults: isolatedDefaults())
245250
let store = SwiftDataStore.inMemoryForTests()
@@ -353,8 +358,8 @@ final class UploadServiceTests: XCTestCase {
353358
background: background,
354359
orchestrator: orchestrator,
355360
usageTracker: tracker,
356-
// nil subscriptionStore → falls back to .free caps but uses
357-
// the injected tracker so each test starts with a clean counter.
361+
// Avoid quota gates in the shared happy-path helper; negative
362+
// preflight coverage injects custom caps in focused tests.
358363
subscriptionStore: AlwaysProSubscriptionStore())
359364
return (service, mock, store, clipboard, orchestrator)
360365
}

apple/Packages/FastSharedCore/Tests/FastSharedCoreTests/UsageTrackerTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,12 @@ final class UsageTrackerTests: XCTestCase {
6767
XCTAssertEqual(newDay, 0)
6868
}
6969

70-
func test_remaining_forFree_cappedAt3() async {
70+
func test_remaining_forLaunchFree_returnsUnlimitedSentinel() async {
7171
let tracker = UsageTracker(clock: FakeClock("2026-04-19"), defaults: isolatedDefaults())
7272
_ = await tracker.increment()
7373
_ = await tracker.increment()
7474
let remaining = await tracker.remaining(for: .free)
75-
XCTAssertEqual(remaining, 1)
75+
XCTAssertEqual(remaining, UsageUnlimited)
7676
}
7777

7878
func test_remaining_forPro_returnsUnlimitedSentinel() async {

apple/fastlane/metadata/en-US/description.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ YOUR FILES, YOUR RULES
2626
- Bearer links with high-entropy tokens. The link is the credential. Revoke any time.
2727
- noindex, no-referrer, no-store on every resolve so tokens stay out of search engines, referrer chains, and caches.
2828

29-
FASTSHARED PRO
29+
LAUNCH ACCESS
3030
- Unlimited uploads per day.
3131
- Files up to 2 GB.
3232
- Link retention up to 30 days.
33+
34+
FASTSHARED PRO
3335
- iCloud history sync across iPhone, iPad, and Mac.
3436
- Family Sharing on Lifetime.
3537
- Priority support.

apple/fastlane/metadata/review_information/notes.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ To test Pro, use a sandbox Apple ID and these App Store Connect products:
1212
- red.fastsha.pro.annual
1313
- red.fastsha.pro.lifetime
1414

15-
Pro unlocks unlimited uploads, 2 GB file size, 30-day retention, iCloud history sync, and priority support. Lifetime has Family Sharing enabled.
15+
For launch, the basic upload flow is intentionally unlimited: non-Pro users can upload without a daily cap, use files up to 2 GB, and choose retention up to 30 days. Pro remains available for iCloud history sync, priority support, and Lifetime Family Sharing.
1616

1717
User-generated content is limited to private bearer links created by the reviewer during testing. There is no public feed, user search, follower graph, chat, comments, or permanent hosting. Uploaded files are private, revocable, and automatically deleted after their retention window. The public report endpoint for abuse is /v1/report/:token.

backend/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,10 @@ Response:
254254
}
255255
```
256256

257-
Each file in the batch counts as one event against the daily cap (free tier
258-
= 3/day) — a batch of 4 from a free user fails fast with `429` before any
259-
DB insert. Junction-table idempotency is keyed on `(share_link_id,
257+
Each file in the batch counts as one event when a daily cap is configured.
258+
Launch Free uses the unlimited sentinel, so the KV counter is skipped. If caps
259+
are tightened later, over-cap batches fail fast with `429` before any DB
260+
insert. Junction-table idempotency is keyed on `(share_link_id,
260261
upload_job_id)`: re-completing the same `uploadId` is a no-op. Two slots
261262
that dedup to the same R2 asset still occupy two distinct junction rows
262263
(one per `uploadJobId`).

backend/src/lib/tierCaps.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// Single source of truth for per-tier quota limits. Both the free-tier
22
// enforcement middleware and `GET /v1/me` read these — no drift allowed.
33
//
4-
// Values are aligned with the public beta hotfix plan:
5-
// Free: 3 uploads/day, max 100 MB, TTL fixed at 24h, no Cloud Sync
6-
// Pro: unlimited (-1 sentinel) uploads, max 2 GB, TTL up to 30 days
4+
// Values are aligned with the launch plan:
5+
// Free: launch-unlimited uploads (-1 sentinel), max 2 GB, TTL up to 30 days,
6+
// no Cloud Sync
7+
// Pro: same upload caps, plus Cloud Sync
78

89
export interface TierCaps {
910
uploadsPerDay: number; // -1 means unlimited
@@ -32,9 +33,9 @@ export function toWireCaps(caps: TierCaps): TierCapsWire {
3233
}
3334

3435
export const FREE_CAPS: TierCaps = {
35-
uploadsPerDay: 3,
36-
maxFileSizeMB: 100,
37-
maxRetentionHours: 24,
36+
uploadsPerDay: -1,
37+
maxFileSizeMB: 2048,
38+
maxRetentionHours: 720,
3839
allowsCloudSync: false,
3940
};
4041

0 commit comments

Comments
 (0)