From a98eb0eb981116c3b77fd4178f819274c7345c8c Mon Sep 17 00:00:00 2001 From: Todd Schiller Date: Tue, 9 Jul 2024 10:02:16 -0400 Subject: [PATCH] Rename `locator` to `integrationConfigLocator` (#8776) --- .secrets.baseline | 4 +- .../varAnalysis/varAnalysis.test.ts | 14 +-- .../auth/launchInteractiveOAuth2Flow.ts | 4 +- .../getPartnerPrincipals.test.ts | 2 +- .../getPartnerPrincipals.ts | 4 +- .../launchAuthIntegration.ts | 5 +- src/background/background.ts | 2 +- src/background/deploymentUpdater.ts | 14 +-- src/background/installer.test.ts | 16 ++-- src/background/installer.ts | 9 +- ...locator.ts => integrationConfigLocator.ts} | 36 +++++--- src/background/messenger/api.ts | 19 +++- src/background/messenger/registration.ts | 54 ++++++----- .../performConfiguredRequest.test.ts | 8 +- src/background/refreshRegistries.ts | 4 +- src/background/refreshToken.test.ts | 8 +- src/background/refreshToken.ts | 2 +- src/background/requests.ts | 6 +- .../integrations/AuthWidget.test.tsx | 8 +- src/components/integrations/AuthWidget.tsx | 6 +- .../automationanywhere/RunApiTask.test.ts | 4 +- .../google/sheets/core/sheetsApi.test.ts | 49 +++++----- .../google/sheets/core/useGoogleAccount.ts | 4 +- .../sheets/core/useSpreadsheetId.test.tsx | 9 +- .../google/sheets/core/useSpreadsheetId.ts | 13 +-- .../ui/AppendSpreadsheetOptions.test.tsx | 11 ++- .../ui/LookupSpreadsheetOptions.test.tsx | 11 ++- .../ui/SpreadsheetPickerWidget.test.tsx | 8 +- .../useCloudExtensionPermissions.ts | 6 +- .../activateMod/IntegrationsBody.test.tsx | 8 +- .../pages/activateMod/useModPermissions.ts | 6 +- .../pages/deployments/DeploymentsContext.tsx | 5 +- .../pages/deployments/activateDeployments.ts | 4 +- .../pages/integrations/IntegrationsPage.tsx | 4 +- .../pages/onboarding/SetupPage.tsx | 4 +- .../partner/ControlRoomTokenForm.tsx | 4 +- .../pages/packageEditor/useSubmitPackage.ts | 4 +- src/hooks/useRefreshRegistries.ts | 16 ++-- src/integrations/autoConfigure.ts | 11 ++- .../RequireIntegrationConfig.test.tsx | 32 ++++--- ...st.ts => integrationConfigLocator.test.ts} | 11 ++- ...locator.ts => integrationConfigLocator.ts} | 92 +++++++++++-------- ...izedIntegrationConfigFormikAdapter.test.ts | 12 +-- ...SanitizedIntegrationConfigFormikAdapter.ts | 9 +- ...indSanitizedIntegrationConfigWithRetry.ts} | 24 +++-- ...makeIntegrationsContextFromDependencies.ts | 4 +- src/integrations/util/permissionsHelpers.ts | 4 +- .../deploymentPermissionsHelpers.ts | 4 +- .../pipelineTests/integrationsContext.test.ts | 38 ++++---- .../pipelineTests/validateInput.test.ts | 6 +- .../activateMod/ActivateModPanel.test.tsx | 8 +- src/tsconfig.strictNullChecks.json | 8 +- src/utils/deploymentUtils.ts | 6 +- 53 files changed, 382 insertions(+), 282 deletions(-) rename src/background/{locator.ts => integrationConfigLocator.ts} (62%) rename src/integrations/{locator.test.ts => integrationConfigLocator.test.ts} (90%) rename src/integrations/{locator.ts => integrationConfigLocator.ts} (74%) rename src/integrations/util/{locateSanitizedIntegrationConfigWithRetry.ts => findSanitizedIntegrationConfigWithRetry.ts} (76%) diff --git a/.secrets.baseline b/.secrets.baseline index 3d7d732728..fa5bc22669 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -244,7 +244,7 @@ "filename": "src/runtime/pipelineTests/validateInput.test.ts", "hashed_secret": "0ba9927dc8deb33a61c711371f7ace3407591191", "is_verified": false, - "line_number": 274 + "line_number": 276 } ], "src/testUtils/factories/authFactories.ts": [ @@ -264,5 +264,5 @@ } ] }, - "generated_at": "2024-06-20T16:25:50Z" + "generated_at": "2024-07-08T21:14:43Z" } diff --git a/src/analysis/analysisVisitors/varAnalysis/varAnalysis.test.ts b/src/analysis/analysisVisitors/varAnalysis/varAnalysis.test.ts index 9087aa829a..8f1a9ebffd 100644 --- a/src/analysis/analysisVisitors/varAnalysis/varAnalysis.test.ts +++ b/src/analysis/analysisVisitors/varAnalysis/varAnalysis.test.ts @@ -39,7 +39,7 @@ import { type ListElement, } from "@/pageEditor/documentBuilder/documentBuilderTypes"; import { type Schema } from "@/types/schemaTypes"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { modMetadataFactory } from "@/testUtils/factories/modComponentFactories"; import { formStateFactory, @@ -58,11 +58,13 @@ import IdentityTransformer from "@/bricks/transformers/IdentityTransformer"; import { createNewConfiguredBrick } from "@/bricks/exampleBrickConfigs"; import pixiebrixIntegrationDependencyFactory from "@/integrations/util/pixiebrixIntegrationDependencyFactory"; -jest.mocked(services.locate).mockResolvedValue( - sanitizedIntegrationConfigFactory({ - serviceId: validateRegistryId("@test/service"), - }), -); +jest + .mocked(integrationConfigLocator.findSanitizedIntegrationConfig) + .mockResolvedValue( + sanitizedIntegrationConfigFactory({ + serviceId: validateRegistryId("@test/service"), + }), + ); // XXX: should be using actual bricks instead of a single outputSchema across all tests in order to test // different outputSchema scenarios diff --git a/src/background/auth/launchInteractiveOAuth2Flow.ts b/src/background/auth/launchInteractiveOAuth2Flow.ts index a6cf91d4c5..ab5082cf32 100644 --- a/src/background/auth/launchInteractiveOAuth2Flow.ts +++ b/src/background/auth/launchInteractiveOAuth2Flow.ts @@ -16,7 +16,7 @@ */ import type { UUID } from "@/types/stringTypes"; -import { locator } from "@/background/locator"; +import { integrationConfigLocator } from "@/background/integrationConfigLocator"; import { BusinessError } from "@/errors/businessErrors"; import integrationRegistry from "@/integrations/registry"; import launchOAuth2Flow from "@/background/auth/launchOAuth2Flow"; @@ -33,7 +33,7 @@ import launchOAuth2Flow from "@/background/auth/launchOAuth2Flow"; // Currently in its own file because launchOAuth2Flow has strict null checks enabled and this file references the // integration registry and configuration locator async function launchInteractiveOAuth2Flow(configId: UUID): Promise { - const config = await locator.findIntegrationConfig(configId); + const config = await integrationConfigLocator.findIntegrationConfig(configId); if (!config) { throw new BusinessError(`Integration config not found: ${configId}`); diff --git a/src/background/auth/partnerIntegrations/getPartnerPrincipals.test.ts b/src/background/auth/partnerIntegrations/getPartnerPrincipals.test.ts index 4d860bab92..d87b639c04 100644 --- a/src/background/auth/partnerIntegrations/getPartnerPrincipals.test.ts +++ b/src/background/auth/partnerIntegrations/getPartnerPrincipals.test.ts @@ -19,7 +19,7 @@ import { appApiMock } from "@/testUtils/appApiMock"; import tokenIntegrationDefinition from "@contrib/integrations/automation-anywhere.yaml"; import oauthIntegrationDefinition from "@contrib/integrations/automation-anywhere-oauth2.yaml"; import { syncRemotePackages } from "@/registry/memoryRegistry"; -import { locator as serviceLocator } from "@/background/locator"; +import { integrationConfigLocator as serviceLocator } from "@/background/integrationConfigLocator"; import { getPartnerPrincipals } from "@/background/auth/partnerIntegrations/getPartnerPrincipals"; import { integrationConfigFactory, diff --git a/src/background/auth/partnerIntegrations/getPartnerPrincipals.ts b/src/background/auth/partnerIntegrations/getPartnerPrincipals.ts index e910aef87d..38b9c660bf 100644 --- a/src/background/auth/partnerIntegrations/getPartnerPrincipals.ts +++ b/src/background/auth/partnerIntegrations/getPartnerPrincipals.ts @@ -22,7 +22,7 @@ import { CONTROL_ROOM_TOKEN_INTEGRATION_ID, } from "@/integrations/constants"; import { compact, flatten } from "lodash"; -import { locator as serviceLocator } from "@/background/locator"; +import { integrationConfigLocator as serviceLocator } from "@/background/integrationConfigLocator"; import { canParseUrl } from "@/utils/urlUtils"; /** @@ -40,7 +40,7 @@ export async function getPartnerPrincipals(): Promise { await Promise.all( partnerIds.map(async (id) => { try { - return await serviceLocator.locateAllForService(id); + return await serviceLocator.findAllSanitizedConfigsForIntegration(id); } catch { // `serviceLocator` throws if the user doesn't have the service definition. Handle case where the brick // definition for CONTROL_ROOM_OAUTH_SERVICE_ID hasn't been made available on the server yet diff --git a/src/background/auth/partnerIntegrations/launchAuthIntegration.ts b/src/background/auth/partnerIntegrations/launchAuthIntegration.ts index 836249cad3..b64f087433 100644 --- a/src/background/auth/partnerIntegrations/launchAuthIntegration.ts +++ b/src/background/auth/partnerIntegrations/launchAuthIntegration.ts @@ -18,7 +18,7 @@ import type { RegistryId } from "@/types/registryTypes"; import { expectContext } from "@/utils/expectContext"; import serviceRegistry from "@/integrations/registry"; -import { locator as serviceLocator } from "@/background/locator"; +import { integrationConfigLocator as serviceLocator } from "@/background/integrationConfigLocator"; import { assertNotNullish } from "@/utils/nullishUtils"; import launchOAuth2Flow from "@/background/auth/launchOAuth2Flow"; import { CONTROL_ROOM_OAUTH_INTEGRATION_ID } from "@/integrations/constants"; @@ -47,7 +47,8 @@ export async function launchAuthIntegration({ const integration = await serviceRegistry.lookup(integrationId); await serviceLocator.refreshLocal(); - const allAuths = await serviceLocator.locateAllForService(integrationId); + const allAuths = + await serviceLocator.findAllSanitizedConfigsForIntegration(integrationId); const localAuths = allAuths.filter((x) => !x.proxy); if (localAuths.length === 0) { diff --git a/src/background/background.ts b/src/background/background.ts index 68203f9347..6c628078c1 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -28,7 +28,7 @@ import "@/background/axiosFetch"; import { initMessengerLogging } from "@/development/messengerLogging"; import registerMessenger from "@/background/messenger/registration"; import registerExternalMessenger from "@/background/messenger/external/registration"; -import initLocator from "@/background/locator"; +import initLocator from "@/background/integrationConfigLocator"; import initContextMenus from "@/background/contextMenus/initContextMenus"; import initBrowserAction from "@/background/browserAction"; import initInstaller from "@/background/installer"; diff --git a/src/background/deploymentUpdater.ts b/src/background/deploymentUpdater.ts index 412648a59a..d08d21d00f 100644 --- a/src/background/deploymentUpdater.ts +++ b/src/background/deploymentUpdater.ts @@ -54,7 +54,6 @@ import { } from "@/utils/deploymentUtils"; import { selectUpdatePromptState } from "@/store/settings/settingsSelectors"; import settingsSlice from "@/store/settings/settingsSlice"; -import { locator } from "@/background/locator"; import { getEditorState, saveEditorState } from "@/store/editorStorage"; import { type EditorState } from "@/pageEditor/store/editor/pageEditorTypes"; import { editorSlice } from "@/pageEditor/store/editor/editorSlice"; @@ -73,7 +72,7 @@ import { allSettled } from "@/utils/promiseUtils"; import type { Manifest } from "webextension-polyfill"; import { getRequestHeadersByAPIVersion } from "@/data/service/apiVersioning"; import { fetchDeploymentModDefinitions } from "@/modDefinitions/modDefinitionRawApiCalls"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import type { ActivatableDeployment } from "@/types/deploymentTypes"; import { isAxiosError } from "@/errors/networkErrorHelpers"; import type { components } from "@/types/swagger"; @@ -89,7 +88,10 @@ const { reducer: optionsReducer, actions: optionsActions } = extensionsSlice; const { reducer: editorReducer, actions: editorActions } = editorSlice; // eslint-disable-next-line local-rules/persistBackgroundData -- Function -const locateAllForService = locator.locateAllForService.bind(locator); +const findAllSanitizedIntegrationConfigs = + integrationConfigLocator.findAllSanitizedConfigsForIntegration.bind( + integrationConfigLocator, + ); /** * Heartbeat frequency for reporting/checking deployments from the server. @@ -319,7 +321,7 @@ async function activateDeployment({ deployment, configuredDependencies: await mergeDeploymentIntegrationDependencies( activatableDeployment, - services.locateAllForId, + integrationConfigLocator.findAllSanitizedConfigsForIntegration, ), // Assume backend properly validates the options optionsArgs: deployment.options_config as OptionsArgs, @@ -397,7 +399,7 @@ async function canAutoActivate({ const personalConfigs = await findLocalDeploymentConfiguredIntegrationDependencies( activatableDeployment, - locateAllForService, + findAllSanitizedIntegrationConfigs, ); return personalConfigs.every(({ configs }) => configs.length === 1); } @@ -682,7 +684,7 @@ async function activateDeploymentsInBackground({ activatableDeployment, ...(await checkDeploymentPermissions({ activatableDeployment, - locate: locateAllForService, + locate: findAllSanitizedIntegrationConfigs, optionalPermissions, })), })), diff --git a/src/background/installer.test.ts b/src/background/installer.test.ts index e078e222a9..c1652d5315 100644 --- a/src/background/installer.test.ts +++ b/src/background/installer.test.ts @@ -23,7 +23,7 @@ import { setAvailableVersion, } from "@/background/installer"; import * as auth from "@/auth/authStorage"; -import { locator } from "@/background/locator"; +import { integrationConfigLocator } from "@/background/integrationConfigLocator"; import { uuidv4 } from "@/types/helpers"; import { waitForEffect } from "@/testUtils/testHelpers"; import { INTERNAL_reset as resetManagedStorage } from "@/store/enterprise/managedStorage"; @@ -45,9 +45,9 @@ jest.mock("@/auth/authStorage", () => ({ jest.mock("@/background/telemetry"); -jest.mock("@/background/locator", () => ({ - locator: { - locateAllForService: jest.fn().mockResolvedValue([]), +jest.mock("@/background/integrationConfigLocator", () => ({ + integrationConfigLocator: { + findAllSanitizedConfigsForIntegration: jest.fn().mockResolvedValue([]), }, })); @@ -57,7 +57,9 @@ const queryTabsMock = jest.mocked(browser.tabs.query); const isLinkedMock = jest.mocked(auth.isLinked); const getExtensionTokenMock = jest.mocked(auth.getExtensionToken); const getUserData = jest.mocked(auth.getUserData); -const locateAllForServiceMock = jest.mocked(locator.locateAllForService); +const findAllSanitizedConfigsForIntegrationMock = jest.mocked( + integrationConfigLocator.findAllSanitizedConfigsForIntegration, +); const browserManagedStorageMock = jest.mocked(browser.storage.managed.get); beforeEach(async () => { @@ -159,7 +161,7 @@ describe("checkPartnerAuth", () => { queryTabsMock.mockResolvedValue([]); isLinkedMock.mockResolvedValue(true); getExtensionTokenMock.mockResolvedValue("abc123"); - locateAllForServiceMock.mockResolvedValue([ + findAllSanitizedConfigsForIntegrationMock.mockResolvedValue([ // Include a cloud configuration to clarify that local integration is still required { id: uuidv4(), serviceId: "automation-anywhere", proxy: true } as any, ]); @@ -203,7 +205,7 @@ describe("checkPartnerAuth", () => { queryTabsMock.mockResolvedValue([]); isLinkedMock.mockResolvedValue(true); getExtensionTokenMock.mockResolvedValue("abc123"); - locateAllForServiceMock.mockResolvedValue([ + findAllSanitizedConfigsForIntegrationMock.mockResolvedValue([ { id: uuidv4(), serviceId: "automation-anywhere" } as any, ]); getUserData.mockResolvedValue({ diff --git a/src/background/installer.ts b/src/background/installer.ts index 933584168d..1cd4645d9e 100644 --- a/src/background/installer.ts +++ b/src/background/installer.ts @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import { locator as serviceLocator } from "@/background/locator"; +import { integrationConfigLocator as serviceLocator } from "@/background/integrationConfigLocator"; import { type Runtime } from "webextension-polyfill"; import { initTelemetry, recordEvent } from "@/background/telemetry"; import { getUUID } from "@/telemetry/telemetryHelpers"; @@ -195,9 +195,10 @@ export async function requirePartnerAuth(): Promise { console.debug("requirePartnerAuth", userData); if (userData.partner?.partnerTheme === "automation-anywhere") { - const configs = await serviceLocator.locateAllForService( - CONTROL_ROOM_TOKEN_INTEGRATION_ID, - ); + const configs = + await serviceLocator.findAllSanitizedConfigsForIntegration( + CONTROL_ROOM_TOKEN_INTEGRATION_ID, + ); if (!configs.some((x) => !x.proxy)) { const extensionConsoleUrl = getExtensionConsoleUrl(); diff --git a/src/background/locator.ts b/src/background/integrationConfigLocator.ts similarity index 62% rename from src/background/locator.ts rename to src/background/integrationConfigLocator.ts index 919f095457..c832b7c59a 100644 --- a/src/background/locator.ts +++ b/src/background/integrationConfigLocator.ts @@ -15,41 +15,44 @@ * along with this program. If not, see . */ -import LazyLocatorFactory from "@/integrations/locator"; +import IntegrationConfigLocator from "@/integrations/integrationConfigLocator"; import { expectContext } from "@/utils/expectContext"; import { memoizeUntilSettled } from "@/utils/promiseUtils"; -export const locator = new LazyLocatorFactory(); +/** + * Singleton IntegrationConfigLocator instance. + */ +export const integrationConfigLocator = new IntegrationConfigLocator(); export default async function initLocator() { - // Service locator cannot run in contentScript due to CSP and wanting to isolate local secrets. + // IntegrationConfigLocator cannot run in contentScript due to CSP and wanting to isolate local secrets. // Force use of background page to ensure there's a singleton locator instance across all frames/pages. expectContext( "background", - "The service locator must run in the background worker", + "The integration configuration locator must run in the background worker", ); - console.debug("Eagerly initializing service locator"); - await locator.refresh(); + console.debug("Eagerly initializing integration configuration locator"); + await integrationConfigLocator.refresh(); } -async function _refreshServices({ +async function _refreshIntegrationConfigs({ local = true, remote = true, } = {}): Promise { - // Service locator cannot run in contentScript due to CSP and wanting to isolate local secrets. + // Integration configuration locator cannot run in contentScript due to CSP and wanting to isolate local secrets. // Force use of background page to ensure there's a singleton locator instance across all frames/pages. expectContext( "background", - "The service locator must run in the background worker", + "The integration configuration locator must run in the background worker", ); if (remote && local) { - await locator.refresh(); + await integrationConfigLocator.refresh(); } else if (remote) { - await locator.refreshRemote(); + await integrationConfigLocator.refreshRemote(); } else if (local) { - await locator.refreshLocal(); + await integrationConfigLocator.refreshLocal(); } else { // Prevent buggy call sites from silently causing issues throw new Error("Either local or remote must be set to true"); @@ -62,6 +65,9 @@ async function _refreshServices({ */ // Memoize while running, because multiple elements on the page might be trying to refresh services. But can't // memoize completely, as that would prevent future refreshes -export const refreshServices = memoizeUntilSettled(_refreshServices, { - cacheKey: JSON.stringify, -}); +export const refreshIntegrationConfigs = memoizeUntilSettled( + _refreshIntegrationConfigs, + { + cacheKey: JSON.stringify, + }, +); diff --git a/src/background/messenger/api.ts b/src/background/messenger/api.ts index 68bff64c2d..153c4c1b02 100644 --- a/src/background/messenger/api.ts +++ b/src/background/messenger/api.ts @@ -79,10 +79,16 @@ export const fetchFeatureFlagsInBackground = getMethod( bg, ); -export const services = { - locateAllForId: getMethod("LOCATE_SERVICES_FOR_ID", bg), - locate: getMethod("LOCATE_SERVICE", bg), - refresh: getMethod("REFRESH_SERVICES", bg), +export const integrationConfigLocator = { + findAllSanitizedConfigsForIntegration: getMethod( + "LOCATOR_FIND_ALL_SANITIZED_CONFIGS_FOR_INTEGRATION", + bg, + ), + findSanitizedIntegrationConfig: getMethod( + "LOCATOR_FIND_SANITIZED_INTEGRATION_CONFIG", + bg, + ), + refresh: getMethod("LOCATOR_REFRESH", bg), refreshLocal: getMethod("LOCATOR_REFRESH_LOCAL", bg), }; @@ -147,7 +153,10 @@ export const removeModComponentForEveryTab = getNotifier( "REMOVE_MOD_COMPONENT_EVERY_TAB", bg, ); -export const clearServiceCache = getMethod("CLEAR_SERVICE_CACHE", bg); +export const clearIntegrationRegistry = getMethod( + "INTEGRATION_REGISTRY_CLEAR", + bg, +); export const getUserData = getMethod("GET_USER_DATA", bg); export const installStarterBlueprints = getMethod( "INSTALL_STARTER_BLUEPRINTS", diff --git a/src/background/messenger/registration.ts b/src/background/messenger/registration.ts index 0122e00c84..f63cbad8ba 100644 --- a/src/background/messenger/registration.ts +++ b/src/background/messenger/registration.ts @@ -37,8 +37,8 @@ import { import { setToolbarBadge } from "@/background/toolbarBadge"; import { rememberFocus } from "@/utils/focusTracker"; import writeToClipboardInFocusedContext from "@/background/clipboard"; -import * as registry from "@/registry/packageRegistry"; -import serviceRegistry from "@/integrations/registry"; +import * as packageRegistry from "@/registry/packageRegistry"; +import integrationRegistry from "@/integrations/registry"; import { getUserData } from "@/auth/authStorage"; import { clearExtensionDebugLogs, @@ -48,7 +48,10 @@ import { recordLog, } from "@/telemetry/logging"; import { fetchFeatureFlags } from "@/auth/featureFlagStorage"; -import { locator, refreshServices } from "@/background/locator"; +import { + integrationConfigLocator, + refreshIntegrationConfigs, +} from "@/background/integrationConfigLocator"; import { closeTab, focusTab, openTab } from "@/background/tabs"; import launchInteractiveOAuth2Flow from "@/background/auth/launchInteractiveOAuth2Flow"; import { performConfiguredRequest } from "@/background/requests"; @@ -95,14 +98,13 @@ declare global { SET_TOOLBAR_BADGE: typeof setToolbarBadge; DOCUMENT_RECEIVED_FOCUS: typeof rememberFocus; WRITE_TO_CLIPBOARD_IN_FOCUSED_DOCUMENT: typeof writeToClipboardInFocusedContext; - REGISTRY_SYNC: typeof registry.syncPackages; - REGISTRY_CLEAR: typeof registry.clear; - REGISTRY_GET_BY_KINDS: typeof registry.getByKinds; - REGISTRY_FIND: typeof registry.find; + REGISTRY_SYNC: typeof packageRegistry.syncPackages; + REGISTRY_CLEAR: typeof packageRegistry.clear; + REGISTRY_GET_BY_KINDS: typeof packageRegistry.getByKinds; + REGISTRY_FIND: typeof packageRegistry.find; QUERY_TABS: typeof browser.tabs.query; FETCH_FEATURE_FLAGS: typeof fetchFeatureFlags; - CLEAR_SERVICE_CACHE: typeof serviceRegistry.clear; GET_USER_DATA: typeof getUserData; RECORD_LOG: typeof recordLog; RECORD_ERROR: typeof recordError; @@ -110,10 +112,11 @@ declare global { CLEAR_LOG: typeof clearLog; CLEAR_EXTENSION_DEBUG_LOGS: typeof clearExtensionDebugLogs; - LOCATE_SERVICES_FOR_ID: typeof locator.locateAllForService; - LOCATE_SERVICE: typeof locator.locate; - REFRESH_SERVICES: typeof refreshServices; - LOCATOR_REFRESH_LOCAL: typeof locator.refreshLocal; + INTEGRATION_REGISTRY_CLEAR: typeof integrationRegistry.clear; + LOCATOR_FIND_ALL_SANITIZED_CONFIGS_FOR_INTEGRATION: typeof integrationConfigLocator.findAllSanitizedConfigsForIntegration; + LOCATOR_FIND_SANITIZED_INTEGRATION_CONFIG: typeof integrationConfigLocator.findSanitizedIntegrationConfig; + LOCATOR_REFRESH: typeof refreshIntegrationConfigs; + LOCATOR_REFRESH_LOCAL: typeof integrationConfigLocator.refreshLocal; OPEN_TAB: typeof openTab; CLOSE_TAB: typeof closeTab; @@ -168,14 +171,13 @@ export default function registerMessenger(): void { SET_TOOLBAR_BADGE: setToolbarBadge, DOCUMENT_RECEIVED_FOCUS: rememberFocus, WRITE_TO_CLIPBOARD_IN_FOCUSED_DOCUMENT: writeToClipboardInFocusedContext, - REGISTRY_SYNC: registry.syncPackages, - REGISTRY_CLEAR: registry.clear, - REGISTRY_GET_BY_KINDS: registry.getByKinds, - REGISTRY_FIND: registry.find, + REGISTRY_SYNC: packageRegistry.syncPackages, + REGISTRY_CLEAR: packageRegistry.clear, + REGISTRY_GET_BY_KINDS: packageRegistry.getByKinds, + REGISTRY_FIND: packageRegistry.find, QUERY_TABS: browser.tabs.query, FETCH_FEATURE_FLAGS: fetchFeatureFlags, - CLEAR_SERVICE_CACHE: serviceRegistry.clear.bind(serviceRegistry), GET_USER_DATA: getUserData, RECORD_LOG: recordLog, RECORD_ERROR: recordError, @@ -183,10 +185,20 @@ export default function registerMessenger(): void { CLEAR_LOG: clearLog, CLEAR_EXTENSION_DEBUG_LOGS: clearExtensionDebugLogs, - LOCATE_SERVICES_FOR_ID: locator.locateAllForService.bind(locator), - LOCATE_SERVICE: locator.locate.bind(locator), - LOCATOR_REFRESH_LOCAL: locator.refreshLocal.bind(locator), - REFRESH_SERVICES: refreshServices, + INTEGRATION_REGISTRY_CLEAR: + integrationRegistry.clear.bind(integrationRegistry), + LOCATOR_FIND_ALL_SANITIZED_CONFIGS_FOR_INTEGRATION: + integrationConfigLocator.findAllSanitizedConfigsForIntegration.bind( + integrationConfigLocator, + ), + LOCATOR_FIND_SANITIZED_INTEGRATION_CONFIG: + integrationConfigLocator.findSanitizedIntegrationConfig.bind( + integrationConfigLocator, + ), + LOCATOR_REFRESH_LOCAL: integrationConfigLocator.refreshLocal.bind( + integrationConfigLocator, + ), + LOCATOR_REFRESH: refreshIntegrationConfigs, OPEN_TAB: openTab, CLOSE_TAB: closeTab, diff --git a/src/background/performConfiguredRequest.test.ts b/src/background/performConfiguredRequest.test.ts index f389f6fb75..dcb93b954a 100644 --- a/src/background/performConfiguredRequest.test.ts +++ b/src/background/performConfiguredRequest.test.ts @@ -20,7 +20,7 @@ import axios, { type AxiosError } from "axios"; import MockAdapter from "axios-mock-adapter"; import { performConfiguredRequest } from "./requests"; import * as token from "@/auth/authStorage"; -import Locator from "@/integrations/locator"; +import Locator from "@/integrations/integrationConfigLocator"; import { validateRegistryId } from "@/types/helpers"; import enrichAxiosErrors from "@/utils/enrichAxiosErrors"; import { ContextError } from "@/errors/genericErrors"; @@ -67,7 +67,7 @@ jest.mock("@/background/auth/getToken", () => ({ getToken: jest.fn().mockResolvedValue({ token: "iamatoken" }), })); jest.mock("@/auth/authStorage"); -jest.mock("@/integrations/locator"); +jest.mock("@/integrations/integrationConfigLocator"); enrichAxiosErrors(); @@ -184,7 +184,7 @@ describe("unauthenticated direct requests", () => { describe("authenticated direct requests", () => { beforeEach(() => { jest - .spyOn(Locator.prototype, "locate") + .spyOn(Locator.prototype, "findSanitizedIntegrationConfig") .mockResolvedValue(directIntegrationConfig); jest .spyOn(Locator.prototype, "findIntegrationConfig") @@ -370,7 +370,7 @@ describe("Retry token request", () => { beforeEach(() => { mockGetToken.mockClear(); jest - .spyOn(Locator.prototype, "locate") + .spyOn(Locator.prototype, "findSanitizedIntegrationConfig") .mockResolvedValue(directTokenIntegrationConfig); jest .spyOn(Locator.prototype, "findIntegrationConfig") diff --git a/src/background/refreshRegistries.ts b/src/background/refreshRegistries.ts index 01e4e660fc..3be15c86ae 100644 --- a/src/background/refreshRegistries.ts +++ b/src/background/refreshRegistries.ts @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import { refreshServices } from "./locator"; +import { refreshIntegrationConfigs } from "./integrationConfigLocator"; import { syncRemotePackages } from "@/registry/memoryRegistry"; import { expectContext } from "@/utils/expectContext"; @@ -30,6 +30,6 @@ export async function refreshRegistries(): Promise { await Promise.all([ // Call the @/baseRegistry copy of fetchNewPackages so it invalidates in-memory registries syncRemotePackages(), - refreshServices(), + refreshIntegrationConfigs(), ]); } diff --git a/src/background/refreshToken.test.ts b/src/background/refreshToken.test.ts index 3f916851e3..b1a8584077 100644 --- a/src/background/refreshToken.test.ts +++ b/src/background/refreshToken.test.ts @@ -20,7 +20,7 @@ import { appApiMock } from "@/testUtils/appApiMock"; import { sanitizedIntegrationConfigFactory } from "@/testUtils/factories/integrationFactories"; import { type IntegrationConfig } from "@/integrations/integrationTypes"; import { fromJS } from "@/integrations/UserDefinedIntegration"; -import { locator } from "@/background/locator"; +import { integrationConfigLocator } from "@/background/integrationConfigLocator"; import aaDefinition from "@contrib/integrations/automation-anywhere-oauth2.yaml"; import googleDefinition from "@contrib/integrations/google-oauth2-pkce.yaml"; import greenhouseDefintion from "@contrib/integrations/greenhouse.yaml"; @@ -143,7 +143,7 @@ describe.each([googleIntegration, microsoftIntegration])( config: {}, } as IntegrationConfig, ]); - await locator.refreshLocal(); + await integrationConfigLocator.refreshLocal(); appApiMock.onGet("/api/services/shared/").reply(200, []); appApiMock.onPost().reply(200, { @@ -181,7 +181,7 @@ describe.each([googleIntegration, microsoftIntegration])( config: {}, } as IntegrationConfig, ]); - await locator.refreshLocal(); + await integrationConfigLocator.refreshLocal(); appApiMock.onGet("/api/services/shared/").reply(200, []); appApiMock.onPost().reply(200, { @@ -218,7 +218,7 @@ describe.each([googleIntegration, microsoftIntegration])( config: {}, } as IntegrationConfig, ]); - await locator.refreshLocal(); + await integrationConfigLocator.refreshLocal(); appApiMock.onGet("/api/services/shared/").reply(200, []); appApiMock.onPost().reply(401); diff --git a/src/background/refreshToken.ts b/src/background/refreshToken.ts index bc00998beb..1461393811 100644 --- a/src/background/refreshToken.ts +++ b/src/background/refreshToken.ts @@ -21,7 +21,7 @@ import { type Integration, type SanitizedIntegrationConfig, } from "@/integrations/integrationTypes"; -import { locator as serviceLocator } from "@/background/locator"; +import { integrationConfigLocator as serviceLocator } from "@/background/integrationConfigLocator"; import { getCachedAuthData, setCachedAuthData, diff --git a/src/background/requests.ts b/src/background/requests.ts index 590c1b5e6b..2180b1eeb3 100644 --- a/src/background/requests.ts +++ b/src/background/requests.ts @@ -19,7 +19,7 @@ import axios, { type AxiosResponse, type Method } from "axios"; import type { NetworkRequestConfig } from "@/types/networkTypes"; import serviceRegistry from "@/integrations/registry"; import { getExtensionToken } from "@/auth/authStorage"; -import { locator } from "@/background/locator"; +import { integrationConfigLocator } from "@/background/integrationConfigLocator"; import { isEmpty } from "lodash"; import launchOAuth2Flow from "@/background/auth/launchOAuth2Flow"; import { expectContext } from "@/utils/expectContext"; @@ -176,7 +176,9 @@ async function authenticate( ); } - const localConfig = await locator.findIntegrationConfig(config.id); + const localConfig = await integrationConfigLocator.findIntegrationConfig( + config.id, + ); if (!localConfig) { // Is an application error because PixieBrix should not have reached here in the first place. diff --git a/src/components/integrations/AuthWidget.test.tsx b/src/components/integrations/AuthWidget.test.tsx index 364373f89b..9af417b2fc 100644 --- a/src/components/integrations/AuthWidget.test.tsx +++ b/src/components/integrations/AuthWidget.test.tsx @@ -31,8 +31,8 @@ import { uuidSequence } from "@/testUtils/factories/stringFactories"; import selectEvent from "react-select-event"; import { refreshRegistries } from "@/hooks/useRefreshRegistries"; import { clear, find, syncPackages } from "@/registry/packageRegistry"; -import { services, registry } from "@/background/messenger/api"; -import { refreshServices } from "@/background/locator"; +import { integrationConfigLocator, registry } from "@/background/messenger/api"; +import { refreshIntegrationConfigs } from "@/background/integrationConfigLocator"; import registerDefaultWidgets from "@/components/fields/schemaFields/widgets/registerDefaultWidgets"; import { produce } from "immer"; import { userEvent } from "@/pageEditor/testHelpers"; @@ -63,7 +63,9 @@ beforeAll(async () => { appApiMock.onGet("/api/services/shared/").reply(200, [remoteConfig]); appApiMock.onGet("/api/registry/bricks/").reply(200, [integrationDefinition]); // Wire up directly to the background implementations for integration testing - jest.mocked(services.refresh).mockImplementation(refreshServices); + jest + .mocked(integrationConfigLocator.refresh) + .mockImplementation(refreshIntegrationConfigs); jest.mocked(registry.syncRemote).mockImplementation(syncPackages); jest.mocked(registry.find).mockImplementation(find); jest.mocked(registry.clear).mockImplementation(clear); diff --git a/src/components/integrations/AuthWidget.tsx b/src/components/integrations/AuthWidget.tsx index 6bcecde8ff..a92e27df0e 100644 --- a/src/components/integrations/AuthWidget.tsx +++ b/src/components/integrations/AuthWidget.tsx @@ -22,7 +22,7 @@ import { useField } from "formik"; import { useDispatch, useSelector } from "react-redux"; import registry from "@/integrations/registry"; import { uuidv4 } from "@/types/helpers"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { Button } from "react-bootstrap"; import IntegrationConfigEditorModal from "@/components/integrations/IntegrationConfigEditorModal"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -116,8 +116,8 @@ const AuthWidgetContent: React.FC = ({ // Need to write the current Redux options to storage so the locator can read them during checks await flushReduxPersistence(); try { - // Also refresh the integration config locator on the background so the new auth works immediately - await services.refresh({ remote: false, local: true }); + // Also refresh the integration config locator on the background so the new configuration works immediately + await integrationConfigLocator.refresh({ remote: false, local: true }); } catch (error) { notify.error({ message: diff --git a/src/contrib/automationanywhere/RunApiTask.test.ts b/src/contrib/automationanywhere/RunApiTask.test.ts index 11a84cc8ac..3d94a8b431 100644 --- a/src/contrib/automationanywhere/RunApiTask.test.ts +++ b/src/contrib/automationanywhere/RunApiTask.test.ts @@ -33,7 +33,7 @@ import { TEST_overrideFeatureFlags } from "@/auth/featureFlagStorage"; import { fromJS } from "@/integrations/UserDefinedIntegration"; import controlRoomOAuth2Service from "@contrib/integrations/automation-anywhere-oauth2.yaml"; import serviceRegistry from "@/integrations/registry"; -import { locator } from "@/background/locator"; +import { integrationConfigLocator } from "@/background/integrationConfigLocator"; import { type UUID } from "@/types/stringTypes"; import { setCachedAuthData } from "@/background/auth/authStorage"; import { autoUUIDSequence } from "@/testUtils/factories/stringFactories"; @@ -92,7 +92,7 @@ beforeEach(async () => { configId = config.id; appApiMock.reset(); appApiMock.onGet("/api/services/shared/").reply(200, [config]); - await locator.refresh(); + await integrationConfigLocator.refresh(); await setCachedAuthData(configId, { access_token: "testtoken1234", diff --git a/src/contrib/google/sheets/core/sheetsApi.test.ts b/src/contrib/google/sheets/core/sheetsApi.test.ts index 29e61efc15..2750aae665 100644 --- a/src/contrib/google/sheets/core/sheetsApi.test.ts +++ b/src/contrib/google/sheets/core/sheetsApi.test.ts @@ -24,7 +24,7 @@ import axios from "axios"; import { performConfiguredRequest as realProxyService } from "@/background/requests"; import { performConfiguredRequestInBackground as apiProxyService } from "@/background/messenger/api"; import { integrationConfigFactory } from "@/testUtils/factories/integrationFactories"; -import { locator } from "@/background/locator"; +import { integrationConfigLocator } from "@/background/integrationConfigLocator"; import googleDefinition from "@contrib/integrations/google-oauth2-pkce.yaml"; import { fromJS } from "@/integrations/UserDefinedIntegration"; import { @@ -106,17 +106,18 @@ describe("error handling", () => { launchOAuth2FlowMock.mockReset(); deleteCachedAuthDataSpy.mockReset(); - await locator.refresh(); + await integrationConfigLocator.refresh(); }); it("Returns permissions error for 404 message with google integration", async () => { // Google Request axiosMock.onGet().reply(404); - const config = await locator.locate( - googleIntegration.id, - integrationConfig.id, - ); + const config = + await integrationConfigLocator.findSanitizedIntegrationConfig( + googleIntegration.id, + integrationConfig.id, + ); await setCachedAuthData(integrationConfig.id, { access_token: "NOTAREALTOKEN", @@ -134,10 +135,11 @@ describe("error handling", () => { // Google Request axiosMock.onGet().reply(400); - const config = await locator.locate( - googleIntegration.id, - integrationConfig.id, - ); + const config = + await integrationConfigLocator.findSanitizedIntegrationConfig( + googleIntegration.id, + integrationConfig.id, + ); await setCachedAuthData(integrationConfig.id, { access_token: "NOTAREALTOKEN", @@ -168,10 +170,11 @@ describe("error handling", () => { // Google Request axiosMock.onGet().reply(status); - const config = await locator.locate( - googleIntegration.id, - integrationConfig.id, - ); + const config = + await integrationConfigLocator.findSanitizedIntegrationConfig( + googleIntegration.id, + integrationConfig.id, + ); const authData: AuthData = { _oauthBrand: null, @@ -215,10 +218,11 @@ describe("error handling", () => { axiosMock.onGet().reply(status); axiosMock.onPost().reply(401); - const config = await locator.locate( - googleIntegration.id, - integrationConfig.id, - ); + const config = + await integrationConfigLocator.findSanitizedIntegrationConfig( + googleIntegration.id, + integrationConfig.id, + ); await setCachedAuthData(integrationConfig.id, { access_token: "NOTAREALTOKEN", @@ -253,10 +257,11 @@ describe("error handling", () => { refresh_token: "NOTAREALREFRESHTOKEN2", }); - const config = await locator.locate( - googleIntegration.id, - integrationConfig.id, - ); + const config = + await integrationConfigLocator.findSanitizedIntegrationConfig( + googleIntegration.id, + integrationConfig.id, + ); await setCachedAuthData(integrationConfig.id, { access_token: "NOTAREALTOKEN", diff --git a/src/contrib/google/sheets/core/useGoogleAccount.ts b/src/contrib/google/sheets/core/useGoogleAccount.ts index 3c1de5183b..30907d41cc 100644 --- a/src/contrib/google/sheets/core/useGoogleAccount.ts +++ b/src/contrib/google/sheets/core/useGoogleAccount.ts @@ -18,7 +18,7 @@ import { type SanitizedIntegrationConfig } from "@/integrations/integrationTypes"; import { type FetchableAsyncState } from "@/types/sliceTypes"; import useAsyncState from "@/hooks/useAsyncState"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { useContext } from "react"; import ModIntegrationsContext from "@/mods/ModIntegrationsContext"; import { validateRegistryId } from "@/types/helpers"; @@ -42,7 +42,7 @@ function useGoogleAccount(): FetchableAsyncState { beforeAll(() => { - servicesLocateMock.mockResolvedValue( + findSanitizedIntegrationConfigMock.mockResolvedValue( sanitizedIntegrationConfigFactory({ serviceId: GOOGLE_SHEET_SERVICE_ID, // @ts-expect-error -- The type here is a record with a _brand field, so casting doesn't work diff --git a/src/contrib/google/sheets/core/useSpreadsheetId.ts b/src/contrib/google/sheets/core/useSpreadsheetId.ts index 7945ceca78..f0973fd876 100644 --- a/src/contrib/google/sheets/core/useSpreadsheetId.ts +++ b/src/contrib/google/sheets/core/useSpreadsheetId.ts @@ -18,7 +18,7 @@ import { useField } from "formik"; import { isIntegrationDependencyValueFormat } from "@/components/fields/schemaFields/fieldTypeCheckers"; import { isEmpty } from "lodash"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { getErrorMessage } from "@/errors/errorHelpers"; import { getOptionsArgForFieldValue } from "@/utils/getOptionsArgForFieldValue"; import { getSheetIdIntegrationOutputKey } from "@/contrib/google/sheets/core/getSheetIdIntegrationOutputKey"; @@ -93,11 +93,12 @@ async function findSpreadsheetIdFromFieldValue( ); } - const sanitizedIntegrationConfig = await services.locate( - integrationId, - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion,@typescript-eslint/no-non-null-assertion -- just checked above - configId!, - ); + const sanitizedIntegrationConfig = + await integrationConfigLocator.findSanitizedIntegrationConfig( + integrationId, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion,@typescript-eslint/no-non-null-assertion -- just checked above + configId!, + ); const configSpreadsheetId = sanitizedIntegrationConfig.config?.spreadsheetId; if (!configSpreadsheetId) { diff --git a/src/contrib/google/sheets/ui/AppendSpreadsheetOptions.test.tsx b/src/contrib/google/sheets/ui/AppendSpreadsheetOptions.test.tsx index b7e6dd442c..0b3386cb8a 100644 --- a/src/contrib/google/sheets/ui/AppendSpreadsheetOptions.test.tsx +++ b/src/contrib/google/sheets/ui/AppendSpreadsheetOptions.test.tsx @@ -28,7 +28,10 @@ import { getToggleOptions } from "@/components/fields/schemaFields/getToggleOpti import SpreadsheetPickerWidget from "@/contrib/google/sheets/ui/SpreadsheetPickerWidget"; import { render } from "@/pageEditor/testHelpers"; import { validateRegistryId } from "@/types/helpers"; -import { hasCachedAuthData, services } from "@/background/messenger/api"; +import { + hasCachedAuthData, + integrationConfigLocator, +} from "@/background/messenger/api"; import { integrationDependencyFactory, sanitizedIntegrationConfigFactory, @@ -61,7 +64,9 @@ import { autoUUIDSequence } from "@/testUtils/factories/stringFactories"; jest.mock("@/contrib/google/sheets/core/sheetsApi"); jest.mock("@/hooks/auth"); -const servicesLocateMock = jest.mocked(services.locate); +const findSanitizedIntegrationConfigMock = jest.mocked( + integrationConfigLocator.findSanitizedIntegrationConfig, +); const useAuthOptionsMock = jest.mocked(useAuthOptions); const isLoggedInMock = jest.mocked(hasCachedAuthData); const getAllSpreadsheetsMock = jest.mocked(getAllSpreadsheets); @@ -176,7 +181,7 @@ const fileListResponse: FileList = { beforeAll(() => { registerDefaultWidgets(); - servicesLocateMock.mockImplementation( + findSanitizedIntegrationConfigMock.mockImplementation( async (serviceId) => servicesLookup[serviceId], ); useAuthOptionsMock.mockReturnValue( diff --git a/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.test.tsx b/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.test.tsx index de8a071a78..70b1c6bcf5 100644 --- a/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.test.tsx +++ b/src/contrib/google/sheets/ui/LookupSpreadsheetOptions.test.tsx @@ -23,7 +23,10 @@ import { act, screen } from "@testing-library/react"; import { validateRegistryId } from "@/types/helpers"; import selectEvent from "react-select-event"; import { render } from "@/pageEditor/testHelpers"; -import { services, hasCachedAuthData } from "@/background/messenger/api"; +import { + integrationConfigLocator, + hasCachedAuthData, +} from "@/background/messenger/api"; import { autoUUIDSequence } from "@/testUtils/factories/stringFactories"; import { integrationDependencyFactory, @@ -46,7 +49,9 @@ import { getSpreadsheet, } from "@/contrib/google/sheets/core/sheetsApi"; -const servicesLocateMock = jest.mocked(services.locate); +const findSanitizedIntegrationConfigMock = jest.mocked( + integrationConfigLocator.findSanitizedIntegrationConfig, +); // XXX: sheetsApi should likely be mocked at the network level, not the module level jest.mock("@/contrib/google/sheets/core/sheetsApi"); @@ -176,7 +181,7 @@ async function expectTabsAndHeadersToBeLoaded() { beforeAll(() => { registerDefaultWidgets(); - servicesLocateMock.mockImplementation( + findSanitizedIntegrationConfigMock.mockImplementation( async (serviceId) => servicesLookup[serviceId], ); useAuthOptionsMock.mockReturnValue( diff --git a/src/contrib/google/sheets/ui/SpreadsheetPickerWidget.test.tsx b/src/contrib/google/sheets/ui/SpreadsheetPickerWidget.test.tsx index deae12727e..a70447bde8 100644 --- a/src/contrib/google/sheets/ui/SpreadsheetPickerWidget.test.tsx +++ b/src/contrib/google/sheets/ui/SpreadsheetPickerWidget.test.tsx @@ -18,7 +18,7 @@ import { render } from "@/pageEditor/testHelpers"; import React from "react"; import SpreadsheetPickerWidget from "@/contrib/google/sheets/ui/SpreadsheetPickerWidget"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { validateRegistryId } from "@/types/helpers"; import { uuidSequence } from "@/testUtils/factories/stringFactories"; import { act, screen } from "@testing-library/react"; @@ -46,7 +46,9 @@ function newId(): UUID { return uuidSequence(idSequence++); } -const servicesLocateMock = jest.mocked(services.locate); +const findSanitizedIntegrationConfigMock = jest.mocked( + integrationConfigLocator.findSanitizedIntegrationConfig, +); const TEST_SPREADSHEET_ID = newId(); const OTHER_TEST_SPREADSHEET_ID = newId(); @@ -113,7 +115,7 @@ const renderWithValues = async (initialValues: FormikValues) => { beforeAll(() => { registerDefaultWidgets(); - servicesLocateMock.mockImplementation( + findSanitizedIntegrationConfigMock.mockImplementation( async (serviceId) => servicesLookup[serviceId], ); getAllSpreadsheetsMock.mockResolvedValue(fileListResponse); diff --git a/src/extensionConsole/pages/activateExtension/useCloudExtensionPermissions.ts b/src/extensionConsole/pages/activateExtension/useCloudExtensionPermissions.ts index 7d99f1d534..db76598c2e 100644 --- a/src/extensionConsole/pages/activateExtension/useCloudExtensionPermissions.ts +++ b/src/extensionConsole/pages/activateExtension/useCloudExtensionPermissions.ts @@ -17,7 +17,7 @@ import { type IntegrationDependency } from "@/integrations/integrationTypes"; import useAsyncState from "@/hooks/useAsyncState"; -import { services as serviceLocator } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { type StandaloneModDefinition } from "@/types/contract"; import { type AsyncState } from "@/types/sliceTypes"; import { emptyPermissionsFactory } from "@/permissions/permissionsUtils"; @@ -42,8 +42,8 @@ function useCloudExtensionPermissions( ): AsyncPermissionsState { const permissionsState = useAsyncState( async () => { - // Refresh services because the user may have created a team integration since the last refresh. - await serviceLocator.refresh(); + // Refresh integration configurations because the user may have created a team integration since the last refresh. + await integrationConfigLocator.refresh(); return checkCloudExtensionPermissions(extension, services); }, [extension, services], diff --git a/src/extensionConsole/pages/activateMod/IntegrationsBody.test.tsx b/src/extensionConsole/pages/activateMod/IntegrationsBody.test.tsx index 233a783ed4..522cf7a631 100644 --- a/src/extensionConsole/pages/activateMod/IntegrationsBody.test.tsx +++ b/src/extensionConsole/pages/activateMod/IntegrationsBody.test.tsx @@ -36,8 +36,8 @@ import { integrationDependencyFactory, } from "@/testUtils/factories/integrationFactories"; import getModDefinitionIntegrationIds from "@/integrations/util/getModDefinitionIntegrationIds"; -import { registry, services } from "@/background/messenger/api"; -import { refreshServices } from "@/background/locator"; +import { registry, integrationConfigLocator } from "@/background/messenger/api"; +import { refreshIntegrationConfigs } from "@/background/integrationConfigLocator"; import { clear, find, syncPackages } from "@/registry/packageRegistry"; import { type ModDefinition } from "@/types/modDefinitionTypes"; import { produce } from "immer"; @@ -117,7 +117,9 @@ beforeAll(async () => { .onGet("/api/registry/bricks/") .reply(200, [integrationDefinition1, integrationDefinition2]); // Wire up directly to the background implementations for integration testing - jest.mocked(services.refresh).mockImplementation(refreshServices); + jest + .mocked(integrationConfigLocator.refresh) + .mockImplementation(refreshIntegrationConfigs); jest.mocked(registry.syncRemote).mockImplementation(syncPackages); jest.mocked(registry.find).mockImplementation(find); jest.mocked(registry.clear).mockImplementation(clear); diff --git a/src/extensionConsole/pages/activateMod/useModPermissions.ts b/src/extensionConsole/pages/activateMod/useModPermissions.ts index db1c13eae5..395ef6af56 100644 --- a/src/extensionConsole/pages/activateMod/useModPermissions.ts +++ b/src/extensionConsole/pages/activateMod/useModPermissions.ts @@ -16,7 +16,7 @@ */ import useAsyncState from "@/hooks/useAsyncState"; -import { services as serviceLocator } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { type ModDefinition } from "@/types/modDefinitionTypes"; import { checkModDefinitionPermissions } from "@/modDefinitions/modDefinitionPermissionsHelpers"; import { emptyPermissionsFactory } from "@/permissions/permissionsUtils"; @@ -47,8 +47,8 @@ function useModPermissions( const permissionsState = useAsyncState( async () => { - // Refresh services because the user may have created a team integration since the last refresh. - await serviceLocator.refresh(); + // Refresh integration configurations because the user may have created a team integration since the last refresh. + await integrationConfigLocator.refresh(); return checkModDefinitionPermissions( modDefinition, configuredDependencies, diff --git a/src/extensionConsole/pages/deployments/DeploymentsContext.tsx b/src/extensionConsole/pages/deployments/DeploymentsContext.tsx index f33ad5f6b3..da7b2d4da5 100644 --- a/src/extensionConsole/pages/deployments/DeploymentsContext.tsx +++ b/src/extensionConsole/pages/deployments/DeploymentsContext.tsx @@ -25,7 +25,7 @@ import reportEvent from "@/telemetry/reportEvent"; import { Events } from "@/telemetry/events"; import { selectActivatedModComponents } from "@/store/extensionsSelectors"; import notify from "@/utils/notify"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { refreshRegistries } from "@/hooks/useRefreshRegistries"; import { type Dispatch } from "@reduxjs/toolkit"; import useFlags, { type Restrict } from "@/hooks/useFlags"; @@ -151,7 +151,8 @@ function useDeployments(): DeploymentsState { activatableDeployments.map(async (activatableDeployment) => checkDeploymentPermissions({ activatableDeployment, - locate: services.locateAllForId, + locate: + integrationConfigLocator.findAllSanitizedConfigsForIntegration, // In the UI context, always prompt the user to accept permissions to ensure they get the full // functionality of the mod optionalPermissions: [], diff --git a/src/extensionConsole/pages/deployments/activateDeployments.ts b/src/extensionConsole/pages/deployments/activateDeployments.ts index 03b3354427..51f4f07b61 100644 --- a/src/extensionConsole/pages/deployments/activateDeployments.ts +++ b/src/extensionConsole/pages/deployments/activateDeployments.ts @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import extensionsSlice from "@/store/extensionsSlice"; import { Events } from "@/telemetry/events"; import reportEvent from "@/telemetry/reportEvent"; @@ -61,7 +61,7 @@ async function activateDeployment({ deployment, configuredDependencies: await mergeDeploymentIntegrationDependencies( activatableDeployment, - services.locateAllForId, + integrationConfigLocator.findAllSanitizedConfigsForIntegration, ), // Assume validation on the backend for options optionsArgs: deployment.options_config, diff --git a/src/extensionConsole/pages/integrations/IntegrationsPage.tsx b/src/extensionConsole/pages/integrations/IntegrationsPage.tsx index 841156df27..acfd2984df 100644 --- a/src/extensionConsole/pages/integrations/IntegrationsPage.tsx +++ b/src/extensionConsole/pages/integrations/IntegrationsPage.tsx @@ -27,7 +27,7 @@ import IntegrationConfigEditorModal from "@/components/integrations/IntegrationC import PrivateIntegrationsCard from "./PrivateIntegrationsCard"; import ConnectExtensionCard from "./ConnectExtensionCard"; import { faCloud, faPlus } from "@fortawesome/free-solid-svg-icons"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import ZapierIntegrationModal from "@/extensionConsole/pages/integrations/ZapierIntegrationModal"; import notify from "@/utils/notify"; import { useLocation } from "react-router"; @@ -317,7 +317,7 @@ const IntegrationsPage: React.VFC = () => { const syncIntegrations = useCallback(async () => { await flushReduxPersistence(); try { - await services.refresh(); + await integrationConfigLocator.refresh(); } catch (error) { notify.error({ message: diff --git a/src/extensionConsole/pages/onboarding/SetupPage.tsx b/src/extensionConsole/pages/onboarding/SetupPage.tsx index 65ea50281d..a1c9cf3e9f 100644 --- a/src/extensionConsole/pages/onboarding/SetupPage.tsx +++ b/src/extensionConsole/pages/onboarding/SetupPage.tsx @@ -25,7 +25,7 @@ import Loader from "@/components/Loader"; import useRequiredPartnerAuth from "@/auth/useRequiredPartnerAuth"; import PartnerSetupCard from "@/extensionConsole/pages/onboarding/partner/PartnerSetupCard"; import { useLocation } from "react-router"; -import { clearServiceCache } from "@/background/messenger/api"; +import { clearIntegrationRegistry } from "@/background/messenger/api"; import notify from "@/utils/notify"; import { syncRemotePackages } from "@/registry/memoryRegistry"; import useAsyncState from "@/hooks/useAsyncState"; @@ -71,7 +71,7 @@ const SetupPage: React.FunctionComponent = () => { try { await syncRemotePackages(); // Must happen after the call to fetch service definitions - await clearServiceCache(); + await clearIntegrationRegistry(); } catch (error) { reportError(error); // If an error was thrown, check if the control room integration definitions are available to determine if we should diff --git a/src/extensionConsole/pages/onboarding/partner/ControlRoomTokenForm.tsx b/src/extensionConsole/pages/onboarding/partner/ControlRoomTokenForm.tsx index 395b936b40..acd77ac856 100644 --- a/src/extensionConsole/pages/onboarding/partner/ControlRoomTokenForm.tsx +++ b/src/extensionConsole/pages/onboarding/partner/ControlRoomTokenForm.tsx @@ -21,7 +21,7 @@ import integrationsSlice from "@/integrations/store/integrationsSlice"; import { useDispatch, useSelector } from "react-redux"; import { uuidv4 } from "@/types/helpers"; import notify from "@/utils/notify"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import Form, { type RenderBody, type RenderSubmit, @@ -99,7 +99,7 @@ const ControlRoomTokenForm: React.FunctionComponent<{ await flushReduxPersistence(); try { - await services.refresh(); + await integrationConfigLocator.refresh(); // Redirect to blueprints screen. The SetupPage will always show a login screen for the "/start" URL history.push("/"); diff --git a/src/extensionConsole/pages/packageEditor/useSubmitPackage.ts b/src/extensionConsole/pages/packageEditor/useSubmitPackage.ts index b59d7c94e4..f6f4072031 100644 --- a/src/extensionConsole/pages/packageEditor/useSubmitPackage.ts +++ b/src/extensionConsole/pages/packageEditor/useSubmitPackage.ts @@ -26,7 +26,7 @@ import useRefreshRegistries from "@/hooks/useRefreshRegistries"; import useReactivateMod from "@/extensionConsole/pages/mods/utils/useReactivateMod"; import notify from "@/utils/notify"; import { Events } from "@/telemetry/events"; -import { clearServiceCache } from "@/background/messenger/api"; +import { clearIntegrationRegistry } from "@/background/messenger/api"; import { loadBrickYaml } from "@/runtime/brickYaml"; import { useCreatePackageMutation, @@ -143,7 +143,7 @@ function useSubmitPackage({ create = false }: SubmitOptions): SubmitCallbacks { if (kind === DefinitionKinds.INTEGRATION) { // Clear the background page's service cache after refreshing so // it's forced to read the updated service definition. - await clearServiceCache(); + await clearIntegrationRegistry(); } reloadModsEveryTab(); diff --git a/src/hooks/useRefreshRegistries.ts b/src/hooks/useRefreshRegistries.ts index cbd657ba07..d776fdc9f1 100644 --- a/src/hooks/useRefreshRegistries.ts +++ b/src/hooks/useRefreshRegistries.ts @@ -20,16 +20,16 @@ import { stubTrue, throttle } from "lodash"; import { useCallback, useState } from "react"; import notify from "@/utils/notify"; import { - clearServiceCache, - services as serviceAuthRegistry, + clearIntegrationRegistry, + integrationConfigLocator, } from "@/background/messenger/api"; import { syncRemotePackages } from "@/registry/memoryRegistry"; -const syncServiceAuths = async () => { - await serviceAuthRegistry.refresh(); - // Ensure the background page is using the latest service definitions for fulfilling requests. This must come after - // the call to serviceRegistry, because that populates the local IDB definitions. - await clearServiceCache(); +const syncIntegrations = async () => { + await integrationConfigLocator.refresh(); + // Ensure the background page is using the latest integration definitions for fulfilling requests. This must come + // after the call to serviceRegistry, because that populates the local IDB definitions. + await clearIntegrationRegistry(); }; /** @@ -40,7 +40,7 @@ const syncServiceAuths = async () => { export async function refreshRegistries(): Promise { // Sync remote packages in order to be able to remove packages that have been deleted/the user no longer has access to console.debug("Refreshing bricks from the server"); - await Promise.all([syncRemotePackages(), syncServiceAuths()]); + await Promise.all([syncRemotePackages(), syncIntegrations()]); } const throttledRefreshRegistries = throttle( diff --git a/src/integrations/autoConfigure.ts b/src/integrations/autoConfigure.ts index 6696de8eba..86b144f816 100644 --- a/src/integrations/autoConfigure.ts +++ b/src/integrations/autoConfigure.ts @@ -23,7 +23,7 @@ import { type UUID } from "@/types/stringTypes"; import { uuidv4 } from "@/types/helpers"; import { type RegistryId } from "@/types/registryTypes"; import { GOOGLE_OAUTH2_PKCE_INTEGRATION_ID } from "@/contrib/google/sheets/core/schemas"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { getGoogleUserEmail } from "@/contrib/google/sheets/core/sheetsApi"; /** @@ -37,10 +37,11 @@ export const autoConfigurations: Record< AutoConfigureIntegrationConfig > = { async [GOOGLE_OAUTH2_PKCE_INTEGRATION_ID](config: IntegrationConfig) { - const googleAccount = await services.locate( - config.integrationId, - config.id, - ); + const googleAccount = + await integrationConfigLocator.findSanitizedIntegrationConfig( + config.integrationId, + config.id, + ); try { const userEmail = await getGoogleUserEmail(googleAccount); return { diff --git a/src/integrations/components/RequireIntegrationConfig.test.tsx b/src/integrations/components/RequireIntegrationConfig.test.tsx index 635ba9863d..169cb86157 100644 --- a/src/integrations/components/RequireIntegrationConfig.test.tsx +++ b/src/integrations/components/RequireIntegrationConfig.test.tsx @@ -16,7 +16,7 @@ */ import React from "react"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { useAuthOptions } from "@/hooks/auth"; import { integrationDependencyFactory, @@ -39,7 +39,9 @@ import { makeVariableExpression } from "@/utils/variableUtils"; jest.mock("@/hooks/auth"); jest.mock("@/integrations/util/checkIntegrationAuth.ts"); -const serviceLocateMock = jest.mocked(services.locate); +const findSanitizedIntegrationConfigMock = jest.mocked( + integrationConfigLocator.findSanitizedIntegrationConfig, +); const useAuthOptionMock = jest.mocked(useAuthOptions); const checkIntegrationAuthMock = jest.mocked(checkIntegrationAuth); @@ -71,21 +73,23 @@ const authOptions = [localConfig1, localConfig2].map( ); useAuthOptionMock.mockReturnValue(valueToAsyncState(authOptions)); -serviceLocateMock.mockImplementation(async (integrationId, configId) => { - if (configId === localConfig1.id) { - return localConfig1; - } +findSanitizedIntegrationConfigMock.mockImplementation( + async (integrationId, configId) => { + if (configId === localConfig1.id) { + return localConfig1; + } - if (configId === localConfig2.id) { - return localConfig2; - } + if (configId === localConfig2.id) { + return localConfig2; + } - if (configId === remoteConfig.id) { - return remoteConfig; - } + if (configId === remoteConfig.id) { + return remoteConfig; + } - throw new Error("Invalid config id"); -}); + throw new Error("Invalid config id"); + }, +); const integrationDependency1 = integrationDependencyFactory({ integrationId, diff --git a/src/integrations/locator.test.ts b/src/integrations/integrationConfigLocator.test.ts similarity index 90% rename from src/integrations/locator.test.ts rename to src/integrations/integrationConfigLocator.test.ts index d514a87a23..2d1021eefa 100644 --- a/src/integrations/locator.test.ts +++ b/src/integrations/integrationConfigLocator.test.ts @@ -17,13 +17,13 @@ import { appApiMock } from "@/testUtils/appApiMock"; import { remoteIntegrationConfigurationFactory } from "@/testUtils/factories/integrationFactories"; -import LazyLocatorFactory from "@/integrations/locator"; +import IntegrationConfigLocator from "@/integrations/integrationConfigLocator"; import controlRoomTokenService from "@contrib/integrations/automation-anywhere.yaml"; import { fromJS } from "@/integrations/UserDefinedIntegration"; import serviceRegistry from "@/integrations/registry"; const integration = fromJS(controlRoomTokenService as any); -const locator = new LazyLocatorFactory(); +const locator = new IntegrationConfigLocator(); jest.mock("@/background/messenger/api", () => { const actual = jest.requireActual("@/background/messenger/api"); @@ -61,7 +61,10 @@ describe("locator", () => { await locator.refreshRemote(); - const option = await locator.locate(config.service.name, config.id); + const option = await locator.findSanitizedIntegrationConfig( + config.service.name, + config.id, + ); expect(option.proxy).toBe(true); await expect( @@ -89,7 +92,7 @@ describe("locator", () => { await locator.refreshRemote(); - const option = await locator.locate( + const option = await locator.findSanitizedIntegrationConfig( config.service.config.metadata.id, config.id, ); diff --git a/src/integrations/locator.ts b/src/integrations/integrationConfigLocator.ts similarity index 74% rename from src/integrations/locator.ts rename to src/integrations/integrationConfigLocator.ts index 5c2c218134..3d272a9463 100644 --- a/src/integrations/locator.ts +++ b/src/integrations/integrationConfigLocator.ts @@ -17,7 +17,7 @@ import { type RemoteIntegrationConfig } from "@/types/contract"; import { isEmpty, sortBy } from "lodash"; -import servicesRegistry from "@/integrations/registry"; +import integrationRegistry from "@/integrations/registry"; import { validateRegistryId } from "@/types/helpers"; import { expectContext, forbidContext } from "@/utils/expectContext"; import { ExtensionNotLinkedError } from "@/errors/genericErrors"; @@ -83,12 +83,12 @@ type Option = { let wasInitialized = false; /** - * Singleton class that produces `ServiceLocator` methods via `getLocator`. + * Singleton class for finding local and remote integration configurations. * - * NOTE: this class handles service credentials, not the service definitions. For service definitions, see the - * `services.registry` file. + * NOTE: this class handles integration configurations, not the integration definitions. For integration definitions, + * see the `integrations.registry` file. */ -class LazyLocatorFactory { +class IntegrationConfigLocator { private remote: RemoteIntegrationConfig[] = []; private local: IntegrationConfig[] = []; @@ -100,11 +100,13 @@ class LazyLocatorFactory { constructor() { forbidContext( "contentScript", - "LazyLocatorFactory cannot run in the contentScript", + "LazyIntegrationConfigLocatorFactory cannot run in the contentScript", ); if (wasInitialized) { - throw new Error("LazyLocatorFactory is a singleton class"); + throw new Error( + "LazyIntegrationConfigLocatorFactory is a singleton class", + ); } wasInitialized = true; @@ -117,7 +119,7 @@ class LazyLocatorFactory { async refreshRemote(): Promise { try { // As of https://github.com/pixiebrix/pixiebrix-app/issues/562, the API gracefully handles unauthenticated calls - // to this endpoint. However, there's no need to pull the built-in services because the user can't call them + // to this endpoint. However, there's no need to pull the built-in integrations because the user can't call them // without being authenticated const client = await getLinkedApiClient(); const { data } = await client.get( @@ -125,7 +127,9 @@ class LazyLocatorFactory { "/api/services/shared/", ); this.remote = data; - console.debug(`Fetched ${this.remote.length} remote service auth(s)`); + console.debug( + `Fetched ${this.remote.length} remote integration configuration(s)`, + ); } catch (error) { if (error instanceof ExtensionNotLinkedError) { this.remote = []; @@ -151,7 +155,7 @@ class LazyLocatorFactory { await Promise.all([this.refreshLocal(), this.refreshRemote()]); this.initializeOptions(); this.updateTimestamp = timestamp; - console.debug("Refreshed service configuration locator", { + console.debug("Refreshed integration configuration locator", { updateTimestamp: this.updateTimestamp, }); }); @@ -185,11 +189,13 @@ class LazyLocatorFactory { } /** - * Return the corresponding integration configuration, including secrets. Returns `null` if not available. + * Return the corresponding integration configuration, including secrets. Returns `undefined` if a remote + * configuration requiring the proxy, or not found. * * Prior to 1.7.34, only could return locally-defined configurations. Now also returns remote pushdown configurations. * * @param configId UUID of the integration configuration + * @see findSanitizedIntegrationConfig */ async findIntegrationConfig( configId: UUID, @@ -214,25 +220,25 @@ class LazyLocatorFactory { return [...this.local, ...remote].find((x) => x.id === configId); } - async locateAllForService( - serviceId: RegistryId, + async findAllSanitizedConfigsForIntegration( + integrationId: RegistryId, ): Promise { if (!this.initialized) { await this.refresh(); } - if (serviceId === PIXIEBRIX_INTEGRATION_ID) { + if (integrationId === PIXIEBRIX_INTEGRATION_ID) { // HACK: for now use the separate storage for the extension key return [pixiebrixConfigurationFactory()]; } - let service: IntegrationABC; + let integration: IntegrationABC; // Handle case where locateAllForService is called before service definitions are loaded. (For example, because it's // being called from the background page in installer.ts). // In the future, we may want to expose an option on the method to control this behavior. try { - service = await servicesRegistry.lookup(serviceId); + integration = await integrationRegistry.lookup(integrationId); } catch (error) { if (error instanceof DoesNotExistError) { return []; @@ -242,27 +248,33 @@ class LazyLocatorFactory { } return this.options - .filter((x) => x.serviceId === serviceId) + .filter((x) => x.serviceId === integrationId) .map((match) => ({ _sanitizedIntegrationConfigBrand: null, id: match.id, label: match.label, - serviceId, + serviceId: integrationId, proxy: match.proxy, - config: sanitizeIntegrationConfig(service, match.config), + config: sanitizeIntegrationConfig(integration, match.config), })); } - async locate( - serviceId: RegistryId, - authId: UUID, + /** + * Return the sanitized integration configuration, with secrets removed. + * @param integrationId the integration definition (for determining which properties are secrets) + * @param integrationConfigId the configuration id + * @throws MissingConfigurationError if configuration not found for the given integration id + */ + async findSanitizedIntegrationConfig( + integrationId: RegistryId, + integrationConfigId: UUID, ): Promise { expectContext( "background", - "The service locator must run in the background worker", + "The integration configuration locator must run in the background worker", ); - if (serviceId === PIXIEBRIX_INTEGRATION_ID) { + if (integrationId === PIXIEBRIX_INTEGRATION_ID) { // Since 1.8.13 the locator should not be used to instantiate the pixiebrix integration config throw new Error( "Use `pixiebrixConfigurationFactory` to instantiate the pixiebrix integration config", @@ -273,47 +285,49 @@ class LazyLocatorFactory { await this.refresh(); } - const service = await servicesRegistry.lookup(serviceId); + const integration = await integrationRegistry.lookup(integrationId); const match = this.options.find( - (x) => x.serviceId === serviceId && x.id === authId, + (x) => x.serviceId === integrationId && x.id === integrationConfigId, ); if (!match) { throw new MissingConfigurationError( - `Configuration ${authId} not found for ${serviceId}`, - serviceId, - authId, + `Configuration ${integrationConfigId} not found for ${integrationId}`, + integrationId, + integrationConfigId, ); } // Proxied configurations have their secrets removed, so can be empty on the client-side. - // Some OAuth2 PKCE services, e.g. google/oauth2-pkce, don't require any configurations, so can be empty. + // Some OAuth2 PKCE integrations, e.g. google/oauth2-pkce, don't require any configurations, so can be empty. if ( isEmpty(match.config) && - !isEmpty(service.schema.properties) && + !isEmpty(integration.schema.properties) && !match.proxy && - service.hasAuth + integration.hasAuth ) { - console.warn(`Config ${authId} for service ${serviceId} is empty`); + console.warn( + `Config ${integrationConfigId} for integration ${integrationId} is empty`, + ); } - console.debug(`Locate auth for ${serviceId}`, { + console.debug(`Locate integration configuration for ${integrationId}`, { currentTimestamp: Date.now(), updateTimestamp: this.updateTimestamp, - id: authId, + id: integrationConfigId, proxy: match.proxy, }); return { _sanitizedIntegrationConfigBrand: null, - id: authId, + id: integrationConfigId, label: match.label, - serviceId, + serviceId: integrationId, proxy: match.proxy, - config: sanitizeIntegrationConfig(service, match.config), + config: sanitizeIntegrationConfig(integration, match.config), }; } } -export default LazyLocatorFactory; +export default IntegrationConfigLocator; diff --git a/src/integrations/useSanitizedIntegrationConfigFormikAdapter.test.ts b/src/integrations/useSanitizedIntegrationConfigFormikAdapter.test.ts index 24de2fc663..ee2390f3bf 100644 --- a/src/integrations/useSanitizedIntegrationConfigFormikAdapter.test.ts +++ b/src/integrations/useSanitizedIntegrationConfigFormikAdapter.test.ts @@ -66,21 +66,21 @@ const integrationDependency2 = integrationDependencyFactory({ }); jest - .mocked(backgroundApi.services.locate) - .mockImplementation(async (serviceId, authId) => { - if (serviceId === integrationId2) { + .mocked(backgroundApi.integrationConfigLocator.findSanitizedIntegrationConfig) + .mockImplementation(async (integrationId, configId) => { + if (integrationId === integrationId2) { return integrationConfig2; } - if (authId === integrationConfig1.id) { + if (configId === integrationConfig1.id) { return integrationConfig1; } - if (authId === integrationConfig1a.id) { + if (configId === integrationConfig1a.id) { return integrationConfig1a; } - throw new Error(`Unknown authId: ${authId}`); + throw new Error(`Unknown authId: ${configId}`); }); jest.mock("@/integrations/registry", () => { diff --git a/src/integrations/useSanitizedIntegrationConfigFormikAdapter.ts b/src/integrations/useSanitizedIntegrationConfigFormikAdapter.ts index 448a99d650..1ee1e19d97 100644 --- a/src/integrations/useSanitizedIntegrationConfigFormikAdapter.ts +++ b/src/integrations/useSanitizedIntegrationConfigFormikAdapter.ts @@ -20,7 +20,7 @@ import { type IntegrationDependency, type SanitizedIntegrationConfig, } from "@/integrations/integrationTypes"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { type RegistryId } from "@/types/registryTypes"; import useAsyncState from "@/hooks/useAsyncState"; import { type FetchableAsyncState } from "@/types/sliceTypes"; @@ -85,8 +85,11 @@ function useSanitizedIntegrationConfigFormikAdapter( } const { integrationId, configId } = integrationDependency; - // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion,@typescript-eslint/no-non-null-assertion -- unconfigured dependencies are filtered out - return services.locate(integrationId, configId!); + return integrationConfigLocator.findSanitizedIntegrationConfig( + integrationId, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion,@typescript-eslint/no-non-null-assertion -- unconfigured dependencies are filtered out + configId!, + ); }, [integrationDependency]); } diff --git a/src/integrations/util/locateSanitizedIntegrationConfigWithRetry.ts b/src/integrations/util/findSanitizedIntegrationConfigWithRetry.ts similarity index 76% rename from src/integrations/util/locateSanitizedIntegrationConfigWithRetry.ts rename to src/integrations/util/findSanitizedIntegrationConfigWithRetry.ts index 6640d22e28..ca77b397b7 100644 --- a/src/integrations/util/locateSanitizedIntegrationConfigWithRetry.ts +++ b/src/integrations/util/findSanitizedIntegrationConfigWithRetry.ts @@ -18,17 +18,20 @@ import { type RegistryId } from "@/types/registryTypes"; import { type UUID } from "@/types/stringTypes"; import { type SanitizedIntegrationConfig } from "@/integrations/integrationTypes"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { isSpecificError } from "@/errors/errorHelpers"; import { MissingConfigurationError } from "@/errors/businessErrors"; import { memoizeUntilSettled } from "@/utils/promiseUtils"; -async function _locateWithRetry( +async function _findWithRetry( integrationId: RegistryId, - authId: UUID, + integrationConfigId: UUID, ): Promise { try { - return await services.locate(integrationId, authId); + return await integrationConfigLocator.findSanitizedIntegrationConfig( + integrationId, + integrationConfigId, + ); } catch (error) { if (isSpecificError(error, MissingConfigurationError)) { // Retry @@ -38,9 +41,12 @@ async function _locateWithRetry( } // Ensure the locator has the latest configurations (remote and local) - await services.refresh(); + await integrationConfigLocator.refresh(); - return services.locate(integrationId, authId); + return integrationConfigLocator.findSanitizedIntegrationConfig( + integrationId, + integrationConfigId, + ); } /** @@ -52,11 +58,11 @@ async function _locateWithRetry( // trying to locate the same integration. Might also consider full // memoization/caching, but would have to be careful about invalidating the // cache on integration configuration changes -const locateSanitizedIntegrationConfigWithRetry = memoizeUntilSettled( - _locateWithRetry, +const findSanitizedIntegrationConfigWithRetry = memoizeUntilSettled( + _findWithRetry, { cacheKey: JSON.stringify, }, ); -export default locateSanitizedIntegrationConfigWithRetry; +export default findSanitizedIntegrationConfigWithRetry; diff --git a/src/integrations/util/makeIntegrationsContextFromDependencies.ts b/src/integrations/util/makeIntegrationsContextFromDependencies.ts index 457aeb0d42..11c184785a 100644 --- a/src/integrations/util/makeIntegrationsContextFromDependencies.ts +++ b/src/integrations/util/makeIntegrationsContextFromDependencies.ts @@ -23,7 +23,7 @@ import { type IntegrationsContext, type IntegrationsContextValue, } from "@/types/runtimeTypes"; -import locateSanitizedIntegrationConfigWithRetry from "@/integrations/util/locateSanitizedIntegrationConfigWithRetry"; +import findSanitizedIntegrationConfigWithRetry from "@/integrations/util/findSanitizedIntegrationConfigWithRetry"; import { pickBy } from "lodash"; import { type UUID } from "@/types/stringTypes"; import { type RegistryId } from "@/types/registryTypes"; @@ -41,7 +41,7 @@ async function dependencyContextValue({ }): Promise { // Should be safe to call locateWithRetry in parallel b/c the locator.refresh() method debounces/coalesces // the promise - const integrationConfig = await locateSanitizedIntegrationConfigWithRetry( + const integrationConfig = await findSanitizedIntegrationConfigWithRetry( integrationId, configId, ); diff --git a/src/integrations/util/permissionsHelpers.ts b/src/integrations/util/permissionsHelpers.ts index 11ec1f1c78..8eadf6d3e2 100644 --- a/src/integrations/util/permissionsHelpers.ts +++ b/src/integrations/util/permissionsHelpers.ts @@ -20,7 +20,7 @@ import serviceRegistry from "@/integrations/registry"; import { expectContext } from "@/utils/expectContext"; import { type IntegrationDependency } from "@/integrations/integrationTypes"; import { PIXIEBRIX_INTEGRATION_ID } from "@/integrations/constants"; -import locateSanitizedIntegrationConfigWithRetry from "@/integrations/util/locateSanitizedIntegrationConfigWithRetry"; +import findSanitizedIntegrationConfigWithRetry from "@/integrations/util/findSanitizedIntegrationConfigWithRetry"; import { assertNotNullish } from "@/utils/nullishUtils"; /** @@ -45,7 +45,7 @@ export async function collectIntegrationOriginPermissions({ "configId is required for non-pixiebrix integrations", ); - const localConfig = await locateSanitizedIntegrationConfigWithRetry( + const localConfig = await findSanitizedIntegrationConfigWithRetry( integrationId, configId, ); diff --git a/src/permissions/deploymentPermissionsHelpers.ts b/src/permissions/deploymentPermissionsHelpers.ts index a0f68ece9f..3f751b5d53 100644 --- a/src/permissions/deploymentPermissionsHelpers.ts +++ b/src/permissions/deploymentPermissionsHelpers.ts @@ -19,7 +19,7 @@ import { findLocalDeploymentConfiguredIntegrationDependencies, - type Locate, + type FindAllSanitizedConfigsForIntegration, } from "@/utils/deploymentUtils"; import { checkModDefinitionPermissions } from "@/modDefinitions/modDefinitionPermissionsHelpers"; import { type PermissionsStatus } from "@/permissions/permissionsTypes"; @@ -43,7 +43,7 @@ export async function checkDeploymentPermissions({ optionalPermissions, }: { activatableDeployment: ActivatableDeployment; - locate: Locate; + locate: FindAllSanitizedConfigsForIntegration; optionalPermissions: Manifest.OptionalPermission[]; }): Promise { const localAuths = await findLocalDeploymentConfiguredIntegrationDependencies( diff --git a/src/runtime/pipelineTests/integrationsContext.test.ts b/src/runtime/pipelineTests/integrationsContext.test.ts index 62d7fbcba9..2e78423bf0 100644 --- a/src/runtime/pipelineTests/integrationsContext.test.ts +++ b/src/runtime/pipelineTests/integrationsContext.test.ts @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import blockRegistry from "@/bricks/registry"; +import brickRegistry from "@/bricks/registry"; import { reducePipeline } from "@/runtime/reducePipeline"; import { contextBrick, @@ -25,7 +25,7 @@ import { testOptions, } from "./pipelineTestHelpers"; import { validateOutputKey } from "@/runtime/runtimeTypes"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import { uuidv4, validateRegistryId } from "@/types/helpers"; import { type ApiVersion, type TemplateEngine } from "@/types/runtimeTypes"; import { @@ -48,11 +48,13 @@ import { autoUUIDSequence } from "@/testUtils/factories/stringFactories"; import pixiebrixIntegrationDependencyFactory from "@/integrations/util/pixiebrixIntegrationDependencyFactory"; beforeEach(() => { - blockRegistry.clear(); - blockRegistry.register([echoBrick, contextBrick, identityBrick]); + brickRegistry.clear(); + brickRegistry.register([echoBrick, contextBrick, identityBrick]); }); -const locateMock = jest.mocked(services.locate); +const findSanitizedIntegrationConfigMock = jest.mocked( + integrationConfigLocator.findSanitizedIntegrationConfig, +); describe.each([["v1"], ["v2"], ["v3"]])( "apiVersion: %s", @@ -117,17 +119,19 @@ describe.each([["v1"], ["v2"], ["v3"]])( qux: "QUX_VALUE", }, }); - locateMock.mockImplementation(async (integrationId, configId) => { - if (configId === authId1) { - return config1; - } + findSanitizedIntegrationConfigMock.mockImplementation( + async (integrationId, configId) => { + if (configId === authId1) { + return config1; + } - if (configId === authId2) { - return config2; - } + if (configId === authId2) { + return config2; + } - throw new Error(`Unexpected configId: ${configId}`); - }); + throw new Error(`Unexpected configId: ${configId}`); + }, + ); const dependencies: IntegrationDependency[] = [dependency1, dependency2]; const serviceContext = @@ -193,7 +197,7 @@ describe.each([["v1"], ["v2"]])("apiVersion: %s", (apiVersion: ApiVersion) => { const authId = uuidv4(); const serviceId = validateRegistryId("test/api"); - locateMock.mockResolvedValue( + findSanitizedIntegrationConfigMock.mockResolvedValue( sanitizedIntegrationConfigFactory({ id: authId, serviceId, @@ -263,7 +267,7 @@ describe.each([["v3"]])("apiVersion: %s", (apiVersion: ApiVersion) => { const authId = uuidv4(); const serviceId = validateRegistryId("test/api"); - locateMock.mockResolvedValue( + findSanitizedIntegrationConfigMock.mockResolvedValue( sanitizedIntegrationConfigFactory({ id: authId, serviceId, @@ -312,7 +316,7 @@ describe.each([["v3"]])("apiVersion: %s", (apiVersion: ApiVersion) => { const authId = uuidv4(); const serviceId = validateRegistryId("test/api"); - locateMock.mockResolvedValue( + findSanitizedIntegrationConfigMock.mockResolvedValue( sanitizedIntegrationConfigFactory({ id: authId, serviceId, diff --git a/src/runtime/pipelineTests/validateInput.test.ts b/src/runtime/pipelineTests/validateInput.test.ts index 599b629fc1..0e8fdc79ce 100644 --- a/src/runtime/pipelineTests/validateInput.test.ts +++ b/src/runtime/pipelineTests/validateInput.test.ts @@ -45,10 +45,12 @@ import type { SanitizedIntegrationConfig, } from "@/integrations/integrationTypes"; import { toExpression } from "@/utils/expressionUtils"; -import { services } from "@/background/messenger/api"; +import { integrationConfigLocator } from "@/background/messenger/api"; import apiVersionOptions from "@/runtime/apiVersionOptions"; -const locateMock = jest.mocked(services.locate); +const locateMock = jest.mocked( + integrationConfigLocator.findSanitizedIntegrationConfig, +); beforeEach(() => { integrationRegistry.clear(); diff --git a/src/sidebar/activateMod/ActivateModPanel.test.tsx b/src/sidebar/activateMod/ActivateModPanel.test.tsx index b7e451b193..eb849163de 100644 --- a/src/sidebar/activateMod/ActivateModPanel.test.tsx +++ b/src/sidebar/activateMod/ActivateModPanel.test.tsx @@ -41,10 +41,10 @@ import ActivateMultipleModsPanel from "@/sidebar/activateMod/ActivateMultipleMod import ErrorBoundary from "@/sidebar/SidebarErrorBoundary"; import { includesQuickBarStarterBrick } from "@/starterBricks/starterBrickModUtils"; import { generateIntegrationAndRemoteConfig } from "@/testUtils/factories/integrationFactories"; -import { services, registry } from "@/background/messenger/api"; +import { integrationConfigLocator, registry } from "@/background/messenger/api"; import { clear, find, syncPackages } from "@/registry/packageRegistry"; import { refreshRegistries } from "@/hooks/useRefreshRegistries"; -import { refreshServices } from "@/background/locator"; +import { refreshIntegrationConfigs } from "@/background/integrationConfigLocator"; import { type WizardValues } from "@/activation/wizardTypes"; import useActivateMod, { type ActivateResult, @@ -94,7 +94,9 @@ let activateModSpy: jest.MockedFunction< beforeAll(() => { registerDefaultWidgets(); // Wire up registry for integrated testing - jest.mocked(services.refresh).mockImplementation(refreshServices); + jest + .mocked(integrationConfigLocator.refresh) + .mockImplementation(refreshIntegrationConfigs); jest.mocked(registry.syncRemote).mockImplementation(syncPackages); jest.mocked(registry.find).mockImplementation(find); jest.mocked(registry.clear).mockImplementation(clear); diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index ce2ab099f8..2f17536160 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -137,7 +137,7 @@ "./background/initTheme.ts", "./background/installer.test.ts", "./background/installer.ts", - "./background/locator.ts", + "./background/integrationConfigLocator.ts", "./background/messenger/api.ts", "./background/messenger/external/_implementation.ts", "./background/messenger/external/api.ts", @@ -953,8 +953,8 @@ "./integrations/components/RequireIntegrationConfig.tsx", "./integrations/constants.ts", "./integrations/integrationTypes.ts", - "./integrations/locator.test.ts", - "./integrations/locator.ts", + "./integrations/integrationConfigLocator.test.ts", + "./integrations/integrationConfigLocator.ts", "./integrations/registry.ts", "./integrations/sanitizeIntegrationConfig.test.ts", "./integrations/sanitizeIntegrationConfig.ts", @@ -973,7 +973,7 @@ "./integrations/util/getModDefinitionIntegrationIds.ts", "./integrations/util/getUnconfiguredComponentIntegrations.test.ts", "./integrations/util/getUnconfiguredComponentIntegrations.ts", - "./integrations/util/locateSanitizedIntegrationConfigWithRetry.ts", + "./integrations/util/findSanitizedIntegrationConfigWithRetry.ts", "./integrations/util/makeIntegrationsContextFromDependencies.ts", "./integrations/util/permissionsHelpers.ts", "./integrations/util/pixiebrixConfigurationFactory.ts", diff --git a/src/utils/deploymentUtils.ts b/src/utils/deploymentUtils.ts index 29c5d61c71..4c9e4d9d04 100644 --- a/src/utils/deploymentUtils.ts +++ b/src/utils/deploymentUtils.ts @@ -179,7 +179,7 @@ export function selectInstalledDeployments( * Integration config lookup method. Extracted as parameter to support background messenger calls and calls directly * from the background page. */ -export type Locate = ( +export type FindAllSanitizedConfigsForIntegration = ( integrationId: RegistryId, ) => Promise; @@ -194,7 +194,7 @@ const isPersonal = (x: SanitizedIntegrationConfig) => !x.proxy; */ export async function findLocalDeploymentConfiguredIntegrationDependencies( { deployment, modDefinition }: ActivatableDeployment, - locate: Locate, + locate: FindAllSanitizedConfigsForIntegration, ): Promise< Array< Except & { @@ -231,7 +231,7 @@ export async function findLocalDeploymentConfiguredIntegrationDependencies( */ export async function mergeDeploymentIntegrationDependencies( { deployment, modDefinition }: ActivatableDeployment, - locate: Locate, + locate: FindAllSanitizedConfigsForIntegration, ): Promise { // Note/to-do: There is some logic overlap here with findLocalDeploymentConfiguredIntegrationDependencies() above, // but it's tricky to extract right now