diff --git a/CHANGELOG.md b/CHANGELOG.md index b3b61172..440f9cad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ x.x.x Release notes (yyyy-MM-dd) ============================================================= ### Enhancements -* None. +* Improved the proactive token refresh mechanism to make several attempts to refresh the token before it expires and to also ensure that there is only one ongoing refresh timer for a combination of user and realm path. Previously it was possible to end up in a situation where many redundant refreshes were scheduled for the same Realm. ([#2071](https://github.com/realm/realm-js/pull/2071), since v1.0.2) ### Fixes * ([#????](https://github.com/realm/realm-js/issues/????), since v?.?.?) -* None. +* Fixed the signature of `user.logout` to return a `Promise` rather than `void`. It has always done asynchronous work, but previously, it was impossible to be notified that the call has completed. Since that is now possible, the superfluous "User is logged out" message printed in the console upon logout has been removed. ([#2071](https://github.com/realm/realm-js/pull/2071), since v2.3.0) ### Compatibility * Realm Object Server: 3.11.0 or later. diff --git a/docs/sync.js b/docs/sync.js index db62ca4a..9a85b001 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -524,7 +524,9 @@ class User { serialize() {} /** - * Logs out the user from the Realm Object Server. + * Logs out the user from the Realm Object Server. Once the Object Server has confirmed the logout the user + * credentials will be deleted from this device. + * @return {Promise} A promise which is resolved when the user has logged out both locally and on the server. */ logout() {} diff --git a/lib/index.d.ts b/lib/index.d.ts index 8a7b04ab..6c11ea65 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -337,7 +337,7 @@ declare namespace Realm.Sync { createConfiguration(config?: Realm.PartialConfiguration): Realm.Configuration serialize(): SerializedUser; - logout(): void; + logout(): Promise; openManagementRealm(): Realm; retrieveAccount(provider: string, username: string): Promise; diff --git a/lib/user-methods.js b/lib/user-methods.js index f02034cd..88e9b729 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -25,6 +25,10 @@ const merge = require('deepmerge'); const require_method = require; const URL = require('url-parse'); +const refreshTimers = {}; +const retryInterval = 5 * 1000; +const refreshBuffer = 20 * 1000; + function node_require(module) { return require_method(module); } @@ -107,9 +111,20 @@ function append_url(server, path) { } function scheduleAccessTokenRefresh(user, localRealmPath, realmUrl, expirationDate) { - const refreshBuffer = 10 * 1000; - const timeout = expirationDate - Date.now() - refreshBuffer; - setTimeout(() => refreshAccessToken(user, localRealmPath, realmUrl), timeout); + let userTimers = refreshTimers[user.identity]; + if (!userTimers) { + refreshTimers[user.identity] = userTimers = {}; + } + + // We assume that access tokens have ~ the same expiration time, so if someone already + // scheduled a refresh, it's likely to complete before the one we would have scheduled + if (!userTimers[localRealmPath]) { + const timeout = expirationDate - Date.now() - refreshBuffer; + userTimers[localRealmPath] = setTimeout(() => { + delete userTimers[localRealmPath]; + refreshAccessToken(user, localRealmPath, realmUrl); + }, timeout); + } } function print_error() { @@ -177,7 +192,7 @@ function refreshAdminToken(user, localRealmPath, realmUrl) { }) .catch((e) => { print_error(e); - setTimeout(() => refreshAccessToken(user, localRealmPath, realmUrl), 10 * 1000); + setTimeout(() => refreshAccessToken(user, localRealmPath, realmUrl), retryInterval); }); } @@ -239,7 +254,7 @@ function refreshAccessToken(user, localRealmPath, realmUrl) { .catch((e) => { print_error(e); // in case something lower in the HTTP stack breaks, try again in 10 seconds - setTimeout(() => refreshAccessToken(user, localRealmPath, realmUrl), 10 * 1000); + setTimeout(() => refreshAccessToken(user, localRealmPath, realmUrl), retryInterval); }) } @@ -498,6 +513,15 @@ const staticMethods = { const instanceMethods = { logout() { this._logout(); + const userTimers = refreshTimers[this.identity]; + if (userTimers) { + Object.keys(userTimers).forEach((key) => { + clearTimeout(userTimers[key]); + }); + + delete refreshTimers[this.identity]; + } + const url = url_parse(this.server); url.set('pathname', '/auth/revoke'); const headers = { @@ -513,9 +537,8 @@ const instanceMethods = { open_timeout: 5000 }; - performFetch(url.href, options) - .then(() => console.log('User is logged out')) - .catch((e) => print_error(e)); + return performFetch(url.href, options) + .catch((e) => print_error('An error occurred while logging out a user', e)); }, serialize() { return {