mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 05:00:10 +00:00
add documentation
This commit is contained in:
@@ -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<Settings extends object, Site extends SiteId>(
|
||||
profile: ExtensionProfileMetadata<Settings, Site>,
|
||||
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<ExtensionSite> {
|
||||
return defer(() => {
|
||||
const extensions = this.registry.build(site);
|
||||
return extensions ? of(extensions) : EMPTY;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Options> = Omit<
|
||||
ObjectKey<Options>,
|
||||
"target" | "state" | "format" | "classifier"
|
||||
|
||||
@@ -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<Settings extends object, Site extends SiteId>(
|
||||
profile: ExtensionProfileMetadata<Settings, Site>,
|
||||
extension: ExtensionMetadata,
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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<CredentialType, ReadonlyArray<CredentialAlgorithm>> = 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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user