From 4dd40cc04586663a7b9f8805e1baba492175ec9e Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 18 Sep 2023 15:30:49 -0400 Subject: [PATCH] Reduces validation checks --- src/constants.ts | 2 +- src/plus/subscription/subscriptionService.ts | 86 +++++++++++++++++++--------- src/plus/webviews/account/accountWebview.ts | 23 ++++---- src/subscription.ts | 2 + 4 files changed, 76 insertions(+), 37 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 7f0b765..842b00c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -748,7 +748,7 @@ export type GlobalStorage = { pendingWelcomeOnFocus: boolean; pendingWhatsNewOnFocus: boolean; // Don't change this key name ('premium`) as its the stored subscription - 'premium:subscription': Stored; + 'premium:subscription': Stored; 'synced:version': string; // Keep the pre-release version separate from the released version 'synced:preVersion': string; diff --git a/src/plus/subscription/subscriptionService.ts b/src/plus/subscription/subscriptionService.ts index a0e67d2..ff4993c 100644 --- a/src/plus/subscription/subscriptionService.ts +++ b/src/plus/subscription/subscriptionService.ts @@ -99,7 +99,7 @@ export class SubscriptionService implements Disposable { subscription.previewTrial = undefined; } - this.changeSubscription(subscription, true); + this.changeSubscription(subscription, { silent: true }); setTimeout(() => void this.ensureSession(false), 10000); } @@ -130,7 +130,7 @@ export class SubscriptionService implements Disposable { } this._session = session; - void this.validate(); + void this.validate({ force: true }); } private _etag: number = 0; @@ -163,7 +163,7 @@ export class SubscriptionService implements Disposable { registerCommand(Commands.PlusPurchase, () => this.purchase()), registerCommand(Commands.PlusResendVerification, () => this.resendVerification()), - registerCommand(Commands.PlusValidate, () => this.validate()), + registerCommand(Commands.PlusValidate, () => this.validate({ force: true })), registerCommand(Commands.PlusShowPlans, () => this.showPlans()), @@ -382,7 +382,7 @@ export class SubscriptionService implements Disposable { ); if (result === confirm) { - await this.validate(); + await this.validate({ force: true }); return true; } } catch (ex) { @@ -488,7 +488,7 @@ export class SubscriptionService implements Disposable { @gate() @log() - async validate(): Promise { + async validate(options?: { force?: boolean }): Promise { const scope = getLogScope(); const session = await this.ensureSession(false); @@ -498,17 +498,30 @@ export class SubscriptionService implements Disposable { } try { - await this.checkInAndValidate(session); + await this.checkInAndValidate(session, options); } catch (ex) { Logger.error(ex, scope); debugger; } } - private _lastCheckInDate: Date | undefined; + private _lastValidatedDate: Date | undefined; @gate(s => s.account.id) - private async checkInAndValidate(session: AuthenticationSession, showSlowProgress: boolean = false): Promise { - if (!showSlowProgress) return this.checkInAndValidateCore(session); + private async checkInAndValidate( + session: AuthenticationSession, + options?: { force?: boolean; showSlowProgress?: boolean }, + ): Promise { + // Only check in if we haven't in the last 12 hours + if ( + !options?.force && + this._lastValidatedDate != null && + Date.now() - this._lastValidatedDate.getTime() < 12 * 60 * 60 * 1000 && + !isSubscriptionExpired(this._subscription) + ) { + return; + } + + if (!options?.showSlowProgress) return this.checkInAndValidateCore(session); const validating = this.checkInAndValidateCore(session); const result = await Promise.race([ @@ -527,7 +540,7 @@ export class SubscriptionService implements Disposable { } } - @debug({ args: { 0: s => s?.account.label } }) + @debug({ args: { 0: s => s?.account.label } }) private async checkInAndValidateCore(session: AuthenticationSession): Promise { const scope = getLogScope(); @@ -560,8 +573,8 @@ export class SubscriptionService implements Disposable { const data: GKLicenseInfo = await rsp.json(); this.validateSubscription(data); - this._lastCheckInDate = new Date(); } catch (ex) { + this._lastValidatedDate = undefined; Logger.error(ex, scope); debugger; if (ex instanceof AccountValidationError) throw ex; @@ -580,11 +593,11 @@ export class SubscriptionService implements Disposable { // Check 4 times a day to ensure we validate at least once a day this._validationTimer = setInterval( () => { - if (this._lastCheckInDate == null || this._lastCheckInDate.getDate() !== new Date().getDate()) { + if (this._lastValidatedDate == null || this._lastValidatedDate.getDate() !== new Date().getDate()) { void this.ensureSession(false, true); } }, - 1000 * 60 * 60 * 6, + 6 * 60 * 60 * 1000, ); } @@ -665,14 +678,18 @@ export class SubscriptionService implements Disposable { effective = { ...actual }; } - this.changeSubscription({ - ...this._subscription, - plan: { - actual: actual, - effective: effective, + this._lastValidatedDate = new Date(); + this.changeSubscription( + { + ...this._subscription, + plan: { + actual: actual, + effective: effective, + }, + account: account, }, - account: account, - }); + { store: true }, + ); } private _sessionPromise: Promise | undefined; @@ -681,6 +698,10 @@ export class SubscriptionService implements Disposable { @gate() @debug() private async ensureSession(createIfNeeded: boolean, force?: boolean): Promise { + if (force) { + this._lastValidatedDate = undefined; + } + if (this._sessionPromise != null && this._session === undefined) { void (await this._sessionPromise); } @@ -737,7 +758,7 @@ export class SubscriptionService implements Disposable { } try { - await this.checkInAndValidate(session, createIfNeeded); + await this.checkInAndValidate(session, { showSlowProgress: createIfNeeded }); } catch (ex) { Logger.error(ex, scope); debugger; @@ -802,7 +823,7 @@ export class SubscriptionService implements Disposable { @debug() private changeSubscription( subscription: Optional | undefined, - silent: boolean = false, + options?: { silent?: boolean; store?: boolean }, ): void { if (subscription == null) { subscription = { @@ -855,7 +876,12 @@ export class SubscriptionService implements Disposable { const matches = previous != null && JSON.stringify(previous) === JSON.stringify(subscription); // If the previous and new subscriptions are exactly the same, kick out - if (matches) return; + if (matches) { + if (options?.store) { + void this.storeSubscription(subscription); + } + return; + } queueMicrotask(() => { let data = flattenSubscription(subscription); @@ -874,7 +900,7 @@ export class SubscriptionService implements Disposable { this._subscription = subscription; this._etag = Date.now(); - if (!silent) { + if (!options?.silent) { this.updateContext(); if (previous != null) { @@ -886,7 +912,15 @@ export class SubscriptionService implements Disposable { private getStoredSubscription(): Subscription | undefined { const storedSubscription = this.container.storage.get('premium:subscription'); - const subscription = storedSubscription?.data; + let lastValidatedAt: number | undefined; + let subscription: Subscription | undefined; + if (storedSubscription?.data != null) { + ({ lastValidatedAt, ...subscription } = storedSubscription.data); + this._lastValidatedDate = lastValidatedAt != null ? new Date(lastValidatedAt) : undefined; + } else { + subscription = undefined; + } + if (subscription != null) { // Migrate the plan names to the latest names (subscription.plan.actual as Mutable).name = getSubscriptionPlanName( @@ -903,7 +937,7 @@ export class SubscriptionService implements Disposable { private async storeSubscription(subscription: Subscription): Promise { return this.container.storage.store('premium:subscription', { v: 1, - data: subscription, + data: { ...subscription, lastValidatedAt: this._lastValidatedDate?.getTime() }, }); } diff --git a/src/plus/webviews/account/accountWebview.ts b/src/plus/webviews/account/accountWebview.ts index aa9b945..0abd69f 100644 --- a/src/plus/webviews/account/accountWebview.ts +++ b/src/plus/webviews/account/accountWebview.ts @@ -1,7 +1,6 @@ import { Disposable, window } from 'vscode'; import { getAvatarUriFromGravatarEmail } from '../../../avatars'; import type { Container } from '../../../container'; -import type { RepositoriesVisibility } from '../../../git/gitProviderService'; import type { Subscription } from '../../../subscription'; import { registerCommand } from '../../../system/command'; import type { Deferrable } from '../../../system/function'; @@ -30,7 +29,16 @@ export class AccountWebviewProvider implements WebviewProvider { } registerCommands(): Disposable[] { - return [registerCommand(`${this.host.id}.refresh`, () => this.host.refresh(true), this)]; + return [ + registerCommand( + `${this.host.id}.refresh`, + async () => { + await this.validateSubscriptionCore(true); + await this.host.refresh(true); + }, + this, + ), + ]; } includeBootstrap(): Promise { @@ -59,11 +67,6 @@ export class AccountWebviewProvider implements WebviewProvider { queueMicrotask(() => void this.validateSubscription()); } - private async getRepoVisibility(): Promise { - const visibility = await this.container.git.visibility(); - return visibility; - } - private async getSubscription(subscription?: Subscription) { const sub = subscription ?? (await this.container.subscription.getSubscription(true)); @@ -113,9 +116,9 @@ export class AccountWebviewProvider implements WebviewProvider { } private _validating: Promise | undefined; - private async validateSubscriptionCore() { - if (this._validating == null) { - this._validating = this.container.subscription.validate(); + private async validateSubscriptionCore(force?: boolean) { + if (this._validating == null || force) { + this._validating = this.container.subscription.validate({ force: force }); try { await this._validating; } finally { diff --git a/src/subscription.ts b/src/subscription.ts index 90f1065..c271971 100644 --- a/src/subscription.ts +++ b/src/subscription.ts @@ -22,6 +22,8 @@ export interface Subscription { previewTrial?: SubscriptionPreviewTrial; state: SubscriptionState; + + lastValidatedAt?: number; } export interface SubscriptionPlan {