Line data Source code
1 : import 'package:flutter/foundation.dart';
2 : import '../worker.dart';
3 :
4 : export 'request_signing.dart';
5 :
6 : /// HTTP upload worker configuration.
7 : ///
8 : /// Supports multipart/form-data file uploads with optional background
9 : /// URLSession on iOS for uploads that survive app termination.
10 : @immutable
11 : final class HttpUploadWorker extends Worker {
12 11 : const HttpUploadWorker({
13 : required this.url,
14 : required this.filePath,
15 : this.fileFieldName = 'file',
16 : this.fileName,
17 : this.mimeType,
18 : this.headers = const {},
19 : this.additionalFields = const {},
20 : this.timeout = const Duration(minutes: 5),
21 : this.useBackgroundSession = false,
22 : this.requestSigning,
23 : });
24 :
25 : /// The URL to upload to.
26 : final String url;
27 :
28 : /// Path to the file to upload (absolute path).
29 : final String filePath;
30 :
31 : /// Form field name for the file (default: "file").
32 : final String fileFieldName;
33 :
34 : /// Optional custom file name (defaults to actual file name).
35 : final String? fileName;
36 :
37 : /// Optional MIME type (auto-detected if not provided).
38 : final String? mimeType;
39 :
40 : /// Optional HTTP headers to include in the request.
41 : final Map<String, String> headers;
42 :
43 : /// Optional additional form fields to include in the multipart request.
44 : final Map<String, String> additionalFields;
45 :
46 : /// Request timeout (default: 5 minutes).
47 : final Duration timeout;
48 :
49 : /// Use background URLSession for uploads (iOS only).
50 : ///
51 : /// **v2.3.0+ iOS Feature:**
52 : /// When enabled, uploads use `URLSessionConfiguration.background` which:
53 : /// - **Survives app termination** - Uploads continue even if app is killed
54 : /// - **No time limits** - Can upload for hours (vs 30s foreground limit)
55 : /// - **System-managed** - OS handles network changes and retries
56 : /// - **Battery efficient** - OS schedules transfers optimally
57 : ///
58 : /// **Android:**
59 : /// This parameter has no effect on Android. WorkManager already handles
60 : /// background uploads robustly without special configuration.
61 : ///
62 : /// **When to use:**
63 : /// - ✅ Large files (>10MB) that may take minutes to upload
64 : /// - ✅ Uploads that must complete even if user force-quits app
65 : /// - ✅ Uploads on unreliable networks (automatic retry)
66 : /// - ❌ Small files (<1MB) - foreground session is faster
67 : /// - ❌ Immediate uploads that finish in seconds
68 : ///
69 : /// Example:
70 : /// ```dart
71 : /// // Large video upload (survives app termination)
72 : /// worker: NativeWorker.httpUpload(
73 : /// url: 'https://cdn.example.com/videos',
74 : /// filePath: '/videos/large-video.mp4',
75 : /// useBackgroundSession: true, // 🚀 Survives termination
76 : /// ),
77 : /// ```
78 : ///
79 : /// Default: `false` (backward compatible with existing code)
80 : final bool useBackgroundSession;
81 :
82 : /// HMAC-SHA256 request signing configuration.
83 : ///
84 : /// When set, each upload request is signed with the specified secret key
85 : /// and the signature is injected as a request header (default: `X-Signature`).
86 : final RequestSigning? requestSigning;
87 :
88 : // ═══════════════════════════════════════════════════════════════════════════
89 : // BUILDER-STYLE copyWith + convenience methods
90 : // ═══════════════════════════════════════════════════════════════════════════
91 :
92 : /// Returns a copy with the given fields replaced.
93 1 : HttpUploadWorker copyWith({
94 : String? url,
95 : String? filePath,
96 : String? fileFieldName,
97 : String? fileName,
98 : String? mimeType,
99 : Map<String, String>? headers,
100 : Map<String, String>? additionalFields,
101 : Duration? timeout,
102 : bool? useBackgroundSession,
103 : RequestSigning? requestSigning,
104 : }) =>
105 1 : HttpUploadWorker(
106 1 : url: url ?? this.url,
107 1 : filePath: filePath ?? this.filePath,
108 1 : fileFieldName: fileFieldName ?? this.fileFieldName,
109 1 : fileName: fileName ?? this.fileName,
110 1 : mimeType: mimeType ?? this.mimeType,
111 1 : headers: headers ?? this.headers,
112 1 : additionalFields: additionalFields ?? this.additionalFields,
113 1 : timeout: timeout ?? this.timeout,
114 1 : useBackgroundSession: useBackgroundSession ?? this.useBackgroundSession,
115 1 : requestSigning: requestSigning ?? this.requestSigning,
116 : );
117 :
118 : /// Convenience: add or merge HTTP headers.
119 : ///
120 : /// ```dart
121 : /// worker.withHeaders({'Authorization': 'Bearer $token', 'X-App': '1'})
122 : /// ```
123 2 : HttpUploadWorker withHeaders(Map<String, String> extra) => copyWith(
124 3 : headers: {...headers, ...extra},
125 : );
126 :
127 : /// Convenience: add `Authorization` header.
128 : ///
129 : /// ```dart
130 : /// worker.withAuth(token: myToken)
131 : /// worker.withAuth(token: myApiKey, template: 'ApiKey {accessToken}')
132 : /// ```
133 1 : HttpUploadWorker withAuth({
134 : required String token,
135 : String template = 'Bearer {accessToken}',
136 : }) =>
137 2 : withHeaders({
138 1 : 'Authorization': template.replaceAll('{accessToken}', token),
139 : });
140 :
141 : /// Convenience: sign requests with HMAC-SHA256.
142 1 : HttpUploadWorker withSigning(RequestSigning signing) =>
143 1 : copyWith(requestSigning: signing);
144 :
145 5 : @override
146 : String get workerClassName => 'HttpUploadWorker';
147 :
148 5 : @override
149 5 : Map<String, dynamic> toMap() => {
150 5 : 'workerType': 'httpUpload',
151 10 : 'url': url,
152 10 : 'filePath': filePath,
153 10 : 'fileFieldName': fileFieldName,
154 7 : if (fileName != null) 'fileName': fileName,
155 9 : if (mimeType != null) 'mimeType': mimeType,
156 10 : 'headers': headers,
157 10 : 'additionalFields': additionalFields,
158 15 : 'timeoutMs': timeout.inMilliseconds,
159 10 : 'useBackgroundSession': useBackgroundSession,
160 5 : if (requestSigning != null) 'requestSigning': requestSigning!.toMap(),
161 : };
162 : }
|