@uppy/companion-client
Advanced tools
Comparing version 3.4.0 to 3.4.1
# @uppy/companion-client | ||
## 3.4.1 | ||
Released: 2023-09-29 | ||
Included in: Uppy v3.17.0 | ||
- @uppy/companion-client: fix a refresh token race condition (Mikael Finstad / #4695) | ||
## 3.4.0 | ||
@@ -4,0 +11,0 @@ |
@@ -164,23 +164,33 @@ 'use strict'; | ||
// throw Object.assign(new Error(), { isAuthError: true }) // testing simulate access token expired (to refresh token) | ||
// A better way to test this is for example with Google Drive: | ||
// While uploading, go to your google account settings, | ||
// "Third-party apps & services", then click "Companion" and "Remove access". | ||
return await super.request(...arguments); | ||
} catch (err) { | ||
if (!err.isAuthError) throw err; // only handle auth errors (401 from provider) | ||
// only handle auth errors (401 from provider), and only handle them if we have a (refresh) token | ||
if (!err.isAuthError || !(await _classPrivateFieldLooseBase(this, _getAuthToken)[_getAuthToken]())) throw err; | ||
if (_classPrivateFieldLooseBase(this, _refreshingTokenPromise)[_refreshingTokenPromise] == null) { | ||
// Many provider requests may be starting at once, however refresh token should only be called once. | ||
// Once a refresh token operation has started, we need all other request to wait for this operation (atomically) | ||
_classPrivateFieldLooseBase(this, _refreshingTokenPromise)[_refreshingTokenPromise] = (async () => { | ||
try { | ||
const response = await super.request({ | ||
path: this.refreshTokenUrl(), | ||
method: 'POST' | ||
}); | ||
await this.setAuthToken(response.uppyAuthToken); | ||
} catch (refreshTokenErr) { | ||
if (refreshTokenErr.isAuthError) { | ||
// if refresh-token has failed with auth error, delete token, so we don't keep trying to refresh in future | ||
await _classPrivateFieldLooseBase(this, _removeAuthToken)[_removeAuthToken](); | ||
} | ||
throw err; | ||
} finally { | ||
_classPrivateFieldLooseBase(this, _refreshingTokenPromise)[_refreshingTokenPromise] = undefined; | ||
} | ||
})(); | ||
} | ||
await _classPrivateFieldLooseBase(this, _refreshingTokenPromise)[_refreshingTokenPromise]; | ||
// Many provider requests may be starting at once, however refresh token should only be called once. | ||
// Once a refresh token operation has started, we need all other request to wait for this operation (atomically) | ||
_classPrivateFieldLooseBase(this, _refreshingTokenPromise)[_refreshingTokenPromise] = (async () => { | ||
try { | ||
const response = await super.request({ | ||
path: this.refreshTokenUrl(), | ||
method: 'POST' | ||
}); | ||
await this.setAuthToken(response.uppyAuthToken); | ||
} finally { | ||
_classPrivateFieldLooseBase(this, _refreshingTokenPromise)[_refreshingTokenPromise] = undefined; | ||
} | ||
})(); | ||
await _classPrivateFieldLooseBase(this, _refreshingTokenPromise)[_refreshingTokenPromise]; | ||
// now retry the request with our new refresh token | ||
@@ -187,0 +197,0 @@ return super.request(...arguments); |
@@ -15,3 +15,3 @@ 'use strict'; | ||
const packageJson = { | ||
"version": "3.4.0" | ||
"version": "3.4.1" | ||
}; // Remove the trailing slash so we can always safely append /xyz. | ||
@@ -18,0 +18,0 @@ function stripSlash(url) { |
{ | ||
"name": "@uppy/companion-client", | ||
"description": "Client library for communication with Companion. Intended for use in Uppy plugins.", | ||
"version": "3.4.0", | ||
"version": "3.4.1", | ||
"license": "MIT", | ||
@@ -25,8 +25,8 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"@uppy/utils": "^5.5.0", | ||
"@uppy/utils": "^5.5.1", | ||
"namespace-emitter": "^2.0.1" | ||
}, | ||
"devDependencies": { | ||
"@jest/globals": "^29.0.0" | ||
"vitest": "^0.34.5" | ||
} | ||
} |
@@ -164,19 +164,30 @@ 'use strict' | ||
// throw Object.assign(new Error(), { isAuthError: true }) // testing simulate access token expired (to refresh token) | ||
// A better way to test this is for example with Google Drive: | ||
// While uploading, go to your google account settings, | ||
// "Third-party apps & services", then click "Companion" and "Remove access". | ||
return await super.request(...args) | ||
} catch (err) { | ||
if (!err.isAuthError) throw err // only handle auth errors (401 from provider) | ||
// only handle auth errors (401 from provider), and only handle them if we have a (refresh) token | ||
if (!err.isAuthError || !(await this.#getAuthToken())) throw err | ||
await this.#refreshingTokenPromise | ||
if (this.#refreshingTokenPromise == null) { | ||
// Many provider requests may be starting at once, however refresh token should only be called once. | ||
// Once a refresh token operation has started, we need all other request to wait for this operation (atomically) | ||
this.#refreshingTokenPromise = (async () => { | ||
try { | ||
const response = await super.request({ path: this.refreshTokenUrl(), method: 'POST' }) | ||
await this.setAuthToken(response.uppyAuthToken) | ||
} catch (refreshTokenErr) { | ||
if (refreshTokenErr.isAuthError) { | ||
// if refresh-token has failed with auth error, delete token, so we don't keep trying to refresh in future | ||
await this.#removeAuthToken() | ||
} | ||
throw err | ||
} finally { | ||
this.#refreshingTokenPromise = undefined | ||
} | ||
})() | ||
} | ||
// Many provider requests may be starting at once, however refresh token should only be called once. | ||
// Once a refresh token operation has started, we need all other request to wait for this operation (atomically) | ||
this.#refreshingTokenPromise = (async () => { | ||
try { | ||
const response = await super.request({ path: this.refreshTokenUrl(), method: 'POST' }) | ||
await this.setAuthToken(response.uppyAuthToken) | ||
} finally { | ||
this.#refreshingTokenPromise = undefined | ||
} | ||
})() | ||
await this.#refreshingTokenPromise | ||
@@ -183,0 +194,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { describe, it, expect } from '@jest/globals' | ||
import { describe, it, expect } from 'vitest' | ||
import RequestClient from './RequestClient.js' | ||
@@ -3,0 +3,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { afterEach, beforeEach, jest, describe, it, expect } from '@jest/globals' | ||
import { afterEach, beforeEach, vi, describe, it, expect } from 'vitest' | ||
import UppySocket from './Socket.js' | ||
@@ -10,5 +10,5 @@ | ||
beforeEach(() => { | ||
webSocketConstructorSpy = jest.fn() | ||
webSocketCloseSpy = jest.fn() | ||
webSocketSendSpy = jest.fn() | ||
webSocketConstructorSpy = vi.fn() | ||
webSocketCloseSpy = vi.fn() | ||
webSocketSendSpy = vi.fn() | ||
@@ -126,3 +126,3 @@ globalThis.WebSocket = class WebSocket { | ||
const emitterListenerMock = jest.fn() | ||
const emitterListenerMock = vi.fn() | ||
uppySocket.on('hi', emitterListenerMock) | ||
@@ -142,3 +142,3 @@ | ||
const emitterListenerMock = jest.fn() | ||
const emitterListenerMock = vi.fn() | ||
uppySocket.on('hi', emitterListenerMock) | ||
@@ -167,3 +167,3 @@ | ||
const emitterListenerMock = jest.fn() | ||
const emitterListenerMock = vi.fn() | ||
uppySocket.once('hi', emitterListenerMock) | ||
@@ -170,0 +170,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
123805
1694
Updated@uppy/utils@^5.5.1