1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Clipper: Fixed auth mechanism when extension popup is closed before process is finished

This commit is contained in:
Laurent Cozic 2021-06-28 11:39:31 +01:00
parent 1476cdf467
commit 55248ed08b
2 changed files with 61 additions and 25 deletions

View File

@ -237,12 +237,18 @@ class AppComponent extends Component {
renderStartupScreen() {
const messages = {
serverFoundState: {
// We need to display the "Connecting to the Joplin
// application..." message because if the app doesn't currently
// allow access to the clipper API, the clipper tries several
// ports and it takes time before failing. So if we don't
// display any message, it looks like it's not doing anything
// when clicking on the extension button.
'searching': 'Connecting to the Joplin application...',
'not_found': 'Error: Could not connect to the Joplin application. Please ensure that it is started and that the clipper service is enabled in the configuration.',
},
authState: {
'starting': 'Starting...',
'waiting': 'The Joplin Web Clipper requires your authorisation in order to access your data. To do, please open the Joplin desktop application and grant permission. Note: Joplin 2.1+ is needed to use this version of the Web Clipper.',
'waiting': 'The Joplin Web Clipper requires your authorisation in order to access your data. To proceed, please open the Joplin desktop application and grant permission. Note: Joplin 2.1+ is needed to use this version of the Web Clipper.',
'rejected': 'Permission to access your data was not granted. To try again please close this popup and open it again.',
},
};

View File

@ -85,7 +85,13 @@ class Bridge {
type: 'ENV_SET',
env: this.env(),
});
}
token() {
return this.token_;
}
async onReactAppStarts() {
await this.findClipperServerPort();
if (this.clipperServerPortStatus_ !== 'found') {
@ -94,13 +100,7 @@ class Bridge {
}
await this.restoreState();
}
token() {
return this.token_;
}
async onReactAppStarts() {
await this.checkAuth();
if (!this.token_) return; // Didn't get a token
@ -138,29 +138,59 @@ class Bridge {
this.dispatch({ type: 'AUTH_STATE_SET', value: 'waiting' });
const response = await this.clipperApiExec('POST', 'auth');
const authToken = response.auth_token;
// Note that Firefox and Chrome works differently for this:
//
// - In Chrome, the popup stays open, even when the user leaves the
// browser to grant permission in the application.
//
// - In Firefox, as soon as the browser loses focus, the popup closes.
//
// It means we can't rely on local state to get this working - instead
// we request the auth token, and cache it to local storage (along
// with a timestamp). Then next time the user opens the popup (after
// it was automatically closed by Firefox), that cached auth token is
// re-used and the auth process continues.
//
// https://github.com/laurent22/joplin/issues/5125#issuecomment-869547421
const existingAuthInfo = await this.storageGet(['authToken', 'authTokenTimestamp']);
let authToken = null;
if (existingAuthInfo.authToken && Date.now() - existingAuthInfo.authTokenTimestamp < 5 * 60 * 1000) {
console.info('checkAuth: we already have an auth token - reusing it');
authToken = existingAuthInfo.authToken;
} else {
console.info('checkAuth: we do not have an auth token - requesting it...');
const response = await this.clipperApiExec('POST', 'auth');
authToken = response.auth_token;
await this.storageSet({ authToken: authToken, authTokenTimestamp: Date.now() });
}
console.info('checkAuth: we do not have a token - requesting one using auth_token: ', authToken);
while (true) {
const response = await this.clipperApiExec('GET', 'auth/check', { auth_token: authToken });
try {
while (true) {
const response = await this.clipperApiExec('GET', 'auth/check', { auth_token: authToken });
if (response.status === 'rejected') {
console.info('checkAuth: Auth request was not accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'rejected' });
break;
} else if (response.status === 'accepted') {
console.info('checkAuth: Auth request was accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'accepted' });
this.token_ = response.token;
await this.storageSet({ token: this.token_ });
break;
} else if (response.status === 'waiting') {
await msleep(1000);
} else {
throw new Error(`Unknown auth/check status: ${response.status}`);
if (response.status === 'rejected') {
console.info('checkAuth: Auth request was not accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'rejected' });
break;
} else if (response.status === 'accepted') {
console.info('checkAuth: Auth request was accepted', response);
this.dispatch({ type: 'AUTH_STATE_SET', value: 'accepted' });
this.token_ = response.token;
await this.storageSet({ token: this.token_ });
break;
} else if (response.status === 'waiting') {
await msleep(1000);
} else {
throw new Error(`Unknown auth/check status: ${response.status}`);
}
}
} finally {
await this.storageSet({ authToken: '', authTokenTimestamp: 0 });
}
}