interface: extract GcpManager

Now the API is just an API. (I did still have it produce an expiry time,
since it makes the refresh loop easier to do.)

Not yet storing the token now.
This commit is contained in:
J 2021-02-25 00:29:28 +00:00
parent 3c2ce636e7
commit 9fa086e0d3
3 changed files with 111 additions and 35 deletions

View File

@ -4,40 +4,20 @@ import {GcpToken} from '../types/gcp-state';
export default class GcpApi extends BaseApi<StoreState> {
#running = false;
// Return value resolves to the token's expiry time if successful.
refreshToken() {
return this.spider('noun', 'gcp-token', 'get-gcp-token', {})
.then((token) => {
this.store.handleEvent({
data: token
});
startRefreshLoop() {
if (this.#running) {
console.error('GcpApi startRefreshLoop: already running');
} else {
this.#running = true;
this.refreshLoop();
if (token['gcp-token'] !== undefined &&
token['gcp-token']['expiresIn'] !== undefined &&
typeof(token['gcp-token']['expiresIn']) === 'number') {
return Promise.resolve(token['gcp-token']['expiresIn']);
}
}
private refreshLoop() {
console.log("GcpApi refreshLoop");
this.refreshToken().then(({accessKey, expiresIn}: GcpToken) => {
console.log("GcpApi new token");
// XX maybe bad?
this.store.state.gcp.accessKey = accessKey;
const timeout = this.refreshInterval(expiresIn);
console.log("GcpApi refreshing in", timeout);
setTimeout(() => {
this.refreshLoop();
}, timeout);
return Promise.reject({reason: 'invalid token'});
});
}
private refreshInterval(expiresIn: number) {
// Give ourselves 30 seconds for processing delays, but never refresh
// sooner than 10 minute from now. (The expiry window should be about an
// hour.)
return Math.max(600_000, expiresIn - 30_000);
}
private async refreshToken() {
const token = await this.spider('noun', 'gcp-token', 'get-gcp-token', {});
return token['gcp-token'];
}
};

View File

@ -0,0 +1,94 @@
// Singleton that manages GCP token state.
//
// To use:
//
// 1. call setApi with a GlobalApi.
// 2. call start() to start the token refresh loop.
//
//
import GlobalApi from '../api/global';
class GcpManager {
#api: GlobalApi | null = null;
setApi(api: GlobalApi) {
this.#api = api;
}
#running = false;
#timeoutId: number | null = null;
start() {
if (this.#running) {
console.warn('GcpManager already running');
return;
}
if (!this.#api) {
console.error('GcpManager must have api set');
return;
}
this.#running = true;
this.refreshLoop();
}
stop() {
if (!this.#running) {
console.warn('GcpManager already stopped');
console.assert(this.#timeoutId === null);
return;
}
this.#running = false;
if (this.#timeoutId !== null) {
clearTimeout(this.#timeoutId);
this.#timeoutId = null;
}
}
restart() {
if (this.#running)
this.stop();
this.start();
}
private refreshLoop() {
console.log('GcpManager refreshing token');
this.#api.gcp.refreshToken()
.then(
(expiresIn: number) => {
const interval = this.refreshInterval(expiresIn);
this.refreshAfter(interval);
})
.catch(
({reason}) => {
console.error('GcpManager token refresh failed', reason);
this.refreshAfter(10_000); // XX backoff?
});
}
private refreshAfter(durationMs) {
if (!this.#running)
return;
if (this.#timeoutId !== null) {
console.warn('GcpManager already has a timeout set');
return;
}
console.log('GcpManager refreshing after', durationMs, 'ms');
this.#timeoutId = setTimeout(() => {
this.#timeoutId = null;
this.refreshLoop();
}, durationMs);
}
private refreshInterval(expiresIn: number) {
// Give ourselves 30 seconds for processing delays, but never refresh
// sooner than 30 minutes from now. (The expiry window should be about an
// hour.)
return Math.max(30 * 60_000, expiresIn - 30_000);
}
}
const instance = new GcpManager();
Object.freeze(instance);
export default instance;

View File

@ -27,6 +27,7 @@ import GlobalSubscription from '~/logic/subscription/global';
import GlobalApi from '~/logic/api/global';
import { uxToHex } from '~/logic/lib/util';
import { foregroundFromBackground } from '~/logic/lib/sigil';
import gcpManager from '~/logic/lib/gcpManager';
import { withLocalState } from '~/logic/state/local';
@ -78,6 +79,7 @@ class App extends React.Component {
this.appChannel = new window.channel();
this.api = new GlobalApi(this.ship, this.appChannel, this.store);
gcpManager.setApi(this.api);
this.subscription =
new GlobalSubscription(this.store, this.api, this.appChannel);
@ -94,7 +96,7 @@ class App extends React.Component {
// before the app has actually rendered, hence the timeout.
this.updateTheme(this.themeWatcher);
}, 500);
this.api.gcp.startRefreshLoop();
gcpManager.start();
this.api.local.getBaseHash();
this.api.settings.getAll();
this.store.rehydrate();