Line data Source code
1 : import 'package:flutter/foundation.dart';
2 : import 'dart:ui';
3 : import 'native_work_manager.dart';
4 :
5 : // Import all worker implementations
6 : import 'workers.dart';
7 :
8 : // Export all worker implementations
9 : export 'workers.dart';
10 :
11 : // Per-category part files for NativeWorker factory methods
12 : part 'workers/native_worker_http.dart';
13 : part 'workers/native_worker_custom.dart';
14 : part 'workers/native_worker_file.dart';
15 : part 'workers/native_worker_crypto.dart';
16 : part 'workers/native_worker_image.dart';
17 : part 'workers/native_worker_pdf.dart';
18 : part 'workers/native_worker_websocket.dart';
19 :
20 : /// HTTP methods for network workers.
21 : enum HttpMethod { get, post, put, delete, patch }
22 :
23 : /// Compression levels for file compression.
24 : enum CompressionLevel {
25 : /// Low compression (faster, larger file).
26 : low,
27 :
28 : /// Medium compression (balanced).
29 : medium,
30 :
31 : /// High compression (slower, smaller file).
32 : high,
33 : }
34 :
35 : /// Base class for all worker configurations.
36 : ///
37 : /// All built-in workers (HttpDownloadWorker, FileCompressWorker, etc.) extend
38 : /// this class. Custom native workers use [CustomNativeWorker].
39 : @immutable
40 : abstract class Worker {
41 35 : const Worker();
42 :
43 : /// Convert to map for platform channel.
44 : Map<String, dynamic> toMap();
45 :
46 : /// Get the worker class name for native side.
47 : String get workerClassName;
48 : }
49 :
50 : // ═══════════════════════════════════════════════════════════════════════════════
51 : // NATIVE WORKERS (Mode 1) - Zero Flutter Engine
52 : // ═══════════════════════════════════════════════════════════════════════════════
53 :
54 : /// Built-in native workers that run WITHOUT Flutter Engine.
55 : ///
56 : /// These workers execute using KMP native code, saving ~50MB RAM
57 : /// compared to Dart-based workers.
58 : class NativeWorker {
59 0 : NativeWorker._();
60 :
61 : /// Validate URL format and throw helpful error if invalid.
62 17 : static void _validateUrl(String url) {
63 17 : _validateInput(url, 'URL');
64 17 : if (url.isEmpty) {
65 2 : throw ArgumentError(
66 : 'URL cannot be empty.\n'
67 : 'Provide a valid HTTP/HTTPS URL like "https://api.example.com/endpoint"',
68 : );
69 : }
70 :
71 17 : final uri = Uri.tryParse(url);
72 : if (uri == null ||
73 85 : (!uri.hasScheme || (uri.scheme != 'http' && uri.scheme != 'https'))) {
74 6 : throw ArgumentError(
75 : 'Invalid URL format: "$url"\n'
76 : 'URL must start with http:// or https://\n'
77 : 'Example: "https://api.example.com/endpoint"',
78 : );
79 : }
80 :
81 : // SECURITY: Enforce HTTPS if configured
82 19 : if (NativeWorkManager.enforceHttps && uri.scheme == 'http') {
83 2 : throw ArgumentError(
84 : 'Insecure URL blocked: "$url"\n'
85 : 'HTTPS is enforced by NativeWorkManager.initialize(enforceHttps: true)',
86 : );
87 : }
88 :
89 : // SECURITY: Block private IPs if configured
90 17 : if (NativeWorkManager.blockPrivateIPs) {
91 2 : final host = uri.host.toLowerCase();
92 1 : final isPrivate = host == 'localhost' ||
93 1 : host == '127.0.0.1' ||
94 1 : host == '::1' ||
95 1 : host.startsWith('192.168.') ||
96 1 : host.startsWith('10.') ||
97 2 : RegExp(r'^172\.(1[6-9]|2[0-9]|3[0-1])\.').hasMatch(host);
98 :
99 : if (isPrivate) {
100 2 : throw ArgumentError(
101 : 'Private IP URL blocked: "$url"\n'
102 : 'Requests to private/loopback IPs are blocked by '
103 : 'NativeWorkManager.initialize(blockPrivateIPs: true) to prevent SSRF.',
104 : );
105 : }
106 : }
107 : }
108 :
109 : /// Check input for malicious patterns (Null bytes, injection chars).
110 27 : static void _validateInput(String? value, String label) {
111 : if (value == null) return;
112 :
113 27 : if (value.contains('\u0000')) {
114 2 : throw ArgumentError(
115 2 : '$label contains null bytes which is not allowed for security reasons.');
116 : }
117 : }
118 :
119 : /// Validate file path for path traversal or injection.
120 22 : static void _validatePath(String path, String label) {
121 22 : _validateInput(path, label);
122 :
123 : // SECURITY: Prevent path traversal attacks
124 22 : final normalized = path.toLowerCase();
125 44 : if (normalized.contains('..') || normalized.contains('%2e%2e')) {
126 2 : throw ArgumentError(
127 2 : '$label cannot contain ".." or encoded dot-segments (path traversal attempt blocked).');
128 : }
129 :
130 43 : if (path.contains(';') || path.contains('|')) {
131 1 : throw ArgumentError(
132 1 : '$label contains illegal shell injection characters.');
133 : }
134 :
135 : // SECURITY: Require absolute paths (relative paths can be manipulated)
136 : // EXCEPTION: Allow placeholders in the format {{taskId.key}} for task chains
137 21 : if (!path.startsWith('/') &&
138 3 : !path.startsWith('{{') &&
139 3 : !path.startsWith(r'C:\') &&
140 3 : !path.startsWith(r'\\')) {
141 6 : throw ArgumentError(
142 : 'Relative path not allowed in $label: "$path"\n'
143 : 'Use absolute paths like "/path/to/file.jpg"\n'
144 : 'Relative paths are blocked to prevent path traversal attacks',
145 : );
146 : }
147 : }
148 :
149 : /// Validate file path and throw helpful error if invalid.
150 22 : static void _validateFilePath(String path, String parameterName) {
151 22 : if (path.isEmpty) {
152 22 : throw ArgumentError(
153 : '$parameterName cannot be empty.\n'
154 : 'Provide an absolute file path like "/data/user/0/app/files/data.db"',
155 : );
156 : }
157 22 : _validatePath(path, parameterName);
158 : }
159 :
160 : // ── HTTP workers ────────────────────────────────────────────────────────────
161 :
162 : /// HTTP request worker (GET, POST, PUT, DELETE).
163 : /// See [_buildHttpRequest] (in native_worker_http.dart) for full documentation.
164 13 : static Worker httpRequest({
165 : required String url,
166 : HttpMethod method = HttpMethod.get,
167 : Map<String, String> headers = const {},
168 : String? body,
169 : Duration timeout = const Duration(seconds: 30),
170 : TokenRefreshConfig? tokenRefresh,
171 : }) =>
172 13 : _buildHttpRequest(
173 : url: url,
174 : method: method,
175 : headers: headers,
176 : body: body,
177 : timeout: timeout,
178 : tokenRefresh: tokenRefresh,
179 : );
180 :
181 : /// HTTP file upload worker (multipart).
182 : /// See [_buildHttpUpload] (in native_worker_http.dart) for full documentation.
183 9 : static Worker httpUpload({
184 : required String url,
185 : required String filePath,
186 : String fileFieldName = 'file',
187 : String? fileName,
188 : String? mimeType,
189 : Map<String, String> headers = const {},
190 : Map<String, String> additionalFields = const {},
191 : Duration timeout = const Duration(minutes: 5),
192 : bool useBackgroundSession = false,
193 : }) =>
194 9 : _buildHttpUpload(
195 : url: url,
196 : filePath: filePath,
197 : fileFieldName: fileFieldName,
198 : fileName: fileName,
199 : mimeType: mimeType,
200 : headers: headers,
201 : additionalFields: additionalFields,
202 : timeout: timeout,
203 : useBackgroundSession: useBackgroundSession,
204 : );
205 :
206 : /// Upload multiple files in a single multipart/form-data HTTP request.
207 : /// See [_buildMultiUpload] (in native_worker_http.dart) for full documentation.
208 1 : static MultiUploadWorker multiUpload({
209 : required String url,
210 : required List<UploadFile> files,
211 : Map<String, String> headers = const {},
212 : Map<String, String> additionalFields = const {},
213 : Duration timeout = const Duration(minutes: 10),
214 : bool useBackgroundSession = false,
215 : }) =>
216 1 : _buildMultiUpload(
217 : url: url,
218 : files: files,
219 : headers: headers,
220 : additionalFields: additionalFields,
221 : timeout: timeout,
222 : useBackgroundSession: useBackgroundSession,
223 : );
224 :
225 : /// Move a file from app-private storage to a shared / public location.
226 : /// See [_buildMoveToSharedStorage] (in native_worker_http.dart) for full documentation.
227 1 : static MoveToSharedStorageWorker moveToSharedStorage({
228 : required String sourcePath,
229 : required SharedStorageType storageType,
230 : String? fileName,
231 : String? mimeType,
232 : String? subDir,
233 : }) =>
234 1 : _buildMoveToSharedStorage(
235 : sourcePath: sourcePath,
236 : storageType: storageType,
237 : fileName: fileName,
238 : mimeType: mimeType,
239 : subDir: subDir,
240 : );
241 :
242 : /// HTTP file download worker.
243 : /// See [_buildHttpDownload] (in native_worker_http.dart) for full documentation.
244 11 : static Worker httpDownload({
245 : required String url,
246 : required String savePath,
247 : Map<String, String> headers = const {},
248 : Duration timeout = const Duration(minutes: 5),
249 : bool enableResume = true,
250 : String? expectedChecksum,
251 : String checksumAlgorithm = 'SHA-256',
252 : bool useBackgroundSession = false,
253 : bool skipExisting = false,
254 : bool allowPause = false,
255 : Map<String, String>? cookies,
256 : String? authToken,
257 : String authHeaderTemplate = 'Bearer {accessToken}',
258 : DuplicatePolicy onDuplicate = DuplicatePolicy.overwrite,
259 : bool moveToPublicDownloads = false,
260 : bool saveToGallery = false,
261 : @Deprecated(
262 : 'Native ZIP support is removed in v1.1.0. Use Dart "archive" package.')
263 : bool extractAfterDownload = false,
264 : @Deprecated(
265 : 'Native ZIP support is removed in v1.1.0. Use Dart "archive" package.')
266 : String? extractPath,
267 : @Deprecated(
268 : 'Native ZIP support is removed in v1.1.0. Use Dart "archive" package.')
269 : bool deleteArchiveAfterExtract = false,
270 : }) =>
271 11 : _buildHttpDownload(
272 : url: url,
273 : savePath: savePath,
274 : headers: headers,
275 : timeout: timeout,
276 : enableResume: enableResume,
277 : expectedChecksum: expectedChecksum,
278 : checksumAlgorithm: checksumAlgorithm,
279 : useBackgroundSession: useBackgroundSession,
280 : skipExisting: skipExisting,
281 : allowPause: allowPause,
282 : cookies: cookies,
283 : authToken: authToken,
284 : authHeaderTemplate: authHeaderTemplate,
285 : onDuplicate: onDuplicate,
286 : moveToPublicDownloads: moveToPublicDownloads,
287 : saveToGallery: saveToGallery,
288 : extractAfterDownload: extractAfterDownload,
289 : extractPath: extractPath,
290 : deleteArchiveAfterExtract: deleteArchiveAfterExtract,
291 : );
292 :
293 : /// Parallel chunked HTTP download worker.
294 : /// See [_buildParallelHttpDownload] (in native_worker_http.dart) for full documentation.
295 1 : static Worker parallelHttpDownload({
296 : required String url,
297 : required String savePath,
298 : int numChunks = 4,
299 : Map<String, String> headers = const {},
300 : Duration timeout = const Duration(minutes: 10),
301 : String? expectedChecksum,
302 : String checksumAlgorithm = 'SHA-256',
303 : bool showNotification = false,
304 : String? notificationTitle,
305 : String? notificationBody,
306 : bool skipExisting = false,
307 : }) =>
308 1 : _buildParallelHttpDownload(
309 : url: url,
310 : savePath: savePath,
311 : numChunks: numChunks,
312 : headers: headers,
313 : timeout: timeout,
314 : expectedChecksum: expectedChecksum,
315 : checksumAlgorithm: checksumAlgorithm,
316 : showNotification: showNotification,
317 : notificationTitle: notificationTitle,
318 : notificationBody: notificationBody,
319 : skipExisting: skipExisting,
320 : );
321 :
322 : /// Data sync worker (POST JSON, receive JSON).
323 : /// See [_buildHttpSync] (in native_worker_http.dart) for full documentation.
324 7 : static Worker httpSync({
325 : required String url,
326 : HttpMethod method = HttpMethod.post,
327 : Map<String, String> headers = const {},
328 : Map<String, dynamic>? requestBody,
329 : Duration timeout = const Duration(seconds: 60),
330 : TokenRefreshConfig? tokenRefresh,
331 : RequestSigning? requestSigning,
332 : }) =>
333 7 : _buildHttpSync(
334 : url: url,
335 : method: method,
336 : headers: headers,
337 : requestBody: requestBody,
338 : timeout: timeout,
339 : tokenRefresh: tokenRefresh,
340 : requestSigning: requestSigning,
341 : );
342 :
343 : // ── Custom worker ────────────────────────────────────────────────────────────
344 :
345 : /// Custom native worker for user-defined implementations.
346 : /// See [_buildCustom] (in native_worker_custom.dart) for full documentation.
347 2 : static Worker custom({
348 : required String className,
349 : Map<String, dynamic>? input,
350 : }) =>
351 2 : _buildCustom(className: className, input: input);
352 :
353 : // ── File workers ─────────────────────────────────────────────────────────────
354 :
355 : /// File compression worker (ZIP format).
356 : /// See [_buildFileCompress] (in native_worker_file.dart) for full documentation.
357 4 : @Deprecated(
358 : 'Native ZIP support is removed in v1.1.0 to achieve Zero Dependencies. '
359 : 'Please use the Dart "archive" package for ZIP operations instead.',
360 : )
361 : static Worker fileCompress({
362 : required String inputPath,
363 : required String outputPath,
364 : CompressionLevel level = CompressionLevel.medium,
365 : List<String> excludePatterns = const [],
366 : bool deleteOriginal = false,
367 : }) =>
368 4 : _buildFileCompress(
369 : inputPath: inputPath,
370 : outputPath: outputPath,
371 : level: level,
372 : excludePatterns: excludePatterns,
373 : deleteOriginal: deleteOriginal,
374 : );
375 :
376 : /// File decompression worker (ZIP extraction).
377 : /// See [_buildFileDecompress] (in native_worker_file.dart) for full documentation.
378 4 : @Deprecated(
379 : 'Native ZIP support is removed in v1.1.0 to achieve Zero Dependencies. '
380 : 'Please use the Dart "archive" package for ZIP operations instead.',
381 : )
382 : static Worker fileDecompress({
383 : required String zipPath,
384 : required String targetDir,
385 : bool deleteAfterExtract = false,
386 : bool overwrite = true,
387 : }) =>
388 4 : _buildFileDecompress(
389 : zipPath: zipPath,
390 : targetDir: targetDir,
391 : deleteAfterExtract: deleteAfterExtract,
392 : overwrite: overwrite,
393 : );
394 :
395 : /// Copy file or directory worker.
396 : /// See [_buildFileCopy] (in native_worker_file.dart) for full documentation.
397 3 : static Worker fileCopy({
398 : required String sourcePath,
399 : required String destinationPath,
400 : bool overwrite = false,
401 : bool recursive = true,
402 : }) =>
403 3 : _buildFileCopy(
404 : sourcePath: sourcePath,
405 : destinationPath: destinationPath,
406 : overwrite: overwrite,
407 : recursive: recursive,
408 : );
409 :
410 : /// Move file or directory worker.
411 : /// See [_buildFileMove] (in native_worker_file.dart) for full documentation.
412 3 : static Worker fileMove({
413 : required String sourcePath,
414 : required String destinationPath,
415 : bool overwrite = false,
416 : }) =>
417 3 : _buildFileMove(
418 : sourcePath: sourcePath,
419 : destinationPath: destinationPath,
420 : overwrite: overwrite,
421 : );
422 :
423 : /// Delete file or directory worker.
424 : /// See [_buildFileDelete] (in native_workmanager_file.dart) for full documentation.
425 4 : static Worker fileDelete({required String path, bool recursive = false}) =>
426 4 : _buildFileDelete(path: path, recursive: recursive);
427 :
428 : /// List directory contents worker.
429 : /// See [_buildFileList] (in native_worker_file.dart) for full documentation.
430 2 : static Worker fileList({
431 : required String path,
432 : String? pattern,
433 : bool recursive = false,
434 : }) =>
435 2 : _buildFileList(path: path, pattern: pattern, recursive: recursive);
436 :
437 : /// Create directory worker (mkdir).
438 : /// See [_buildFileMkdir] (in native_worker_file.dart) for full documentation.
439 2 : static Worker fileMkdir({required String path, bool createParents = true}) =>
440 2 : _buildFileMkdir(path: path, createParents: createParents);
441 :
442 : // ── Crypto workers ───────────────────────────────────────────────────────────
443 :
444 : /// Cryptographic hash of a file.
445 : /// See [_buildHashFile] (in native_worker_crypto.dart) for full documentation.
446 2 : static Worker hashFile({
447 : required String filePath,
448 : HashAlgorithm algorithm = HashAlgorithm.sha256,
449 : }) =>
450 2 : _buildHashFile(filePath: filePath, algorithm: algorithm);
451 :
452 : /// Hash string data.
453 : /// See [_buildHashString] (in native_worker_crypto.dart) for full documentation.
454 2 : static Worker hashString({
455 : required String data,
456 : HashAlgorithm algorithm = HashAlgorithm.sha256,
457 : }) =>
458 2 : _buildHashString(data: data, algorithm: algorithm);
459 :
460 : /// File encryption worker (AES-256-GCM).
461 : /// See [_buildCryptoEncrypt] (in native_worker_crypto.dart) for full documentation.
462 5 : static Worker cryptoEncrypt({
463 : required String inputPath,
464 : required String outputPath,
465 : required String password,
466 : }) =>
467 5 : _buildCryptoEncrypt(
468 : inputPath: inputPath,
469 : outputPath: outputPath,
470 : password: password,
471 : );
472 :
473 : /// File decryption worker (AES-256-GCM).
474 : /// See [_buildCryptoDecrypt] (in native_worker_crypto.dart) for full documentation.
475 4 : static Worker cryptoDecrypt({
476 : required String inputPath,
477 : required String outputPath,
478 : required String password,
479 : }) =>
480 4 : _buildCryptoDecrypt(
481 : inputPath: inputPath,
482 : outputPath: outputPath,
483 : password: password,
484 : );
485 :
486 : // ── PDF workers ──────────────────────────────────────────────────────────────
487 :
488 : /// Merge multiple PDF files into one.
489 : /// See [_buildPdfMerge] (in native_worker_pdf.dart) for full documentation.
490 1 : static Worker pdfMerge({
491 : required List<String> inputPaths,
492 : required String outputPath,
493 : }) =>
494 1 : _buildPdfMerge(inputPaths: inputPaths, outputPath: outputPath);
495 :
496 : /// Re-render a PDF at lower quality to reduce file size.
497 : /// See [_buildPdfCompress] (in native_worker_pdf.dart) for full documentation.
498 1 : static Worker pdfCompress({
499 : required String inputPath,
500 : required String outputPath,
501 : int quality = 80,
502 : }) =>
503 1 : _buildPdfCompress(
504 : inputPath: inputPath, outputPath: outputPath, quality: quality);
505 :
506 : /// Convert image files into a PDF (one image per page).
507 : /// See [_buildPdfFromImages] (in native_worker_pdf.dart) for full documentation.
508 1 : static Worker pdfFromImages({
509 : required List<String> imagePaths,
510 : required String outputPath,
511 : PdfPageSize pageSize = PdfPageSize.a4,
512 : int margin = 0,
513 : }) =>
514 1 : _buildPdfFromImages(
515 : imagePaths: imagePaths,
516 : outputPath: outputPath,
517 : pageSize: pageSize,
518 : margin: margin,
519 : );
520 :
521 : // ── WebSocket worker ─────────────────────────────────────────────────────────
522 :
523 : /// WebSocket worker — connect, send messages, receive responses.
524 : /// See [_buildWebSocket] (in native_worker_websocket.dart) for full documentation.
525 : /// **Android only** — returns failure on iOS.
526 1 : static Worker webSocket({
527 : required String url,
528 : List<String> messages = const [],
529 : Map<String, String> headers = const {},
530 : int timeoutSeconds = 30,
531 : int receiveMessages = 1,
532 : String? storeResponseAt,
533 : int? pingIntervalSeconds,
534 : }) =>
535 1 : _buildWebSocket(
536 : url: url,
537 : messages: messages,
538 : headers: headers,
539 : timeoutSeconds: timeoutSeconds,
540 : receiveMessages: receiveMessages,
541 : storeResponseAt: storeResponseAt,
542 : pingIntervalSeconds: pingIntervalSeconds,
543 : );
544 :
545 : // ── Image workers ────────────────────────────────────────────────────────────
546 :
547 : /// Image processing worker (resize, compress, convert).
548 : /// See [_buildImageProcess] (in native_worker_image.dart) for full documentation.
549 4 : static Worker imageProcess({
550 : required String inputPath,
551 : required String outputPath,
552 : int? maxWidth,
553 : int? maxHeight,
554 : bool maintainAspectRatio = true,
555 : int quality = 85,
556 : ImageFormat? outputFormat,
557 : Rect? cropRect,
558 : bool deleteOriginal = false,
559 : }) =>
560 4 : _buildImageProcess(
561 : inputPath: inputPath,
562 : outputPath: outputPath,
563 : maxWidth: maxWidth,
564 : maxHeight: maxHeight,
565 : maintainAspectRatio: maintainAspectRatio,
566 : quality: quality,
567 : outputFormat: outputFormat,
568 : cropRect: cropRect,
569 : deleteOriginal: deleteOriginal,
570 : );
571 : }
|