diff --git a/libs/common/src/tools/extension/extension.service.ts b/libs/common/src/tools/extension/extension.service.ts index f673df88aad..c4e6887b821 100644 --- a/libs/common/src/tools/extension/extension.service.ts +++ b/libs/common/src/tools/extension/extension.service.ts @@ -1,4 +1,4 @@ -import { EMPTY, Observable, defer, of, shareReplay } from "rxjs"; +import { shareReplay } from "rxjs"; import { Account } from "../../auth/abstractions/account.service"; import { BoundDependency } from "../dependencies"; @@ -7,7 +7,6 @@ import { UserStateSubject } from "../state/user-state-subject"; import { UserStateSubjectDependencyProvider } from "../state/user-state-subject-dependency-provider"; import { ExtensionRegistry } from "./extension-registry.abstraction"; -import { ExtensionSite } from "./extension-site"; import { ExtensionProfileMetadata, SiteId, VendorId } from "./type"; import { toObjectKey } from "./util"; @@ -15,6 +14,10 @@ import { toObjectKey } from "./util"; * These extensions integrate 3rd party services into Bitwarden. */ export class ExtensionService { + /** Instantiate the extension service. + * @param registry provides runtime status for extension sites + * @param providers provide persistent data + */ constructor( private registry: ExtensionRegistry, private readonly providers: UserStateSubjectDependencyProvider, @@ -26,6 +29,12 @@ export class ExtensionService { private log: SemanticLogger; + /** Get a subject bound to a user's extension settings + * @param profile the site's extension profile + * @param vendor the vendor integrated at the extension site + * @param dependencies.account$ the account to which the settings are bound + * @returns a subject bound to the requested user's generator settings + */ settings( profile: ExtensionProfileMetadata, vendor: VendorId, @@ -38,25 +47,17 @@ export class ExtensionService { const key = toObjectKey(profile, metadata); const account$ = dependencies.account$.pipe(shareReplay({ bufferSize: 1, refCount: true })); + // FIXME: load and apply constraints const subject = new UserStateSubject(key, this.providers, { account$ }); return subject; } + /** Look up extension metadata for a site + * @param site defines the site to retrieve. + * @returns the extensions available at the site. + */ site(site: SiteId) { return this.registry.build(site); } - - /** Look up extension metadata for a site. - * @param site defines the site to retrieve. - * @returns an observable that emits the extension sites available at the - * moment of subscription and then completes. If the extension site is not - * available, the observable completes without emitting. - */ - site$(site: SiteId): Observable { - return defer(() => { - const extensions = this.registry.build(site); - return extensions ? of(extensions) : EMPTY; - }); - } } diff --git a/libs/common/src/tools/extension/type.ts b/libs/common/src/tools/extension/type.ts index e61e45c7fd5..26135e41421 100644 --- a/libs/common/src/tools/extension/type.ts +++ b/libs/common/src/tools/extension/type.ts @@ -115,6 +115,10 @@ export type ExtensionSet = all: true; }; +/** A key for storing JavaScript objects (`{ an: "example" }`) + * in the extension profile system. + * @remarks The omitted keys are filled by the extension service. + */ export type ExtensionStorageKey = Omit< ObjectKey, "target" | "state" | "format" | "classifier" diff --git a/libs/common/src/tools/extension/util.ts b/libs/common/src/tools/extension/util.ts index caf2f26afdd..f700e84c497 100644 --- a/libs/common/src/tools/extension/util.ts +++ b/libs/common/src/tools/extension/util.ts @@ -5,7 +5,10 @@ import { ObjectKey } from "../state/object-key"; import { ExtensionMetadata, ExtensionProfileMetadata, SiteId } from "./type"; -/** Binds an extension profile to an extension site */ +/** Create an object key from an extension instance and a site profile. + * @param profile the extension profile to bind + * @param extension the extension metadata to bind + */ export function toObjectKey( profile: ExtensionProfileMetadata, extension: ExtensionMetadata, diff --git a/libs/common/src/tools/integration/integration-context.spec.ts b/libs/common/src/tools/integration/integration-context.spec.ts index 026470dddf9..42581c08dee 100644 --- a/libs/common/src/tools/integration/integration-context.spec.ts +++ b/libs/common/src/tools/integration/integration-context.spec.ts @@ -1,7 +1,6 @@ import { mock } from "jest-mock-extended"; import { I18nService } from "../../platform/abstractions/i18n.service"; -import { VendorId } from "../extension"; import { IntegrationContext } from "./integration-context"; import { IntegrationId } from "./integration-id"; @@ -9,7 +8,7 @@ import { IntegrationMetadata } from "./integration-metadata"; const EXAMPLE_META = Object.freeze({ // arbitrary - id: "simplelogin" as IntegrationId & VendorId, + id: "simplelogin" as IntegrationId, name: "Example", // arbitrary extends: ["forwarder"], @@ -26,7 +25,7 @@ describe("IntegrationContext", () => { describe("baseUrl", () => { it("outputs the base url from metadata", () => { - const context = new IntegrationContext(EXAMPLE_META, null!, i18n); + const context = new IntegrationContext(EXAMPLE_META, null, i18n); const result = context.baseUrl(); @@ -35,14 +34,14 @@ describe("IntegrationContext", () => { it("throws when the baseurl isn't defined in metadata", () => { const noBaseUrl: IntegrationMetadata = { - id: "simplelogin" as IntegrationId & VendorId, // arbitrary + id: "simplelogin" as IntegrationId, // arbitrary name: "Example", extends: ["forwarder"], // arbitrary selfHost: "maybe", }; i18n.t.mockReturnValue("error"); - const context = new IntegrationContext(noBaseUrl, null!, i18n); + const context = new IntegrationContext(noBaseUrl, null, i18n); expect(() => context.baseUrl()).toThrow("error"); }); @@ -57,7 +56,7 @@ describe("IntegrationContext", () => { it("ignores settings when selfhost is 'never'", () => { const selfHostNever: IntegrationMetadata = { - id: "simplelogin" as IntegrationId & VendorId, // arbitrary + id: "simplelogin" as IntegrationId, // arbitrary name: "Example", extends: ["forwarder"], // arbitrary baseUrl: "example.com", @@ -72,7 +71,7 @@ describe("IntegrationContext", () => { it("always reads the settings when selfhost is 'always'", () => { const selfHostAlways: IntegrationMetadata = { - id: "simplelogin" as IntegrationId & VendorId, // arbitrary + id: "simplelogin" as IntegrationId, // arbitrary name: "Example", extends: ["forwarder"], // arbitrary baseUrl: "example.com", @@ -87,7 +86,7 @@ describe("IntegrationContext", () => { it("fails when the settings are empty and selfhost is 'always'", () => { const selfHostAlways: IntegrationMetadata = { - id: "simplelogin" as IntegrationId & VendorId, // arbitrary + id: "simplelogin" as IntegrationId, // arbitrary name: "Example", extends: ["forwarder"], // arbitrary baseUrl: "example.com", @@ -102,14 +101,14 @@ describe("IntegrationContext", () => { it("reads from the metadata by default when selfhost is 'maybe'", () => { const selfHostMaybe: IntegrationMetadata = { - id: "simplelogin" as IntegrationId & VendorId, // arbitrary + id: "simplelogin" as IntegrationId, // arbitrary name: "Example", extends: ["forwarder"], // arbitrary baseUrl: "example.com", selfHost: "maybe", }; - const context = new IntegrationContext(selfHostMaybe, null!, i18n); + const context = new IntegrationContext(selfHostMaybe, null, i18n); const result = context.baseUrl(); @@ -118,7 +117,7 @@ describe("IntegrationContext", () => { it("overrides the metadata when selfhost is 'maybe'", () => { const selfHostMaybe: IntegrationMetadata = { - id: "simplelogin" as IntegrationId & VendorId, // arbitrary + id: "simplelogin" as IntegrationId, // arbitrary name: "Example", extends: ["forwarder"], // arbitrary baseUrl: "example.com", @@ -175,7 +174,7 @@ describe("IntegrationContext", () => { describe("website", () => { it("returns the website", () => { - const context = new IntegrationContext(EXAMPLE_META, null!, i18n); + const context = new IntegrationContext(EXAMPLE_META, null, i18n); const result = context.website({ website: "www.example.com" }); @@ -183,7 +182,7 @@ describe("IntegrationContext", () => { }); it("returns an empty string when the website is not specified", () => { - const context = new IntegrationContext(EXAMPLE_META, null!, i18n); + const context = new IntegrationContext(EXAMPLE_META, null, i18n); const result = context.website({ website: undefined }); @@ -193,7 +192,7 @@ describe("IntegrationContext", () => { describe("generatedBy", () => { it("creates generated by text", () => { - const context = new IntegrationContext(EXAMPLE_META, null!, i18n); + const context = new IntegrationContext(EXAMPLE_META, null, i18n); i18n.t.mockReturnValue("result"); const result = context.generatedBy({ website: null }); @@ -203,7 +202,7 @@ describe("IntegrationContext", () => { }); it("creates generated by text including the website", () => { - const context = new IntegrationContext(EXAMPLE_META, null!, i18n); + const context = new IntegrationContext(EXAMPLE_META, null, i18n); i18n.t.mockReturnValue("result"); const result = context.generatedBy({ website: "www.example.com" }); diff --git a/libs/tools/generator/core/src/metadata/index.ts b/libs/tools/generator/core/src/metadata/index.ts index 4dce92b58f6..79806fd1bcc 100644 --- a/libs/tools/generator/core/src/metadata/index.ts +++ b/libs/tools/generator/core/src/metadata/index.ts @@ -1,10 +1,12 @@ import { AlgorithmsByType as ABT } from "./data"; import { CredentialType, CredentialAlgorithm } from "./type"; +// `CredentialAlgorithm` is defined in terms of `ABT`; supplying +// type information in the barrel file breaks a circular dependency. +/** Credential generation algorithms grouped by purpose. */ export const AlgorithmsByType: Record> = ABT; export { Profile, Type } from "./data"; -export { toForwarderMetadata } from "./email/forwarder"; export { GeneratorMetadata } from "./generator-metadata"; export { ProfileContext, CoreProfileMetadata, ProfileMetadata } from "./profile-metadata"; export { GeneratorProfile, CredentialAlgorithm, CredentialType } from "./type"; diff --git a/libs/tools/generator/core/src/metadata/util.ts b/libs/tools/generator/core/src/metadata/util.ts index 4f38fe4c98d..86b2742e86d 100644 --- a/libs/tools/generator/core/src/metadata/util.ts +++ b/libs/tools/generator/core/src/metadata/util.ts @@ -31,6 +31,11 @@ export function isForwarderExtensionId( return algorithm && typeof algorithm === "object" && "forwarder" in algorithm; } +/** Extract a `VendorId` from a `CredentialAlgorithm`. + * @param algorithm the algorithm containing the vendor id + * @returns the vendor id if the algorithm identifies a forwarder extension. + * Otherwise, undefined. + */ export function toVendorId(algorithm: CredentialAlgorithm): VendorId | undefined { if (isForwarderExtensionId(algorithm)) { return algorithm.forwarder as VendorId;