diff --git a/src/scripture-forge/src/auth/scripture-forge-authentication-provider.model.ts b/src/scripture-forge/src/auth/scripture-forge-authentication-provider.model.ts index 0dccfe2..46074c2 100644 --- a/src/scripture-forge/src/auth/scripture-forge-authentication-provider.model.ts +++ b/src/scripture-forge/src/auth/scripture-forge-authentication-provider.model.ts @@ -71,6 +71,9 @@ type RevokeRefreshTokenRequestBody = { /** Path on extension redirect URL for picking up auth response */ export const AUTH_PATH = '/callback/auth0'; +/** Error message thrown when not logged in when trying to get access token */ +export const NOT_LOGGED_IN_ERROR_MESSAGE = 'Not logged in'; + /** Necessary auth scopes for accessing Slingshot drafts */ const SCOPES = ['openid', 'profile', 'email', 'sf_data', 'offline_access'].join(' '); /** Auth audience for Scripture Forge...? */ @@ -492,7 +495,7 @@ export default class ScriptureForgeAuthenticationProvider implements Dispose { } this.#hasRetrievedAuthTokensFromStorage = true; } - if (!this.#authTokens) throw new Error('Not logged in'); + if (!this.#authTokens) throw new Error(NOT_LOGGED_IN_ERROR_MESSAGE); if (!this.#authTokens.didExpire && this.#authTokens.accessTokenExpireTime > Date.now()) return this.#authTokens.accessToken; diff --git a/src/scripture-forge/src/main.ts b/src/scripture-forge/src/main.ts index abb0b1d..a39b124 100644 --- a/src/scripture-forge/src/main.ts +++ b/src/scripture-forge/src/main.ts @@ -193,7 +193,13 @@ export async function activate(context: ExecutionActivationContext) { // const scriptureForgeApi = new ScriptureForgeSampleApi(authenticationProvider); const scriptureForgeApi = new ScriptureForgeApi(authenticationProvider); - const slingshotPdpef = new SlingshotProjectDataProviderEngineFactory(scriptureForgeApi); + const slingshotPdpef = new SlingshotProjectDataProviderEngineFactory( + scriptureForgeApi, + sessionChangeEmitter.event, + ); + + context.registrations.add(slingshotPdpef); + const slingshotPdpefPromise = papi.projectDataProviders.registerProjectDataProviderEngineFactory( SCRIPTURE_FORGE_SLINGSHOT_PDPF_ID, SLINGSHOT_PROJECT_INTERFACES, diff --git a/src/scripture-forge/src/projects/slingshot-project-data-provider-engine-factory.model.ts b/src/scripture-forge/src/projects/slingshot-project-data-provider-engine-factory.model.ts index d4bd07e..96c00fa 100644 --- a/src/scripture-forge/src/projects/slingshot-project-data-provider-engine-factory.model.ts +++ b/src/scripture-forge/src/projects/slingshot-project-data-provider-engine-factory.model.ts @@ -4,11 +4,12 @@ import { ProjectMetadataWithoutFactoryInfo, } from '@papi/core'; import { logger } from '@papi/backend'; -import { getErrorMessage, Mutex } from 'platform-bible-utils'; +import { Dispose, getErrorMessage, Mutex, PlatformEvent, Unsubscriber } from 'platform-bible-utils'; import SlingshotProjectDataProviderEngine, { SLINGSHOT_PROJECT_INTERFACES, } from './slingshot-project-data-provider-engine.model'; import ScriptureForgeApi, { ScriptureForgeProjectInfo } from './scripture-forge-api.model'; +import { NOT_LOGGED_IN_ERROR_MESSAGE } from '../auth/scripture-forge-authentication-provider.model'; /** * Duration in milliseconds to throttle the `getProjects` API call. We will return the previous @@ -30,7 +31,7 @@ function getSlingshotAppProjectId(projectId: string): string { } export default class SlingshotProjectDataProviderEngineFactory - implements IProjectDataProviderEngineFactory + implements IProjectDataProviderEngineFactory, Dispose { private projectInfoByAppProjectId = new Map(); private pdpeByAppProjectId = new Map(); @@ -38,8 +39,21 @@ export default class SlingshotProjectDataProviderEngineFactory private lastGetProjectsTime: number = 0; private lastAvailableProjects: ProjectMetadataWithoutFactoryInfo[] = []; private getProjectsMutex = new Mutex(); - - constructor(private scriptureForgeApi: ScriptureForgeApi) {} + /** + * If we know we are logged out and therefore shouldn't try to check for projects. If this is + * `false`, it doesn't mean we are logged in but that we need to try getting projects again + */ + private isLoggedOut = false; + private onDidSessionChangeUnsubscriber: Unsubscriber; + + constructor( + private scriptureForgeApi: ScriptureForgeApi, + onDidSessionChange: PlatformEvent, + ) { + this.onDidSessionChangeUnsubscriber = onDidSessionChange(() => { + this.isLoggedOut = false; + }); + } async getAvailableProjects(): Promise { return this.#getAvailableProjects(); @@ -64,6 +78,10 @@ export default class SlingshotProjectDataProviderEngineFactory return pdpe; } + async dispose(): Promise { + return this.onDidSessionChangeUnsubscriber(); + } + /** * Gets available projects from cache or reaches out to get new project info * @@ -71,6 +89,9 @@ export default class SlingshotProjectDataProviderEngineFactory */ async #getAvailableProjects(force = false) { if (force) this.lastGetProjectsTime = 0; + // If we're not forcing and we know we're logged out, return empty array instead of failing + // again because we know we're already logged out + else if (this.isLoggedOut) return []; if (Date.now() - this.lastGetProjectsTime < GET_PROJECTS_THROTTLE_MS) return this.lastAvailableProjects; @@ -86,9 +107,14 @@ export default class SlingshotProjectDataProviderEngineFactory try { projectsInfo = await this.scriptureForgeApi.getProjects(); } catch (e) { + const errorMessage = getErrorMessage(e); logger.warn( - `Slingshot PDPEF caught Scripture Forge API error while getting available projects. ${getErrorMessage(e)}`, + `Slingshot PDPEF caught Scripture Forge API error while getting available projects. ${errorMessage}`, ); + + // Keep track if we're logged out so we don't keep spamming the logs + if (errorMessage === NOT_LOGGED_IN_ERROR_MESSAGE) this.isLoggedOut = true; + return []; }