downloadFileByShareToken method
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;
}
}