From 8a37bb4ed6fd5bb7535237c145b3602ade916dbf Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 16 Apr 2026 21:54:01 +0100 Subject: [PATCH] perf: speed up security audit test imports --- extensions/discord/account-inspect-api.ts | 6 ++ extensions/discord/index.ts | 4 + extensions/feishu/security-contract-api.ts | 82 ++++++++++++++++++- extensions/slack/account-inspect-api.ts | 6 ++ extensions/slack/index.ts | 4 + extensions/telegram/account-inspect-api.ts | 6 ++ extensions/telegram/index.ts | 4 + extensions/zalouser/channel-plugin-api.ts | 3 + extensions/zalouser/index.ts | 2 +- extensions/zalouser/setup-entry.ts | 2 +- extensions/zalouser/setup-plugin-api.ts | 2 + extensions/zalouser/src/channel.ts | 8 +- extensions/zalouser/src/setup-core.ts | 26 +++++- src/channels/plugins/bundled.ts | 50 +++++++++++ src/channels/read-only-account-inspect.ts | 7 +- src/plugin-sdk/channel-entry-contract.ts | 21 +++++ src/plugin-sdk/facade-loader.ts | 50 ++++++++++- src/plugin-sdk/telegram.ts | 9 +- .../channels/security-audit-contract.ts | 66 +++++++++------ 19 files changed, 321 insertions(+), 37 deletions(-) create mode 100644 extensions/discord/account-inspect-api.ts create mode 100644 extensions/slack/account-inspect-api.ts create mode 100644 extensions/telegram/account-inspect-api.ts create mode 100644 extensions/zalouser/channel-plugin-api.ts create mode 100644 extensions/zalouser/setup-plugin-api.ts diff --git a/extensions/discord/account-inspect-api.ts b/extensions/discord/account-inspect-api.ts new file mode 100644 index 00000000000..4a24677b3d7 --- /dev/null +++ b/extensions/discord/account-inspect-api.ts @@ -0,0 +1,6 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { inspectDiscordAccount } from "./src/account-inspect.js"; + +export function inspectDiscordReadOnlyAccount(cfg: OpenClawConfig, accountId?: string | null) { + return inspectDiscordAccount({ cfg, accountId }); +} diff --git a/extensions/discord/index.ts b/extensions/discord/index.ts index aa5429e4b84..996175f65bb 100644 --- a/extensions/discord/index.ts +++ b/extensions/discord/index.ts @@ -22,6 +22,10 @@ export default defineBundledChannelEntry({ specifier: "./runtime-api.js", exportName: "setDiscordRuntime", }, + accountInspect: { + specifier: "./account-inspect-api.js", + exportName: "inspectDiscordReadOnlyAccount", + }, registerFull(api) { api.on("subagent_spawning", async (event) => { const { handleDiscordSubagentSpawning } = await loadDiscordSubagentHooksModule(); diff --git a/extensions/feishu/security-contract-api.ts b/extensions/feishu/security-contract-api.ts index 6c0e2e78b4f..9f1d56b25fa 100644 --- a/extensions/feishu/security-contract-api.ts +++ b/extensions/feishu/security-contract-api.ts @@ -1 +1,81 @@ -export { collectFeishuSecurityAuditFindings } from "./src/security-audit.js"; +import type { OpenClawConfig } from "./runtime-api.js"; + +function asRecord(value: unknown): Record | undefined { + return value && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +function hasNonEmptyString(value: unknown): boolean { + return typeof value === "string" && value.trim().length > 0; +} + +function hasConfiguredSecretInput(value: unknown): boolean { + if (hasNonEmptyString(value)) { + return true; + } + const record = asRecord(value); + return ( + Boolean(record) && + hasNonEmptyString(record?.source) && + hasNonEmptyString(record?.provider) && + hasNonEmptyString(record?.id) + ); +} + +function isFeishuDocToolEnabled(cfg: OpenClawConfig): boolean { + const channels = asRecord(cfg.channels); + const feishu = asRecord(channels?.feishu); + if (!feishu || feishu.enabled === false) { + return false; + } + + const baseTools = asRecord(feishu.tools); + const baseDocEnabled = baseTools?.doc !== false; + const baseAppId = hasNonEmptyString(feishu.appId); + const baseAppSecret = hasConfiguredSecretInput(feishu.appSecret); + const baseConfigured = baseAppId && baseAppSecret; + + const accounts = asRecord(feishu.accounts); + if (!accounts || Object.keys(accounts).length === 0) { + return baseDocEnabled && baseConfigured; + } + + for (const accountValue of Object.values(accounts)) { + const account = asRecord(accountValue) ?? {}; + if (account.enabled === false) { + continue; + } + const accountTools = asRecord(account.tools); + const effectiveTools = accountTools ?? baseTools; + const docEnabled = effectiveTools?.doc !== false; + if (!docEnabled) { + continue; + } + const accountConfigured = + (hasNonEmptyString(account.appId) || baseAppId) && + (hasConfiguredSecretInput(account.appSecret) || baseAppSecret); + if (accountConfigured) { + return true; + } + } + + return false; +} + +export function collectFeishuSecurityAuditFindings(params: { cfg: OpenClawConfig }) { + if (!isFeishuDocToolEnabled(params.cfg)) { + return []; + } + return [ + { + checkId: "channels.feishu.doc_owner_open_id", + severity: "warn" as const, + title: "Feishu doc create can grant requester permissions", + detail: + 'channels.feishu tools include "doc"; feishu_doc action "create" can grant document access to the trusted requesting Feishu user.', + remediation: + "Disable channels.feishu.tools.doc when not needed, and restrict tool access for untrusted prompts.", + }, + ]; +} diff --git a/extensions/slack/account-inspect-api.ts b/extensions/slack/account-inspect-api.ts new file mode 100644 index 00000000000..7ce9b7bfb2e --- /dev/null +++ b/extensions/slack/account-inspect-api.ts @@ -0,0 +1,6 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { inspectSlackAccount } from "./src/account-inspect.js"; + +export function inspectSlackReadOnlyAccount(cfg: OpenClawConfig, accountId?: string | null) { + return inspectSlackAccount({ cfg, accountId }); +} diff --git a/extensions/slack/index.ts b/extensions/slack/index.ts index 9fab5680053..7f6082f4ef8 100644 --- a/extensions/slack/index.ts +++ b/extensions/slack/index.ts @@ -29,5 +29,9 @@ export default defineBundledChannelEntry({ specifier: "./runtime-api.js", exportName: "setSlackRuntime", }, + accountInspect: { + specifier: "./account-inspect-api.js", + exportName: "inspectSlackReadOnlyAccount", + }, registerFull: registerSlackPluginHttpRoutes, }); diff --git a/extensions/telegram/account-inspect-api.ts b/extensions/telegram/account-inspect-api.ts new file mode 100644 index 00000000000..15fafb2e2d6 --- /dev/null +++ b/extensions/telegram/account-inspect-api.ts @@ -0,0 +1,6 @@ +import type { OpenClawConfig } from "./runtime-api.js"; +import { inspectTelegramAccount } from "./src/account-inspect.js"; + +export function inspectTelegramReadOnlyAccount(cfg: OpenClawConfig, accountId?: string | null) { + return inspectTelegramAccount({ cfg, accountId }); +} diff --git a/extensions/telegram/index.ts b/extensions/telegram/index.ts index 911046b9f82..76ae67a07eb 100644 --- a/extensions/telegram/index.ts +++ b/extensions/telegram/index.ts @@ -17,4 +17,8 @@ export default defineBundledChannelEntry({ specifier: "./runtime-api.js", exportName: "setTelegramRuntime", }, + accountInspect: { + specifier: "./account-inspect-api.js", + exportName: "inspectTelegramReadOnlyAccount", + }, }); diff --git a/extensions/zalouser/channel-plugin-api.ts b/extensions/zalouser/channel-plugin-api.ts new file mode 100644 index 00000000000..00e7a5a8595 --- /dev/null +++ b/extensions/zalouser/channel-plugin-api.ts @@ -0,0 +1,3 @@ +// Keep bundled channel entry imports narrow so bootstrap/discovery paths do +// not drag setup-only or tool runtime surfaces into lightweight plugin loads. +export { zalouserPlugin } from "./src/channel.js"; diff --git a/extensions/zalouser/index.ts b/extensions/zalouser/index.ts index fa28779dd1c..e63d7b752a1 100644 --- a/extensions/zalouser/index.ts +++ b/extensions/zalouser/index.ts @@ -21,7 +21,7 @@ export default defineBundledChannelEntry({ description: "Zalo personal account messaging via native zca-js integration", importMetaUrl: import.meta.url, plugin: { - specifier: "./api.js", + specifier: "./channel-plugin-api.js", exportName: "zalouserPlugin", }, runtime: { diff --git a/extensions/zalouser/setup-entry.ts b/extensions/zalouser/setup-entry.ts index 659201537ec..727590508e8 100644 --- a/extensions/zalouser/setup-entry.ts +++ b/extensions/zalouser/setup-entry.ts @@ -3,7 +3,7 @@ import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entr export default defineBundledChannelSetupEntry({ importMetaUrl: import.meta.url, plugin: { - specifier: "./api.js", + specifier: "./setup-plugin-api.js", exportName: "zalouserSetupPlugin", }, }); diff --git a/extensions/zalouser/setup-plugin-api.ts b/extensions/zalouser/setup-plugin-api.ts new file mode 100644 index 00000000000..891b8987ec9 --- /dev/null +++ b/extensions/zalouser/setup-plugin-api.ts @@ -0,0 +1,2 @@ +// Keep setup-entry imports narrow so setup loads do not pull tool surfaces. +export { zalouserSetupPlugin } from "./src/channel.setup.js"; diff --git a/extensions/zalouser/src/channel.ts b/extensions/zalouser/src/channel.ts index 94f194e67ef..ba06e4decc9 100644 --- a/extensions/zalouser/src/channel.ts +++ b/extensions/zalouser/src/channel.ts @@ -27,12 +27,14 @@ import { } from "./channel.adapters.js"; import { listZalouserDirectoryGroupMembers } from "./directory.js"; import type { ZalouserProbeResult } from "./probe.js"; -import { zalouserSetupAdapter } from "./setup-core.js"; -import { zalouserSetupWizard } from "./setup-surface.js"; +import { createZalouserSetupWizardProxy, zalouserSetupAdapter } from "./setup-core.js"; import { createZalouserPluginBase } from "./shared.js"; import { collectZalouserStatusIssues } from "./status-issues.js"; const loadZalouserChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime.js")); +const zalouserSetupWizardProxy = createZalouserSetupWizardProxy( + async () => (await import("./setup-surface.js")).zalouserSetupWizard, +); function mapUser(params: { id: string; @@ -66,7 +68,7 @@ export const zalouserPlugin: ChannelPlugin null, buildPatch: () => ({}), }); + +export function createZalouserSetupWizardProxy( + loadWizard: () => Promise, +): ChannelSetupWizard { + return createDelegatedSetupWizardProxy({ + channel, + loadWizard, + status: { + configuredLabel: "logged in", + unconfiguredLabel: "needs QR login", + configuredHint: "recommended · logged in", + unconfiguredHint: "recommended · QR login", + configuredScore: 1, + unconfiguredScore: 15, + }, + credentials: [], + delegatePrepare: true, + delegateFinalize: true, + }); +} diff --git a/src/channels/plugins/bundled.ts b/src/channels/plugins/bundled.ts index e58bb22c446..6f9cec3cf7c 100644 --- a/src/channels/plugins/bundled.ts +++ b/src/channels/plugins/bundled.ts @@ -18,9 +18,13 @@ type BundledChannelEntryRuntimeContract = { id: string; name: string; description: string; + features?: { + accountInspect?: boolean; + }; register: (api: unknown) => void; loadChannelPlugin: () => ChannelPlugin; loadChannelSecrets?: () => ChannelPlugin["secrets"] | undefined; + loadChannelAccountInspector?: () => NonNullable; setChannelRuntime?: (runtime: PluginRuntime) => void; }; @@ -49,6 +53,10 @@ type BundledChannelCacheContext = { lazySetupPluginsById: Map; lazySecretsById: Map; lazySetupSecretsById: Map; + lazyAccountInspectorsById: Map< + ChannelId, + NonNullable | null + >; }; const log = createSubsystemLogger("channels"); @@ -100,6 +108,13 @@ function hasSetupEntryFeature( return entry?.features?.[feature] === true; } +function hasChannelEntryFeature( + entry: BundledChannelEntryRuntimeContract | undefined, + feature: keyof NonNullable, +): boolean { + return entry?.features?.[feature] === true; +} + function resolveBundledChannelBoundaryRoot(params: { packageRoot: string; pluginsDir?: string; @@ -222,6 +237,7 @@ function createBundledChannelCacheContext(): BundledChannelCacheContext { lazySetupPluginsById: new Map(), lazySecretsById: new Map(), lazySetupSecretsById: new Map(), + lazyAccountInspectorsById: new Map(), }; } @@ -368,6 +384,24 @@ function getBundledChannelSecretsForRoot( return secrets; } +function getBundledChannelAccountInspectorForRoot( + id: ChannelId, + rootScope: BundledChannelRootScope, + cacheContext: BundledChannelCacheContext, +): NonNullable | undefined { + if (cacheContext.lazyAccountInspectorsById.has(id)) { + return cacheContext.lazyAccountInspectorsById.get(id) ?? undefined; + } + const entry = getLazyGeneratedBundledChannelEntryForRoot(id, rootScope, cacheContext)?.entry; + if (!entry?.loadChannelAccountInspector) { + cacheContext.lazyAccountInspectorsById.set(id, null); + return undefined; + } + const inspector = entry.loadChannelAccountInspector(); + cacheContext.lazyAccountInspectorsById.set(id, inspector); + return inspector; +} + function getBundledChannelSetupPluginForRoot( id: ChannelId, rootScope: BundledChannelRootScope, @@ -449,6 +483,22 @@ export function listBundledChannelSetupPluginsByFeature( }); } +export function hasBundledChannelEntryFeature( + id: ChannelId, + feature: keyof NonNullable, +): boolean { + const { rootScope, cacheContext } = resolveActiveBundledChannelCacheScope(); + const entry = getLazyGeneratedBundledChannelEntryForRoot(id, rootScope, cacheContext)?.entry; + return hasChannelEntryFeature(entry, feature); +} + +export function getBundledChannelAccountInspector( + id: ChannelId, +): NonNullable | undefined { + const { rootScope, cacheContext } = resolveActiveBundledChannelCacheScope(); + return getBundledChannelAccountInspectorForRoot(id, rootScope, cacheContext); +} + export function getBundledChannelPlugin(id: ChannelId): ChannelPlugin | undefined { const { rootScope, cacheContext } = resolveActiveBundledChannelCacheScope(); return getBundledChannelPluginForRoot(id, rootScope, cacheContext); diff --git a/src/channels/read-only-account-inspect.ts b/src/channels/read-only-account-inspect.ts index 8b162c24e23..108415158ee 100644 --- a/src/channels/read-only-account-inspect.ts +++ b/src/channels/read-only-account-inspect.ts @@ -1,5 +1,6 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; -import { getChannelPlugin } from "./plugins/registry.js"; +import { getBundledChannelAccountInspector } from "./plugins/bundled.js"; +import { getLoadedChannelPlugin } from "./plugins/registry.js"; import type { ChannelId } from "./plugins/types.public.js"; export type ReadOnlyInspectedAccount = Record; @@ -9,7 +10,9 @@ export async function inspectReadOnlyChannelAccount(params: { cfg: OpenClawConfig; accountId?: string | null; }): Promise { - const inspectAccount = getChannelPlugin(params.channelId)?.config.inspectAccount; + const inspectAccount = + getLoadedChannelPlugin(params.channelId)?.config.inspectAccount ?? + getBundledChannelAccountInspector(params.channelId); if (!inspectAccount) { return null; } diff --git a/src/plugin-sdk/channel-entry-contract.ts b/src/plugin-sdk/channel-entry-contract.ts index ce26257730d..e62e6f19d5f 100644 --- a/src/plugin-sdk/channel-entry-contract.ts +++ b/src/plugin-sdk/channel-entry-contract.ts @@ -36,6 +36,8 @@ type DefineBundledChannelEntryOptions = { secrets?: BundledEntryModuleRef; configSchema?: ChannelEntryConfigSchema | (() => ChannelEntryConfigSchema); runtime?: BundledEntryModuleRef; + accountInspect?: BundledEntryModuleRef; + features?: BundledChannelEntryFeatures; registerCliMetadata?: (api: OpenClawPluginApi) => void; registerFull?: (api: OpenClawPluginApi) => void; }; @@ -53,15 +55,21 @@ export type BundledChannelSetupEntryFeatures = { legacySessionSurfaces?: boolean; }; +export type BundledChannelEntryFeatures = { + accountInspect?: boolean; +}; + export type BundledChannelEntryContract = { kind: "bundled-channel-entry"; id: string; name: string; description: string; configSchema: ChannelEntryConfigSchema; + features?: BundledChannelEntryFeatures; register: (api: OpenClawPluginApi) => void; loadChannelPlugin: () => TPlugin; loadChannelSecrets?: () => ChannelPlugin["secrets"] | undefined; + loadChannelAccountInspector?: () => NonNullable; setChannelRuntime?: (runtime: PluginRuntime) => void; }; @@ -332,6 +340,8 @@ export function defineBundledChannelEntry({ secrets, configSchema, runtime, + accountInspect, + features, registerCliMetadata, registerFull, }: DefineBundledChannelEntryOptions): BundledChannelEntryContract { @@ -343,6 +353,13 @@ export function defineBundledChannelEntry({ const loadChannelSecrets = secrets ? () => loadBundledEntryExportSync(importMetaUrl, secrets) : undefined; + const loadChannelAccountInspector = accountInspect + ? () => + loadBundledEntryExportSync>( + importMetaUrl, + accountInspect, + ) + : undefined; const setChannelRuntime = runtime ? (pluginRuntime: PluginRuntime) => { const setter = loadBundledEntryExportSync<(runtime: PluginRuntime) => void>( @@ -359,6 +376,9 @@ export function defineBundledChannelEntry({ name, description, configSchema: resolvedConfigSchema, + ...(features || accountInspect + ? { features: { ...features, ...(accountInspect ? { accountInspect: true } : {}) } } + : {}), register(api: OpenClawPluginApi) { if (api.registrationMode === "cli-metadata") { registerCliMetadata?.(api); @@ -374,6 +394,7 @@ export function defineBundledChannelEntry({ }, loadChannelPlugin, ...(loadChannelSecrets ? { loadChannelSecrets } : {}), + ...(loadChannelAccountInspector ? { loadChannelAccountInspector } : {}), ...(setChannelRuntime ? { setChannelRuntime } : {}), }; } diff --git a/src/plugin-sdk/facade-loader.ts b/src/plugin-sdk/facade-loader.ts index 1e51b1c272a..4d9da4da9ea 100644 --- a/src/plugin-sdk/facade-loader.ts +++ b/src/plugin-sdk/facade-loader.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import { createRequire } from "node:module"; import path from "node:path"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { openBoundaryFileSync } from "../infra/boundary-file-read.js"; import { resolveBundledPluginsDir } from "../plugins/bundled-dir.js"; import { @@ -275,6 +275,54 @@ export function loadBundledPluginPublicSurfaceModuleSync(param }); } +export async function loadBundledPluginPublicSurfaceModule(params: { + dirName: string; + artifactBasename: string; + trackedPluginId?: string | (() => string); + env?: NodeJS.ProcessEnv; +}): Promise { + const location = resolveFacadeModuleLocation(params); + if (!location) { + throw new Error( + `Unable to resolve bundled plugin public surface ${params.dirName}/${params.artifactBasename}`, + ); + } + const cached = loadedFacadeModules.get(location.modulePath); + if (cached) { + return cached as T; + } + + const opened = openBoundaryFileSync({ + absolutePath: location.modulePath, + rootPath: location.boundaryRoot, + boundaryLabel: + location.boundaryRoot === getOpenClawPackageRoot() ? "OpenClaw package root" : "plugin root", + rejectHardlinks: false, + }); + if (!opened.ok) { + throw new Error(`Unable to open bundled plugin public surface ${location.modulePath}`, { + cause: opened.error, + }); + } + fs.closeSync(opened.fd); + + try { + const loaded = (await import(pathToFileURL(location.modulePath).href)) as T; + loadedFacadeModules.set(location.modulePath, loaded); + loadedFacadePluginIds.add( + typeof params.trackedPluginId === "function" + ? params.trackedPluginId() + : (params.trackedPluginId ?? params.dirName), + ); + return loaded; + } catch { + return loadFacadeModuleAtLocationSync({ + location, + trackedPluginId: params.trackedPluginId ?? params.dirName, + }); + } +} + export function listImportedBundledPluginFacadeIds(): string[] { return [...loadedFacadePluginIds].toSorted((left, right) => left.localeCompare(right)); } diff --git a/src/plugin-sdk/telegram.ts b/src/plugin-sdk/telegram.ts index d4a3fd3fb86..14597587dd1 100644 --- a/src/plugin-sdk/telegram.ts +++ b/src/plugin-sdk/telegram.ts @@ -3,6 +3,7 @@ type FacadeModule = typeof import("@openclaw/telegram/contract-api.js"); type SecurityAuditFacadeModule = typeof import("@openclaw/telegram/security-audit-contract-api.js"); import { createLazyFacadeArrayValue, + loadBundledPluginPublicSurfaceModule, loadBundledPluginPublicSurfaceModuleSync, } from "./facade-loader.js"; @@ -13,8 +14,8 @@ function loadFacadeModule(): FacadeModule { }); } -function loadSecurityAuditFacadeModule(): SecurityAuditFacadeModule { - return loadBundledPluginPublicSurfaceModuleSync({ +async function loadSecurityAuditFacadeModule(): Promise { + return await loadBundledPluginPublicSurfaceModule({ dirName: "telegram", artifactBasename: "security-audit-contract-api.js", }); @@ -31,8 +32,8 @@ export const singleAccountKeysToMove: FacadeModule["singleAccountKeysToMove"] = createLazyFacadeArrayValue(() => loadFacadeModule().singleAccountKeysToMove); export const collectTelegramSecurityAuditFindings: FacadeModule["collectTelegramSecurityAuditFindings"] = - ((...args) => - loadSecurityAuditFacadeModule().collectTelegramSecurityAuditFindings( + (async (...args) => + (await loadSecurityAuditFacadeModule()).collectTelegramSecurityAuditFindings( ...args, )) as FacadeModule["collectTelegramSecurityAuditFindings"]; diff --git a/test/helpers/channels/security-audit-contract.ts b/test/helpers/channels/security-audit-contract.ts index 8c576d069ff..ad0a67c57c1 100644 --- a/test/helpers/channels/security-audit-contract.ts +++ b/test/helpers/channels/security-audit-contract.ts @@ -1,18 +1,40 @@ -import { loadBundledPluginPublicSurfaceSync } from "../../../src/test-utils/bundled-plugin-public-surface.js"; +import { + loadBundledPluginPublicSurfaceSync, + resolveRelativeBundledPluginPublicModuleId, +} from "../../../src/test-utils/bundled-plugin-public-surface.js"; type DiscordSecurityAuditSurface = typeof import("@openclaw/discord/security-audit-contract-api.js"); type FeishuSecuritySurface = typeof import("@openclaw/feishu/security-contract-api.js"); type SlackSecuritySurface = typeof import("@openclaw/slack/security-contract-api.js"); type SynologyChatSecuritySurface = typeof import("@openclaw/synology-chat/contract-api.js"); -type TelegramSecuritySurface = typeof import("@openclaw/telegram/contract-api.js"); +type TelegramSecuritySurface = typeof import("@openclaw/telegram/security-audit-contract-api.js"); type ZalouserSecuritySurface = typeof import("@openclaw/zalouser/contract-api.js"); -function loadDiscordSecurityAuditSurface(): DiscordSecurityAuditSurface { - return loadBundledPluginPublicSurfaceSync({ - pluginId: "discord", - artifactBasename: "security-audit-contract-api.js", - }); +const discordSecurityAuditModuleId = resolveRelativeBundledPluginPublicModuleId({ + fromModuleUrl: import.meta.url, + pluginId: "discord", + artifactBasename: "security-audit-contract-api.js", +}); +const slackSecurityModuleId = resolveRelativeBundledPluginPublicModuleId({ + fromModuleUrl: import.meta.url, + pluginId: "slack", + artifactBasename: "security-contract-api.js", +}); +const telegramSecurityModuleId = resolveRelativeBundledPluginPublicModuleId({ + fromModuleUrl: import.meta.url, + pluginId: "telegram", + artifactBasename: "security-audit-contract-api.js", +}); +let discordSecurityAuditSurfacePromise: Promise | undefined; +let slackSecuritySurfacePromise: Promise | undefined; +let telegramSecuritySurfacePromise: Promise | undefined; + +function loadDiscordSecurityAuditSurface(): Promise { + discordSecurityAuditSurfacePromise ??= import( + discordSecurityAuditModuleId + ) as Promise; + return discordSecurityAuditSurfacePromise; } function loadFeishuSecuritySurface(): FeishuSecuritySurface { @@ -22,11 +44,9 @@ function loadFeishuSecuritySurface(): FeishuSecuritySurface { }); } -function loadSlackSecuritySurface(): SlackSecuritySurface { - return loadBundledPluginPublicSurfaceSync({ - pluginId: "slack", - artifactBasename: "security-contract-api.js", - }); +function loadSlackSecuritySurface(): Promise { + slackSecuritySurfacePromise ??= import(slackSecurityModuleId) as Promise; + return slackSecuritySurfacePromise; } function loadSynologyChatSecuritySurface(): SynologyChatSecuritySurface { @@ -36,11 +56,11 @@ function loadSynologyChatSecuritySurface(): SynologyChatSecuritySurface { }); } -function loadTelegramSecuritySurface(): TelegramSecuritySurface { - return loadBundledPluginPublicSurfaceSync({ - pluginId: "telegram", - artifactBasename: "contract-api.js", - }); +function loadTelegramSecuritySurface(): Promise { + telegramSecuritySurfacePromise ??= import( + telegramSecurityModuleId + ) as Promise; + return telegramSecuritySurfacePromise; } function loadZalouserSecuritySurface(): ZalouserSecuritySurface { @@ -51,8 +71,8 @@ function loadZalouserSecuritySurface(): ZalouserSecuritySurface { } export const collectDiscordSecurityAuditFindings: DiscordSecurityAuditSurface["collectDiscordSecurityAuditFindings"] = - ((...args) => - loadDiscordSecurityAuditSurface().collectDiscordSecurityAuditFindings( + (async (...args) => + (await loadDiscordSecurityAuditSurface()).collectDiscordSecurityAuditFindings( ...args, )) as DiscordSecurityAuditSurface["collectDiscordSecurityAuditFindings"]; @@ -63,8 +83,8 @@ export const collectFeishuSecurityAuditFindings: FeishuSecuritySurface["collectF )) as FeishuSecuritySurface["collectFeishuSecurityAuditFindings"]; export const collectSlackSecurityAuditFindings: SlackSecuritySurface["collectSlackSecurityAuditFindings"] = - ((...args) => - loadSlackSecuritySurface().collectSlackSecurityAuditFindings( + (async (...args) => + (await loadSlackSecuritySurface()).collectSlackSecurityAuditFindings( ...args, )) as SlackSecuritySurface["collectSlackSecurityAuditFindings"]; @@ -75,8 +95,8 @@ export const collectSynologyChatSecurityAuditFindings: SynologyChatSecuritySurfa )) as SynologyChatSecuritySurface["collectSynologyChatSecurityAuditFindings"]; export const collectTelegramSecurityAuditFindings: TelegramSecuritySurface["collectTelegramSecurityAuditFindings"] = - ((...args) => - loadTelegramSecuritySurface().collectTelegramSecurityAuditFindings( + (async (...args) => + (await loadTelegramSecuritySurface()).collectTelegramSecurityAuditFindings( ...args, )) as TelegramSecuritySurface["collectTelegramSecurityAuditFindings"];