downloadFileByShareToken method

  1. @override
Future<String> downloadFileByShareToken({
  1. required String shareToken,
  2. required String localPath,
})
override

Downloads a file to localPath using a shareToken. Wasn't able to make the download work via the microsoft graph api, therefore this functions uses the shareLink with &download=1 to download the file via a headless browser. Would be glad if someone could rewrite this to use the microsoft graph api if possible.

Implementation

@override
Future<String> downloadFileByShareToken({
  required String shareToken,
  required String localPath,
}) async {
  final completer = Completer<String>();
  late HeadlessInAppWebView headlessWebView;
  // Append `?download=1` to the share link to hint at a direct download.
  final initialUrl =
      Uri.parse(shareToken).replace(queryParameters: {'download': '1'});
  debugPrint(
      "Starting headless WebView to resolve download for: $initialUrl");
  headlessWebView = HeadlessInAppWebView(
      initialUrlRequest: URLRequest(url: WebUri.uri(initialUrl)),
      // This callback captures the final download URL after all redirects.
      onDownloadStartRequest: (controller, downloadStartRequest) async {
        final finalUrl = downloadStartRequest.url.toString();
        debugPrint("WebView captured final download URL: $finalUrl");
        if (!completer.isCompleted) {
          completer.complete(finalUrl);
        }
      },
      onLoadError: (controller, url, code, message) {
        debugPrint("WebView error: Code $code, Message: $message");
        if (!completer.isCompleted) {
          completer.completeError(Exception("WebView error: $message"));
        }
      },
      // Handles cases where the WebView lands on an error page instead of triggering a download.
      onLoadStop: (controller, url) async {
        if (!completer.isCompleted) {
          final pageBody = await controller.getHtml() ?? "";
          if (pageBody.toLowerCase().contains("error") ||
              pageBody.toLowerCase().contains("denied")) {
            completer.completeError(NotFoundException(
                "WebView navigation ended on an error page. File may not exist or permissions are denied."));
          }
        }
      });
  try {
    await headlessWebView.run();
    // Wait for the download URL to be captured, with a timeout.
    final finalDownloadUrl =
        await completer.future.timeout(const Duration(seconds: 30));
    await headlessWebView.dispose();
    // --- Use Dio with a custom interceptor for the final download ---
    final dio = Dio();
    // This interceptor will attach the cookies gathered by the WebView to the Dio request.
    dio.interceptors.add(WebViewCookieInterceptor());
    debugPrint("Downloading with Dio using WebView cookies and Referer.");
    await dio.download(
      finalDownloadUrl,
      localPath,
      options: Options(
        headers: {
          // Set a common User-Agent and Referer to mimic a browser request.
          'User-Agent':
              'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
          'Referer': shareToken,
        },
      ),
    );
    debugPrint("File successfully downloaded to $localPath");
    return localPath;
  } catch (e) {
    debugPrint("Error during WebView download process");
    await headlessWebView.dispose();
    rethrow;
  }
}