From 071e1649bc2fd5c3e8a76ce13415e882df332e9c Mon Sep 17 00:00:00 2001 From: Alec <32174181+alecjmaly@users.noreply.github.com> Date: Sun, 8 Aug 2021 13:38:04 -0400 Subject: [PATCH] All: Resolves #5244: Handles OneDrive throttling responses and sets User-Agent based on Microsoft best practices (#5246) --- packages/lib/onedrive-api.ts | 15 +++++++++++++-- packages/lib/testing/test-utils.ts | 8 ++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/lib/onedrive-api.ts b/packages/lib/onedrive-api.ts index c74755ef3..8eb42b79c 100644 --- a/packages/lib/onedrive-api.ts +++ b/packages/lib/onedrive-api.ts @@ -265,11 +265,13 @@ export default class OneDriveApi { for (let i = 0; i < 5; i++) { options.headers['Authorization'] = `bearer ${this.token()}`; + options.headers['User-Agent'] = `ISV|Joplin|Joplin/${shim.appVersion()}`; - const handleRequestRepeat = async (error: any) => { + const handleRequestRepeat = async (error: any, sleepSeconds: number = null) => { + sleepSeconds ??= (i + 1) * 5; logger.info(`Got error below - retrying (${i})...`); logger.info(error); - await time.sleep((i + 1) * 5); + await time.sleep(sleepSeconds); }; let response = null; @@ -339,6 +341,15 @@ export default class OneDriveApi { await handleRequestRepeat(error); continue; + } else if (error?.code === 'activityLimitReached' && response?.headers?._headers['retry-after'][0] && !isNaN(Number(response?.headers?._headers['retry-after'][0]))) { + // Wait for OneDrive throttling + // Relavent Microsoft Docs: https://docs.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online#best-practices-to-handle-throttling + // Decrement retry count as multiple sync threads will cause repeated throttling errors - this will wait until throttling is resolved to continue, preventing a hard stop on the sync + i--; + const sleepSeconds = response.headers._headers['retry-after'][0]; + logger.info(`OneDrive Throttle, sync thread sleeping for ${sleepSeconds} seconds...`); + await handleRequestRepeat(error, Number(sleepSeconds)); + continue; } else if (error.code == 'itemNotFound' && method == 'DELETE') { // Deleting a non-existing item is ok - noop return; diff --git a/packages/lib/testing/test-utils.ts b/packages/lib/testing/test-utils.ts index d082ae3d9..d4f1bee1e 100644 --- a/packages/lib/testing/test-utils.ts +++ b/packages/lib/testing/test-utils.ts @@ -536,12 +536,16 @@ async function initFileApi() { api.setAuthToken(authToken); fileApi = new FileApi('', new FileApiDriverDropbox(api)); } else if (syncTargetId_ == SyncTargetRegistry.nameToId('onedrive')) { - // To get a token, open the URL below, then copy the *complete* - // redirection URL in onedrive-auth.txt. Keep in mind that auth + // To get a token, open the URL below corresponding to your account type, + // then copy the *complete* redirection URL in onedrive-auth.txt. Keep in mind that auth // data only lasts 1h for OneDrive. // + // Personal OneDrive Account: // https://login.live.com/oauth20_authorize.srf?client_id=f1e68e1e-a729-4514-b041-4fdd5c7ac03a&scope=files.readwrite,offline_access&response_type=token&redirect_uri=https://joplinapp.org // + // Business OneDrive Account: + // https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=f1e68e1e-a729-4514-b041-4fdd5c7ac03a&scope=files.readwrite offline_access&response_type=token&redirect_uri=https://joplinapp.org + // // Also for now OneDrive tests cannot be run in parallel because // for that each suite would need its own sub-directory within the // OneDrive app directory, and it's not clear how to get that