Przeglądaj źródła

fix: remove the sync request delay and handle 429 errors

Gerald 2 tygodni temu
rodzic
commit
039f3d2d01

+ 54 - 26
src/background/sync/base.js

@@ -240,7 +240,6 @@ events.on('change', (state) => {
 export const BaseService = serviceFactory({
   name: 'base',
   displayName: 'BaseService',
-  delayTime: 1000,
   urlPrefix: '',
   metaFile: VIOLENTMONKEY,
   properties: {
@@ -352,33 +351,62 @@ export const BaseService = serviceFactory({
         data,
       }));
   },
-  loadData(options) {
-    const { progress } = this;
-    const { delay = this.delayTime } = options;
-    let lastFetch = Promise.resolve();
-    if (delay) {
-      lastFetch = this.lastFetch
-        .then((ts) => makePause(delay - (Date.now() - ts)))
-        .then(() => Date.now());
-      this.lastFetch = lastFetch;
+  _requestDelay: 0,
+  async _request(options) {
+    options = Object.assign({}, NO_CACHE, options);
+    options.headers = Object.assign({}, this.headers, options.headers);
+    let { url } = options;
+    if (url.startsWith('/')) url = (options.prefix ?? this.urlPrefix) + url;
+    let delay = this._requestDelay;
+    let attempts = 5;
+    while (attempts > 0) {
+      attempts -= 1;
+      if (delay >= 200) await makePause(delay);
+      try {
+        const res = await request(url, options);
+        this._requestDelay >>= 1;
+        return res.data;
+      } catch (err) {
+        if (err?.status !== 429 || attempts <= 0) throw err;
+        const retryAfter = err.headers?.get('retry-after');
+        const serverDelay =
+          retryAfter &&
+          (isNaN(+retryAfter)
+            ? new Date(retryAfter).getTime() - Date.now()
+            : +retryAfter * 1000);
+        if (serverDelay) {
+          delay = serverDelay;
+        } else {
+          delay = Math.max(1000, delay * 2);
+          this._requestDelay = delay;
+        }
+      }
+    }
+  },
+  _requestProcessing: false,
+  async _handleRequests() {
+    if (this._requestProcessing) return;
+    this._requestProcessing = true;
+    while (this._requestQueue.length) {
+      const task = this._requestQueue.shift();
+      task.resolve(this._request(task.options));
+      await task.promise.catch(noop);
+      this.progress.finished += 1;
+      onStateChange();
     }
-    progress.total += 1;
+    this._requestProcessing = false;
+  },
+  loadData(options) {
+    const task = { options };
+    task.promise = new Promise((resolve, reject) => {
+      task.resolve = resolve;
+      task.reject = reject;
+    });
+    (this._requestQueue ||= []).push(task);
+    this.progress.total += 1;
     onStateChange();
-    return lastFetch
-      .then(() => {
-        options = Object.assign({}, NO_CACHE, options);
-        options.headers = Object.assign({}, this.headers, options.headers);
-        let { url } = options;
-        if (url.startsWith('/')) url = (options.prefix ?? this.urlPrefix) + url;
-        return request(url, options);
-      })
-      .catch((error) => ({ error }))
-      .then(({ data, error }) => {
-        progress.finished += 1;
-        onStateChange();
-        if (error) return Promise.reject(error);
-        return data;
-      });
+    this._handleRequests();
+    return task.promise;
   },
   getLocalData() {
     return pluginScript.list();

+ 0 - 1
src/background/sync/dropbox.js

@@ -130,7 +130,6 @@ const Dropbox = BaseService.extend({
     });
     if (!data.access_token) throw data;
     this.config.set({
-      uid: data.account_id,
       token: data.access_token,
       refresh_token: data.refresh_token || params.refresh_token,
     });

+ 5 - 11
src/background/sync/googledrive.js

@@ -166,17 +166,11 @@ const GoogleDrive = BaseService.extend({
       ),
       responseType: 'json',
     });
-    if (data.access_token) {
-      const update = {
-        token: data.access_token,
-      };
-      if (data.refresh_token) {
-        update.refresh_token = data.refresh_token;
-      }
-      this.config.set(update);
-    } else {
-      throw data;
-    }
+    if (!data.access_token) throw data;
+    this.config.set({
+      token: data.access_token,
+      refresh_token: data.refresh_token || params.refresh_token,
+    });
   },
   list() {
     throw new Error('Not supported');

+ 7 - 11
src/background/sync/onedrive.js

@@ -2,8 +2,8 @@
 // - https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow
 //
 // Note:
-// - SPA refresh tokens expire after 24h.
-// - Microsoft does not allow browser extensions to use the native app flow.
+// - SPA refresh tokens expire after 24h, but each refresh operation returns a new refresh_token, extending the expiration.
+// - Browser extensions cannot use the native app authorization flow due to Microsoft's restrictions.
 import { dumpQuery, getUniqId, loadQuery, noop } from '@/common';
 import { FORM_URLENCODED, VM_HOME } from '@/common/consts';
 import { objectGet } from '@/common/object';
@@ -148,15 +148,11 @@ const OneDrive = BaseService.extend({
       ),
       responseType: 'json',
     });
-    if (data.access_token) {
-      this.config.set({
-        uid: data.user_id,
-        token: data.access_token,
-        refresh_token: data.refresh_token,
-      });
-    } else {
-      throw data;
-    }
+    if (!data.access_token) throw data;
+    this.config.set({
+      token: data.access_token,
+      refresh_token: data.refresh_token || params.refresh_token,
+    });
   },
 });
 if (config.client_id) register(OneDrive);