diff --git a/app/assets/javascripts/discourse/app/services/presence.js b/app/assets/javascripts/discourse/app/services/presence.js index 8949b99ee47..38abb4d4796 100644 --- a/app/assets/javascripts/discourse/app/services/presence.js +++ b/app/assets/javascripts/discourse/app/services/presence.js @@ -16,7 +16,7 @@ import { isTesting } from "discourse-common/config/environment"; import getURL from "discourse-common/lib/get-url"; const PRESENCE_INTERVAL_S = 30; -const PRESENCE_DEBOUNCE_MS = isTesting() ? 0 : 500; +const DEFAULT_PRESENCE_DEBOUNCE_MS = isTesting() ? 0 : 500; const PRESENCE_THROTTLE_MS = isTesting() ? 0 : 1000; const PRESENCE_GET_RETRY_MS = 5000; @@ -251,6 +251,8 @@ class PresenceChannelState extends EmberObject.extend(Evented) { } export default class PresenceService extends Service { + _presenceDebounceMs = DEFAULT_PRESENCE_DEBOUNCE_MS; + init() { super.init(...arguments); this._queuedEvents = []; @@ -276,6 +278,7 @@ export default class PresenceService extends Service { super.willDestroy(...arguments); window.removeEventListener("beforeunload", this._beaconLeaveAll); removeOnPresenceChange(this._throttledUpdateServer); + cancel(this._debounceTimer); } // Get a PresenceChannel object representing a single channel @@ -543,11 +546,15 @@ export default class PresenceService extends Service { e.promiseProxy.resolve(); } }); + + this._presenceDebounceMs = DEFAULT_PRESENCE_DEBOUNCE_MS; } catch (e) { // Put the failed events back in the queue for next time this._queuedEvents.unshift(...queue); if (e.jqXHR?.status === 429) { - // Rate limited. No need to raise, we'll try again later + // Rate limited + const waitSeconds = e.jqXHR.responseJSON?.extras?.wait_seconds || 10; + this._presenceDebounceMs = waitSeconds * 1000; } else { throw e; } @@ -585,7 +592,12 @@ export default class PresenceService extends Service { return; } else if (this._queuedEvents.length > 0) { this._cancelTimer(); - debounce(this, this._throttledUpdateServer, PRESENCE_DEBOUNCE_MS); + cancel(this._debounceTimer); + this._debounceTimer = debounce( + this, + this._throttledUpdateServer, + this._presenceDebounceMs + ); } else if ( !this._nextUpdateTimer && this._presentChannels.length > 0 && diff --git a/app/assets/javascripts/discourse/tests/unit/services/presence-test.js b/app/assets/javascripts/discourse/tests/unit/services/presence-test.js index 9aaf803a9e0..51ab78b72bc 100644 --- a/app/assets/javascripts/discourse/tests/unit/services/presence-test.js +++ b/app/assets/javascripts/discourse/tests/unit/services/presence-test.js @@ -477,4 +477,24 @@ module("Unit | Service | presence | entering and leaving", function (hooks) { "skips sending empty updates to the server" ); }); + + test("don't spam requests when server returns 429", function (assert) { + const done = assert.async(); + let requestCount = 0; + pretender.post("/presence/update", async () => { + requestCount++; + return response(429, { extras: { wait_seconds: 2 } }); + }); + + const presenceService = getOwner(this).lookup("service:presence"); + presenceService.currentUser = currentUser(); + const channel = presenceService.getChannel("/test/ch1"); + + setTimeout(function () { + assert.strictEqual(requestCount, 1); + done(); + }, 500); + + channel.enter(); + }); });