@agent-native/core 0.206.0-nightly-20261009124314 → 0.206.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/action.d.ts +7 -1
  2. package/dist/app-config/extension-display-sources.d.ts +31 -0
  3. package/dist/app-config/extension-display-sources.js +45 -0
  4. package/dist/app-config/extensions.d.ts +7 -0
  5. package/dist/app-config/extensions.js +39 -0
  6. package/dist/app-config/index.d.ts +1 -0
  7. package/dist/app-config/index.js +1 -0
  8. package/dist/app-config/schema.d.ts +4 -0
  9. package/dist/app-config/schema.js +2 -0
  10. package/dist/client/agent-chat/index.d.ts +1 -1
  11. package/dist/client/agent-chat/index.js +1 -1
  12. package/dist/client/embed-auth.d.ts +2 -0
  13. package/dist/client/embed-auth.js +160 -12
  14. package/dist/client/host/index.d.ts +2 -2
  15. package/dist/client/host/index.js +2 -2
  16. package/dist/client/mcp-app-host.d.ts +1 -0
  17. package/dist/client/mcp-app-host.js +4 -1
  18. package/dist/client/session-replay.js +70 -6
  19. package/dist/collab/routes.d.ts +1 -1
  20. package/dist/db/client.js +2 -0
  21. package/dist/db/drizzle-config.js +2 -0
  22. package/dist/event-bus/bus.d.ts +6 -0
  23. package/dist/event-bus/bus.js +24 -1
  24. package/dist/event-bus/index.d.ts +1 -1
  25. package/dist/event-bus/index.js +1 -1
  26. package/dist/extensions/html-shell.d.ts +35 -2
  27. package/dist/extensions/html-shell.js +62 -5
  28. package/dist/extensions/iframe-csp.d.ts +20 -0
  29. package/dist/extensions/iframe-csp.js +30 -0
  30. package/dist/extensions/routes.js +17 -5
  31. package/dist/mcp/build-server.d.ts +15 -0
  32. package/dist/mcp/build-server.js +307 -91
  33. package/dist/mcp/connect-route.js +1 -0
  34. package/dist/mcp/embed-app.js +291 -31
  35. package/dist/mcp/oauth-route.js +3 -0
  36. package/dist/mcp/oauth-store.d.ts +2 -1
  37. package/dist/mcp/oauth-store.js +5 -6
  38. package/dist/mcp/oauth-token.d.ts +5 -0
  39. package/dist/mcp/oauth-token.js +17 -0
  40. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  41. package/dist/mcp/server.d.ts +2 -2
  42. package/dist/mcp/server.js +2 -2
  43. package/dist/notifications/routes.d.ts +1 -1
  44. package/dist/observability/routes.d.ts +5 -5
  45. package/dist/org/index.d.ts +2 -2
  46. package/dist/org/workspace-app-access-request.d.ts +8 -0
  47. package/dist/org/workspace-app-access-request.js +20 -0
  48. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  49. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  50. package/dist/resources/store.d.ts +22 -0
  51. package/dist/resources/store.js +88 -5
  52. package/dist/secrets/routes.d.ts +11 -11
  53. package/dist/server/action-routes.d.ts +2 -0
  54. package/dist/server/action-routes.js +187 -31
  55. package/dist/server/agent-chat/mcp-options.d.ts +3 -1
  56. package/dist/server/agent-chat-plugin.js +26 -1
  57. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  58. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  59. package/dist/server/auth.js +11 -20
  60. package/dist/server/embed-route.js +5 -3
  61. package/dist/server/embed-session.d.ts +30 -1
  62. package/dist/server/embed-session.js +376 -30
  63. package/dist/server/realtime-token.d.ts +1 -1
  64. package/dist/server/request-context.d.ts +2 -0
  65. package/dist/server/request-context.js +25 -1
  66. package/dist/shared/embed-auth.d.ts +74 -0
  67. package/dist/shared/embed-auth.js +379 -10
  68. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  69. package/dist/triggers/dispatcher.d.ts +11 -0
  70. package/dist/triggers/dispatcher.js +239 -50
  71. package/dist/triggers/index.d.ts +1 -1
  72. package/dist/triggers/index.js +1 -1
  73. package/docs/content/template-design-developers.mdx +1 -0
  74. package/package.json +4 -4
package/dist/action.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type ActionChatUIConfig } from "./action-ui.js";
3
3
  import type { ActionTool, AgentChatAttachment, AgentChatEvent } from "./agent/types.js";
4
4
  import type { ActionAuditConfig } from "./audit/types.js";
5
5
  import { type ActionAccessConfig } from "./authorization/action-access-runtime.js";
6
- export type ActionCaller = "tool" | "http" | "frontend" | "mcp-widget" | "cli" | "mcp" | "webmcp" | "a2a" | "automation";
6
+ export type ActionCaller = "tool" | "http" | "frontend" | "mcp-widget" | "mcp-widget-write" | "cli" | "mcp" | "webmcp" | "a2a" | "automation";
7
7
  export interface ActionAutomationContext {
8
8
  triggerId: string;
9
9
  triggerName: string;
@@ -38,6 +38,12 @@ export interface ActionRunContext {
38
38
  actionName?: string;
39
39
  /** Present only on frontend GETs authorized by a scoped directory-widget read capability. */
40
40
  mcpDirectoryWidgetReadOnly?: true;
41
+ /** Present only on frontend mutations authorized by a scoped directory-widget write capability. */
42
+ mcpDirectoryWidgetWrite?: {
43
+ appId: string;
44
+ resourceIds: Record<string, string>;
45
+ actionNames: readonly string[];
46
+ };
41
47
  threadId?: string;
42
48
  runId?: string;
43
49
  turnId?: string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * What the extension iframe loads for img-src / media-src when a deployment
3
+ * has not narrowed or widened them.
4
+ *
5
+ * This module has no imports on purpose: the client-rendered extension shell
6
+ * (`extensions/html-shell.ts`, used by `ExtensionViewer` and
7
+ * `InlineExtensionFrame` in the browser) builds its default CSP from this list,
8
+ * and must not pull the server app-config store into browser bundles.
9
+ *
10
+ * Frozen so an importing module cannot mutate the shared default. The schema
11
+ * hands out a fresh copy per parse, so a caller mutating its resolved config
12
+ * cannot reach this list either.
13
+ */
14
+ export declare const DEFAULT_EXTENSION_DISPLAY_SOURCES: readonly string[];
15
+ /**
16
+ * One CSP source expression with no whitespace or `;`, so a configured value
17
+ * can extend `img-src` / `media-src` but can never terminate the directive and
18
+ * append a new one. Shared by the app-config schema and the CSP builder, so the
19
+ * value that is validated at config time is validated again where it is
20
+ * interpolated.
21
+ */
22
+ export declare const EXTENSION_DISPLAY_SOURCE_PATTERN: RegExp;
23
+ export declare const EXTENSION_DISPLAY_SOURCE_MESSAGE = "must be one CSP source expression: 'self', 'none', a scheme such as https: or blob:, or an http(s) origin such as https://cdn.example.com";
24
+ export declare const EXTENSION_DISPLAY_SOURCE_NONE_MESSAGE = "'none' must be the only source in the list";
25
+ /**
26
+ * Returns why `sources` cannot be interpolated into an img-src / media-src
27
+ * directive, or `null` when it can. Mirrors the app-config schema: a non-empty
28
+ * list of single source expressions, with `'none'` only on its own (CSP drops
29
+ * `'none'` when it is mixed with other sources).
30
+ */
31
+ export declare function extensionDisplaySourcesError(sources: unknown): string | null;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * What the extension iframe loads for img-src / media-src when a deployment
3
+ * has not narrowed or widened them.
4
+ *
5
+ * This module has no imports on purpose: the client-rendered extension shell
6
+ * (`extensions/html-shell.ts`, used by `ExtensionViewer` and
7
+ * `InlineExtensionFrame` in the browser) builds its default CSP from this list,
8
+ * and must not pull the server app-config store into browser bundles.
9
+ *
10
+ * Frozen so an importing module cannot mutate the shared default. The schema
11
+ * hands out a fresh copy per parse, so a caller mutating its resolved config
12
+ * cannot reach this list either.
13
+ */
14
+ export const DEFAULT_EXTENSION_DISPLAY_SOURCES = Object.freeze(["'self'", "data:", "blob:"]);
15
+ /**
16
+ * One CSP source expression with no whitespace or `;`, so a configured value
17
+ * can extend `img-src` / `media-src` but can never terminate the directive and
18
+ * append a new one. Shared by the app-config schema and the CSP builder, so the
19
+ * value that is validated at config time is validated again where it is
20
+ * interpolated.
21
+ */
22
+ export const EXTENSION_DISPLAY_SOURCE_PATTERN = /^(?:'self'|'none'|(?:https?|data|blob|mediastream):|https?:\/\/(?:\*\.)?[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*(?::\d{1,5})?)$/;
23
+ export const EXTENSION_DISPLAY_SOURCE_MESSAGE = "must be one CSP source expression: 'self', 'none', a scheme such as https: or blob:, or an http(s) origin such as https://cdn.example.com";
24
+ export const EXTENSION_DISPLAY_SOURCE_NONE_MESSAGE = "'none' must be the only source in the list";
25
+ /**
26
+ * Returns why `sources` cannot be interpolated into an img-src / media-src
27
+ * directive, or `null` when it can. Mirrors the app-config schema: a non-empty
28
+ * list of single source expressions, with `'none'` only on its own (CSP drops
29
+ * `'none'` when it is mixed with other sources).
30
+ */
31
+ export function extensionDisplaySourcesError(sources) {
32
+ if (!Array.isArray(sources) || sources.length === 0) {
33
+ return "must be a non-empty list of CSP source expressions";
34
+ }
35
+ for (const source of sources) {
36
+ if (typeof source !== "string" ||
37
+ !EXTENSION_DISPLAY_SOURCE_PATTERN.test(source)) {
38
+ return `${JSON.stringify(source)} ${EXTENSION_DISPLAY_SOURCE_MESSAGE}`;
39
+ }
40
+ }
41
+ if (sources.includes("'none'") && sources.length !== 1) {
42
+ return EXTENSION_DISPLAY_SOURCE_NONE_MESSAGE;
43
+ }
44
+ return null;
45
+ }
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ import { DEFAULT_EXTENSION_DISPLAY_SOURCES } from "./extension-display-sources.js";
3
+ export { DEFAULT_EXTENSION_DISPLAY_SOURCES };
4
+ export declare const extensionsConfig: z.ZodObject<{
5
+ iframeImageSources: z.ZodDefault<z.ZodArray<z.ZodString>>;
6
+ iframeMediaSources: z.ZodDefault<z.ZodArray<z.ZodString>>;
7
+ }, z.core.$strip>;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { DEFAULT_EXTENSION_DISPLAY_SOURCES, EXTENSION_DISPLAY_SOURCE_MESSAGE, EXTENSION_DISPLAY_SOURCE_NONE_MESSAGE, EXTENSION_DISPLAY_SOURCE_PATTERN, } from "./extension-display-sources.js";
3
+ /**
4
+ * SECURITY — these values are interpolated into the sandboxed extension
5
+ * iframe's Content-Security-Policy. A source expression is validated as a
6
+ * single token with no whitespace or `;`, so a configured value can extend
7
+ * `img-src` / `media-src` but can never terminate the directive and append a
8
+ * new one. `connect-src` is not configurable and stays `'self'`. A remote
9
+ * `img-src` / `media-src` origin is still an explicit egress permission:
10
+ * the browser requests that URL, so extension script can encode data into
11
+ * it. The host bridge is the permission-gated path for API calls, not a
12
+ * claim that these sources are egress-free.
13
+ */
14
+ const cspSource = z
15
+ .string()
16
+ .regex(EXTENSION_DISPLAY_SOURCE_PATTERN, EXTENSION_DISPLAY_SOURCE_MESSAGE);
17
+ // The iframe shell builds its default CSP from this same list, so the declared
18
+ // default and the shipped policy cannot drift apart. It lives in an
19
+ // import-free module so the client-rendered shell can use it without pulling
20
+ // the app-config store into browser bundles.
21
+ export { DEFAULT_EXTENSION_DISPLAY_SOURCES };
22
+ function displaySources(doc) {
23
+ return (z
24
+ .array(cspSource)
25
+ .min(1)
26
+ // CSP drops a directive that mixes 'none' with other sources, so
27
+ // ["'none'", "https:"] reads as a deny-all that still allows https.
28
+ // Reject the combination instead of emitting a policy that does not
29
+ // say what the config says.
30
+ .refine((sources) => !sources.includes("'none'") || sources.length === 1, { error: EXTENSION_DISPLAY_SOURCE_NONE_MESSAGE })
31
+ // A fresh copy per parse: the shared default is frozen, and a caller
32
+ // mutating its resolved config must not reach the next parse.
33
+ .default(() => [...DEFAULT_EXTENSION_DISPLAY_SOURCES])
34
+ .meta({ doc }));
35
+ }
36
+ export const extensionsConfig = z.object({
37
+ iframeImageSources: displaySources("Replaces the image sources the sandboxed extension iframe may load. Defaults to 'self' data: blob:, which blocks every remote image; add https: or one origin to show product photos, avatars, or CDN assets. Every allowed remote origin is an explicit egress permission: the browser requests that URL. connect-src stays 'self'.").meta({ env: "AGENT_NATIVE_EXTENSION_IFRAME_IMAGE_SOURCES" }),
38
+ iframeMediaSources: displaySources("Replaces the media sources the sandboxed extension iframe may load, with the same syntax and the same default as `extensions.iframeImageSources`. A remote entry is an explicit egress permission: the browser requests that URL.").meta({ env: "AGENT_NATIVE_EXTENSION_IFRAME_MEDIA_SOURCES" }),
39
+ });
@@ -1,4 +1,5 @@
1
1
  export { defineAppConfig, enterpriseAuthAdaptersBuilt, getAppConfig, resetAppConfigForTests, } from "./store.js";
2
2
  export { resolveAppHomePath } from "./app-identity.js";
3
3
  export { AppConfigurationError } from "./configuration-error.js";
4
+ export { DEFAULT_EXTENSION_DISPLAY_SOURCES } from "./extensions.js";
4
5
  export { appConfigSchema, type AppConfig, type AppConfigInput, } from "./schema.js";
@@ -1,4 +1,5 @@
1
1
  export { defineAppConfig, enterpriseAuthAdaptersBuilt, getAppConfig, resetAppConfigForTests, } from "./store.js";
2
2
  export { resolveAppHomePath } from "./app-identity.js";
3
3
  export { AppConfigurationError } from "./configuration-error.js";
4
+ export { DEFAULT_EXTENSION_DISPLAY_SOURCES } from "./extensions.js";
4
5
  export { appConfigSchema, } from "./schema.js";
@@ -71,6 +71,10 @@ export declare const appConfigSchema: z.ZodObject<{
71
71
  workspaceOwnerEmails: z.ZodDefault<z.ZodArray<z.ZodString>>;
72
72
  defaultOwnerEmail: z.ZodOptional<z.ZodString>;
73
73
  }, z.core.$strip>>;
74
+ extensions: z.ZodPrefault<z.ZodObject<{
75
+ iframeImageSources: z.ZodDefault<z.ZodArray<z.ZodString>>;
76
+ iframeMediaSources: z.ZodDefault<z.ZodArray<z.ZodString>>;
77
+ }, z.core.$strip>>;
74
78
  integrations: z.ZodPrefault<z.ZodObject<{
75
79
  durableDispatch: z.ZodOptional<z.ZodBoolean>;
76
80
  allowUnverifiedWebhooks: z.ZodDefault<z.ZodBoolean>;
@@ -6,6 +6,7 @@ import { analyticsConfig } from "./analytics.js";
6
6
  import { appConfig } from "./app.js";
7
7
  import { authConfig } from "./auth.js";
8
8
  import { dispatchConfig } from "./dispatch.js";
9
+ import { extensionsConfig } from "./extensions.js";
9
10
  import { integrationsConfig } from "./integrations.js";
10
11
  import { launchDarklyConfig } from "./launchdarkly.js";
11
12
  import { mcpConfig } from "./mcp.js";
@@ -26,6 +27,7 @@ export const appConfigSchema = z.object({
26
27
  app: appConfig.prefault({}),
27
28
  auth: authConfig.prefault({}),
28
29
  dispatch: dispatchConfig.prefault({}),
30
+ extensions: extensionsConfig.prefault({}),
29
31
  integrations: integrationsConfig.prefault({}),
30
32
  launchDarkly: launchDarklyConfig.prefault({}),
31
33
  mcp: mcpConfig.prefault({}),
@@ -13,7 +13,7 @@ export { useSendToAgentChat } from "../use-send-to-agent-chat.js";
13
13
  export { DEFAULT_MODEL, CHAT_MODEL_SELECTION_CHANGED_EVENT, chatModelSelectionStorageKey, loadChatModelCatalog, useChatModels, type UseChatModelsResult, type UseChatModelsOptions, type PersistedModelSelection, type EngineModelGroup, } from "../use-chat-models.js";
14
14
  export { fetchAgentEngineConfiguredState, useAgentEngineConfigured, type AgentEngineConfiguredState, type FetchAgentEngineConfiguredStateOptions, type UseAgentEngineConfiguredResult, } from "../use-agent-engine-configured.js";
15
15
  export { AGENT_CHAT_AI_SETUP_REQUIRED_CODE, AgentChatAiSetupRequiredError, ensureAgentEngineReadiness, getAgentEngineReadiness, invalidateAgentEngineReadiness, isAgentChatAiSetupRequiredError, isLocalRuntimeEngine, requireAgentEngineConfiguredForDispatch, subscribeAgentEngineReadiness, LOCAL_RUNTIME_ENGINE_IDS, agentEngineStatusUrlForChatApi, type AgentEngineReadinessSource, } from "../agent-engine-readiness.js";
16
- export { AGENT_NATIVE_MCP_APP_HOST_MESSAGE_TYPES, getMcpAppHostContext, initializeMcpAppHost, isMcpAppWidgetEmbed, isOpenAiMcpAppHost, openMcpAppHostLink, requestMcpAppDisplayMode, sendMcpAppHostMessage, updateMcpAppModelContext, useIsMcpAppWidgetEmbed, useMcpAppHostContext, type AgentNativeMcpAppHostMessageType, type McpAppDisplayMode, type McpAppHostChatMessage, type McpAppHostCapabilities, type McpAppHostContext, type McpAppHostInfo, type McpAppHostContextSnapshot, type McpAppModelContextContentPart, type McpAppModelContextUpdate, } from "../mcp-app-host.js";
16
+ export { AGENT_NATIVE_MCP_APP_HOST_MESSAGE_TYPES, getMcpAppHostContext, initializeMcpAppHost, isMcpAppWidgetEmbed, isOpenAiMcpAppHost, openMcpAppHostLink, requestMcpAppDisplayMode, sendMcpAppHostMessage, updateMcpAppModelContext, useIsMcpAppWidgetEmbed, useIsMcpDirectoryWidgetReadOnlyEmbed, useIsMcpDirectoryWidgetWriteEmbed, useMcpAppHostContext, type AgentNativeMcpAppHostMessageType, type McpAppDisplayMode, type McpAppHostChatMessage, type McpAppHostCapabilities, type McpAppHostContext, type McpAppHostInfo, type McpAppHostContextSnapshot, type McpAppModelContextContentPart, type McpAppModelContextUpdate, } from "../mcp-app-host.js";
17
17
  export { buildDynamicAgentSuggestions, dedupeSuggestions, mergeAgentSuggestions, normalizeAgentDynamicSuggestionsConfig, useAgentDynamicSuggestions, useAgentDynamicSuggestionsResult, type AgentDynamicSuggestionContext, type AgentDynamicSuggestionsConfig, type AgentDynamicSuggestionsOption, } from "../dynamic-suggestions.js";
18
18
  export { clearChatStorage } from "../chat/storage.js";
19
19
  export { isAssistantChatHistoryVersion, coerceAssistantChatHistoryDate, type AssistantChatHistoryDate, type AssistantChatHistoryVersion, } from "../chat/assistant-chat-history-version.js";
@@ -13,7 +13,7 @@ export { useSendToAgentChat } from "../use-send-to-agent-chat.js";
13
13
  export { DEFAULT_MODEL, CHAT_MODEL_SELECTION_CHANGED_EVENT, chatModelSelectionStorageKey, loadChatModelCatalog, useChatModels, } from "../use-chat-models.js";
14
14
  export { fetchAgentEngineConfiguredState, useAgentEngineConfigured, } from "../use-agent-engine-configured.js";
15
15
  export { AGENT_CHAT_AI_SETUP_REQUIRED_CODE, AgentChatAiSetupRequiredError, ensureAgentEngineReadiness, getAgentEngineReadiness, invalidateAgentEngineReadiness, isAgentChatAiSetupRequiredError, isLocalRuntimeEngine, requireAgentEngineConfiguredForDispatch, subscribeAgentEngineReadiness, LOCAL_RUNTIME_ENGINE_IDS, agentEngineStatusUrlForChatApi, } from "../agent-engine-readiness.js";
16
- export { AGENT_NATIVE_MCP_APP_HOST_MESSAGE_TYPES, getMcpAppHostContext, initializeMcpAppHost, isMcpAppWidgetEmbed, isOpenAiMcpAppHost, openMcpAppHostLink, requestMcpAppDisplayMode, sendMcpAppHostMessage, updateMcpAppModelContext, useIsMcpAppWidgetEmbed, useMcpAppHostContext, } from "../mcp-app-host.js";
16
+ export { AGENT_NATIVE_MCP_APP_HOST_MESSAGE_TYPES, getMcpAppHostContext, initializeMcpAppHost, isMcpAppWidgetEmbed, isOpenAiMcpAppHost, openMcpAppHostLink, requestMcpAppDisplayMode, sendMcpAppHostMessage, updateMcpAppModelContext, useIsMcpAppWidgetEmbed, useIsMcpDirectoryWidgetReadOnlyEmbed, useIsMcpDirectoryWidgetWriteEmbed, useMcpAppHostContext, } from "../mcp-app-host.js";
17
17
  export { buildDynamicAgentSuggestions, dedupeSuggestions, mergeAgentSuggestions, normalizeAgentDynamicSuggestionsConfig, useAgentDynamicSuggestions, useAgentDynamicSuggestionsResult, } from "../dynamic-suggestions.js";
18
18
  export { clearChatStorage } from "../chat/storage.js";
19
19
  export { isAssistantChatHistoryVersion, coerceAssistantChatHistoryDate, } from "../chat/assistant-chat-history-version.js";
@@ -11,12 +11,14 @@ export declare function getEmbedAuthToken(): string | null;
11
11
  * allowed.
12
12
  */
13
13
  export declare function hasMcpDirectoryWidgetCapabilityToken(): boolean;
14
+ export declare function hasMcpDirectoryWidgetWriteCapabilityToken(): boolean;
14
15
  /**
15
16
  * True when this document is the app nested in an MCP App widget shell (the
16
17
  * chat bridge is active) on a directory-widget read capability, which the
17
18
  * server limits to the widget's own resource reads.
18
19
  */
19
20
  export declare function isMcpDirectoryWidgetReadOnlyEmbed(): boolean;
21
+ export declare function isMcpDirectoryWidgetWriteEmbed(): boolean;
20
22
  export declare function isEmbedAuthActive(): boolean;
21
23
  export declare function _resetEmbedAuthForTests(): void;
22
24
  /**
@@ -1,5 +1,5 @@
1
- import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
2
- import { isMcpDirectoryWidgetReadCapabilityScope } from "../shared/embed-auth.js";
1
+ import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TARGET_HEADER, EMBED_TARGET_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, MCP_DIRECTORY_WIDGET_SESSION_EXPIRED_HEADER, } from "../shared/embed-auth.js";
2
+ import { isMcpDirectoryWidgetReadCapabilityScope, isMcpDirectoryWidgetWriteCapabilityScope, } from "../shared/embed-auth.js";
3
3
  import { FRAMEWORK_INTERNAL_ROUTE_PREFIX } from "../shared/framework-route-prefix.js";
4
4
  import { MCP_APP_HOST_FILL_ATTRIBUTE } from "../shared/mcp-app-display.js";
5
5
  import { EMBED_TOKEN_STORAGE_KEY, MCP_CHAT_BRIDGE_STORAGE_KEY, } from "../shared/mcp-app-widget-embed.js";
@@ -7,11 +7,16 @@ import { SIGN_IN_ENTRY_PATH, SIGN_IN_LEGACY_ENTRY_PATH, } from "../shared/sign-i
7
7
  import { frameworkRoutePrefix } from "./api-path.js";
8
8
  let installed = false;
9
9
  let memoryToken = null;
10
+ let renewedFromUrlToken = null;
11
+ let pendingWidgetSessionRenewal = null;
12
+ let widgetSessionRenewalRequestId = 0;
10
13
  let mcpChatBridgeActive = false;
11
14
  let mcpChatBridgeScope = null;
12
15
  const AUTH_FAILURE_COOLDOWN_MS = 60_000;
13
16
  const GUARDED_METHODS = new Set(["GET", "HEAD"]);
14
17
  const AUTH_FAILURE_HEADER = "x-agent-native-auth-circuit-breaker";
18
+ const EMBED_TOKEN_RENEWED_FROM_STORAGE_KEY = `${EMBED_TOKEN_STORAGE_KEY}:renewed-from`;
19
+ const EMBED_SESSION_RENEWAL_TIMEOUT_MS = 15_000;
15
20
  const MCP_CHAT_BRIDGE_VIEWPORT_STYLE_ID = "agent-native-mcp-chat-bridge-viewport";
16
21
  const MCP_CHAT_BRIDGE_VIEWPORT_HEIGHT = 560;
17
22
  let pendingMcpChatBridgeViewportNotification = null;
@@ -43,7 +48,7 @@ export function readEmbedMcpChatBridgeFlagFromUrl() {
43
48
  return value === "1" || value === "true";
44
49
  }
45
50
  function currentMcpChatBridgeScope(win) {
46
- return readTokenFromUrl(win) ?? memoryToken ?? storedToken(win);
51
+ return getEmbedAuthToken() ?? memoryToken ?? storedToken(win);
47
52
  }
48
53
  function clearMcpChatBridge(win) {
49
54
  mcpChatBridgeActive = false;
@@ -142,6 +147,31 @@ export function getEmbedAuthToken() {
142
147
  return null;
143
148
  const fromUrl = readTokenFromUrl(win);
144
149
  if (fromUrl) {
150
+ let renewedFrom = renewedFromUrlToken;
151
+ if (!renewedFrom) {
152
+ try {
153
+ renewedFrom =
154
+ win.sessionStorage?.getItem(EMBED_TOKEN_RENEWED_FROM_STORAGE_KEY) ??
155
+ null;
156
+ }
157
+ catch {
158
+ renewedFrom = null;
159
+ }
160
+ }
161
+ if (renewedFrom === fromUrl) {
162
+ const refreshed = memoryToken ?? storedToken(win);
163
+ if (refreshed && refreshed !== fromUrl)
164
+ return refreshed;
165
+ }
166
+ if (renewedFrom && renewedFrom !== fromUrl) {
167
+ renewedFromUrlToken = null;
168
+ try {
169
+ win.sessionStorage?.removeItem(EMBED_TOKEN_RENEWED_FROM_STORAGE_KEY);
170
+ }
171
+ catch {
172
+ // coercion-ok: cleanup is best-effort; the refreshed URL token still supersedes this marker.
173
+ }
174
+ }
145
175
  storeToken(fromUrl, win);
146
176
  return fromUrl;
147
177
  }
@@ -160,7 +190,7 @@ function readEmbedTokenScope(token) {
160
190
  return undefined;
161
191
  }
162
192
  }
163
- let readOnlyScopeCache = null;
193
+ let widgetScopeCache = null;
164
194
  /**
165
195
  * True when the embed credential is a directory-widget read capability, which
166
196
  * only the MCP App widget flows mint. It stays readable after a client
@@ -173,13 +203,29 @@ export function hasMcpDirectoryWidgetCapabilityToken() {
173
203
  const token = getEmbedAuthToken();
174
204
  if (!token)
175
205
  return false;
176
- if (readOnlyScopeCache?.token !== token) {
177
- readOnlyScopeCache = {
206
+ if (widgetScopeCache?.token !== token) {
207
+ const scope = readEmbedTokenScope(token);
208
+ widgetScopeCache = {
178
209
  token,
179
- readOnly: isMcpDirectoryWidgetReadCapabilityScope(readEmbedTokenScope(token)),
210
+ readOnly: isMcpDirectoryWidgetReadCapabilityScope(scope),
211
+ writable: isMcpDirectoryWidgetWriteCapabilityScope(scope),
180
212
  };
181
213
  }
182
- return readOnlyScopeCache.readOnly;
214
+ return widgetScopeCache.readOnly || widgetScopeCache.writable;
215
+ }
216
+ export function hasMcpDirectoryWidgetWriteCapabilityToken() {
217
+ const token = getEmbedAuthToken();
218
+ if (!token)
219
+ return false;
220
+ if (widgetScopeCache?.token !== token) {
221
+ const scope = readEmbedTokenScope(token);
222
+ widgetScopeCache = {
223
+ token,
224
+ readOnly: isMcpDirectoryWidgetReadCapabilityScope(scope),
225
+ writable: isMcpDirectoryWidgetWriteCapabilityScope(scope),
226
+ };
227
+ }
228
+ return widgetScopeCache.writable;
183
229
  }
184
230
  /**
185
231
  * True when this document is the app nested in an MCP App widget shell (the
@@ -187,7 +233,21 @@ export function hasMcpDirectoryWidgetCapabilityToken() {
187
233
  * server limits to the widget's own resource reads.
188
234
  */
189
235
  export function isMcpDirectoryWidgetReadOnlyEmbed() {
190
- return hasMcpDirectoryWidgetCapabilityToken() && isEmbedMcpChatBridgeActive();
236
+ const token = getEmbedAuthToken();
237
+ if (!token)
238
+ return false;
239
+ if (widgetScopeCache?.token !== token) {
240
+ const scope = readEmbedTokenScope(token);
241
+ widgetScopeCache = {
242
+ token,
243
+ readOnly: isMcpDirectoryWidgetReadCapabilityScope(scope),
244
+ writable: isMcpDirectoryWidgetWriteCapabilityScope(scope),
245
+ };
246
+ }
247
+ return widgetScopeCache.readOnly && isEmbedMcpChatBridgeActive();
248
+ }
249
+ export function isMcpDirectoryWidgetWriteEmbed() {
250
+ return (hasMcpDirectoryWidgetWriteCapabilityToken() && isEmbedMcpChatBridgeActive());
191
251
  }
192
252
  export function isEmbedAuthActive() {
193
253
  const win = browserWindow();
@@ -303,7 +363,10 @@ export function _resetEmbedAuthForTests() {
303
363
  }
304
364
  installed = false;
305
365
  memoryToken = null;
306
- readOnlyScopeCache = null;
366
+ renewedFromUrlToken = null;
367
+ pendingWidgetSessionRenewal = null;
368
+ widgetSessionRenewalRequestId = 0;
369
+ widgetScopeCache = null;
307
370
  mcpChatBridgeActive = false;
308
371
  mcpChatBridgeScope = null;
309
372
  authFailureCache.clear();
@@ -526,6 +589,63 @@ function requestUrlAndKey(input, init, win) {
526
589
  shouldGuard: shouldGuardAuthFailure(method, url),
527
590
  };
528
591
  }
592
+ function requestWidgetSessionRenewal(win) {
593
+ if (pendingWidgetSessionRenewal)
594
+ return pendingWidgetSessionRenewal;
595
+ if (!win.parent || win.parent === win)
596
+ return Promise.resolve(false);
597
+ widgetSessionRenewalRequestId += 1;
598
+ const requestId = `embed-renewal-${Date.now()}-${widgetSessionRenewalRequestId}`;
599
+ const renewal = new Promise((resolve) => {
600
+ let settled = false;
601
+ let timeoutId = null;
602
+ const finish = (succeeded) => {
603
+ if (settled)
604
+ return;
605
+ settled = true;
606
+ if (timeoutId !== null)
607
+ win.clearTimeout(timeoutId);
608
+ win.removeEventListener("message", onMessage);
609
+ resolve(succeeded);
610
+ };
611
+ const onMessage = (event) => {
612
+ if (event.source !== win.parent)
613
+ return;
614
+ const message = event.data;
615
+ const data = message && typeof message === "object" ? message.data : null;
616
+ if (message?.type !== "agentNative.embedSessionRenewed" ||
617
+ data?.requestId !== requestId) {
618
+ return;
619
+ }
620
+ if (data.ok !== true) {
621
+ finish(false);
622
+ return;
623
+ }
624
+ try {
625
+ win.parent.postMessage({ type: "agentNative.embedSessionRenewalApplied", requestId }, "*");
626
+ }
627
+ catch (error) {
628
+ console.warn("[agent-native] could not acknowledge embedded session renewal", error);
629
+ }
630
+ finish(true);
631
+ };
632
+ win.addEventListener("message", onMessage);
633
+ timeoutId = win.setTimeout(() => finish(false), EMBED_SESSION_RENEWAL_TIMEOUT_MS);
634
+ try {
635
+ win.parent.postMessage({
636
+ type: "agentNative.embedSessionExpired",
637
+ data: { requestId },
638
+ }, "*");
639
+ }
640
+ catch {
641
+ finish(false);
642
+ }
643
+ }).finally(() => {
644
+ pendingWidgetSessionRenewal = null;
645
+ });
646
+ pendingWidgetSessionRenewal = renewal;
647
+ return renewal;
648
+ }
529
649
  export function ensureEmbedAuthFetchInterceptor() {
530
650
  const win = browserWindow();
531
651
  if (!win)
@@ -534,7 +654,7 @@ export function ensureEmbedAuthFetchInterceptor() {
534
654
  markEmbedMcpChatBridgeActive();
535
655
  const urlToken = readTokenFromUrl(win);
536
656
  if (urlToken) {
537
- storeToken(urlToken, win);
657
+ getEmbedAuthToken();
538
658
  stripTokenFromUrl(win);
539
659
  }
540
660
  ensureMcpChatBridgeViewportClamp(win);
@@ -562,7 +682,35 @@ export function ensureEmbedAuthFetchInterceptor() {
562
682
  if (token && sameOrigin(input, win)) {
563
683
  [fetchInput, fetchInit] = withEmbedAuthHeaders(input, init, token, win);
564
684
  }
565
- const response = await originalFetch(fetchInput, fetchInit);
685
+ const canRenewWidgetSession = Boolean(token) &&
686
+ isMcpDirectoryWidgetWriteEmbed() &&
687
+ request &&
688
+ sameOrigin(input, win);
689
+ let replayRequest = null;
690
+ let firstRequest = null;
691
+ if (canRenewWidgetSession) {
692
+ try {
693
+ firstRequest = new Request(fetchInput, fetchInit);
694
+ replayRequest = firstRequest.clone();
695
+ }
696
+ catch {
697
+ // coercion-ok: non-cloneable streamed writes retain their original response and are not replayed.
698
+ }
699
+ }
700
+ let response = firstRequest
701
+ ? await originalFetch(firstRequest)
702
+ : await originalFetch(fetchInput, fetchInit);
703
+ if (response.status === 401 && canRenewWidgetSession) {
704
+ const sessionExpired = response.headers.get(MCP_DIRECTORY_WIDGET_SESSION_EXPIRED_HEADER) ===
705
+ "1";
706
+ if (sessionExpired && replayRequest && token) {
707
+ const renewed = await requestWidgetSessionRenewal(win);
708
+ if (renewed) {
709
+ const [retryInput, retryInit] = withEmbedAuthHeaders(replayRequest, undefined, token, win);
710
+ response = await originalFetch(retryInput, retryInit);
711
+ }
712
+ }
713
+ }
566
714
  if (request?.shouldGuard && isAuthFailureStatus(response.status)) {
567
715
  await recordAuthFailure(request.key, response);
568
716
  }
@@ -1,12 +1,12 @@
1
1
  export { initializeAgentNativeClient } from "../client-bootstrap.js";
2
2
  export { agentNativeApiDisabledReason, AgentNativeApiDisabledError, setAgentNativeApiDisabled, } from "../api-surface.js";
3
- export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, } from "../embed-auth.js";
3
+ export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, isMcpDirectoryWidgetWriteEmbed, } from "../embed-auth.js";
4
4
  export { sendToFrame, onFrameMessage, requestUserInfo, getFrameOrigin, getFramePostMessageTargetOrigin, getCallbackOrigin, oauthRedirectUri, isInFrame, isTrustedFrameMessage, enterStyleEditing, enterTextEditing, exitSelectionMode, type UserInfo, } from "../frame.js";
5
5
  export { getBuilderParentOrigin, isInBuilderFrame, sendToBuilderChat, shouldParentFrameOwnAgentPanel, isTrustedBuilderMessage, tryDelegateBuildRequestToBuilder, type BuilderChatMessage, } from "../builder-frame.js";
6
6
  export { getClientSurface, type ClientSurface } from "../client-surface.js";
7
7
  export { defineClientAction, type AgentNativeClientActionDefinition, type AgentNativeClientActionRunner, } from "../client-action.js";
8
8
  export { DESKTOP_LOCAL_CODE_CHANGE_EVENT, requestDesktopLocalCodeChange, type DesktopLocalCodeChangeDetail, } from "../desktop-local-code-change.js";
9
- export { buildSessionReplayIframeBootstrap, injectSessionReplayIframeBootstrap, } from "../../extensions/session-replay-iframe.js";
9
+ export { buildSessionReplayIframeBootstrap, injectSessionReplayIframeBootstrap, RRWEB_RECORD_IFRAME_CDN_URL, } from "../../extensions/session-replay-iframe.js";
10
10
  export { SESSION_REPLAY_IFRAME_ATTRIBUTE, SESSION_REPLAY_IFRAME_PROBE, SESSION_REPLAY_IFRAME_START, SESSION_REPLAY_IFRAME_STOP, } from "../../session-replay-iframe-protocol.js";
11
11
  export { AGENT_NATIVE_HOST_BRIDGE_VERSION, AGENT_NATIVE_HOST_MESSAGE_TYPES, announceAgentNativeFrameReady, createAgentNativeHostBridge, defaultAgentNativeHostCommands, onAgentNativeHostInit, readAgentNativeScreenContext, requestAgentNativeHostActions, requestAgentNativeHostContext, requestAgentNativeHostWebMcpTools, runAgentNativeHostAction, runAgentNativeHostWebMcpTool, sendAgentNativeHostCommand, type AgentNativeActionAvailability, type AgentNativeActionManifestEntry, type AgentNativeClientAction, type AgentNativeClientActionApprovalConfig, type AgentNativeClientActionGetter, type AgentNativeClientActionRuntime, type AgentNativeClientActions, type AgentNativeHostAuth, type AgentNativeHostAuthPayload, type AgentNativeHostBridge, type AgentNativeHostBridgeEvent, type AgentNativeHostBridgeOptions, type AgentNativeHostCapabilities, type AgentNativeHostCommandHandler, type AgentNativeHostCommandHandlers, type AgentNativeHostCommandRequest, type AgentNativeHostContext, type AgentNativeHostContextGetter, type AgentNativeHostInit, type AgentNativeHostMessageType, type AgentNativeHostRequestOptions, type AgentNativeHostResourceContext, type AgentNativeHostRouteContext, type AgentNativeHostSelectionContext, type AgentNativeHostSession, type AgentNativeJsonSchema, type AgentNativeScreenSnapshot, type AgentNativeScreenSnapshotOptions, type BuiltInAgentNativeHostCommand, } from "../host-bridge.js";
12
12
  export { AgentNativeWebMcpUnsupportedError, createAgentNativeWebMcpClient, createAgentNativeWebMcpRegistration, initializeAgentNativeWebMcp, isAgentNativeWebMcpSupported, type AgentNativeWebMcpApprovalRequest, type AgentNativeWebMcpClient, type AgentNativeWebMcpClientOptions, type AgentNativeWebMcpRegistration, type AgentNativeWebMcpRegistrationOptions, type AgentNativeWebMcpTool, type AgentNativeWebMcpToolAnnotations, type AgentNativeWebMcpToolExecutionOptions, type AgentNativeWebMcpToolResult, } from "../webmcp.js";
@@ -1,12 +1,12 @@
1
1
  export { initializeAgentNativeClient } from "../client-bootstrap.js";
2
2
  export { agentNativeApiDisabledReason, AgentNativeApiDisabledError, setAgentNativeApiDisabled, } from "../api-surface.js";
3
- export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, } from "../embed-auth.js";
3
+ export { ensureEmbedAuthFetchInterceptor, getEmbedAuthToken, isEmbedAuthActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, isMcpDirectoryWidgetWriteEmbed, } from "../embed-auth.js";
4
4
  export { sendToFrame, onFrameMessage, requestUserInfo, getFrameOrigin, getFramePostMessageTargetOrigin, getCallbackOrigin, oauthRedirectUri, isInFrame, isTrustedFrameMessage, enterStyleEditing, enterTextEditing, exitSelectionMode, } from "../frame.js";
5
5
  export { getBuilderParentOrigin, isInBuilderFrame, sendToBuilderChat, shouldParentFrameOwnAgentPanel, isTrustedBuilderMessage, tryDelegateBuildRequestToBuilder, } from "../builder-frame.js";
6
6
  export { getClientSurface } from "../client-surface.js";
7
7
  export { defineClientAction, } from "../client-action.js";
8
8
  export { DESKTOP_LOCAL_CODE_CHANGE_EVENT, requestDesktopLocalCodeChange, } from "../desktop-local-code-change.js";
9
- export { buildSessionReplayIframeBootstrap, injectSessionReplayIframeBootstrap, } from "../../extensions/session-replay-iframe.js";
9
+ export { buildSessionReplayIframeBootstrap, injectSessionReplayIframeBootstrap, RRWEB_RECORD_IFRAME_CDN_URL, } from "../../extensions/session-replay-iframe.js";
10
10
  export { SESSION_REPLAY_IFRAME_ATTRIBUTE, SESSION_REPLAY_IFRAME_PROBE, SESSION_REPLAY_IFRAME_START, SESSION_REPLAY_IFRAME_STOP, } from "../../session-replay-iframe-protocol.js";
11
11
  export { AGENT_NATIVE_HOST_BRIDGE_VERSION, AGENT_NATIVE_HOST_MESSAGE_TYPES, announceAgentNativeFrameReady, createAgentNativeHostBridge, defaultAgentNativeHostCommands, onAgentNativeHostInit, readAgentNativeScreenContext, requestAgentNativeHostActions, requestAgentNativeHostContext, requestAgentNativeHostWebMcpTools, runAgentNativeHostAction, runAgentNativeHostWebMcpTool, sendAgentNativeHostCommand, } from "../host-bridge.js";
12
12
  export { AgentNativeWebMcpUnsupportedError, createAgentNativeWebMcpClient, createAgentNativeWebMcpRegistration, initializeAgentNativeWebMcp, isAgentNativeWebMcpSupported, } from "../webmcp.js";
@@ -64,6 +64,7 @@ export declare function useIsMcpAppWidgetEmbed(): boolean;
64
64
  * instead of showing errors for requests that can never land.
65
65
  */
66
66
  export declare function useIsMcpDirectoryWidgetReadOnlyEmbed(): boolean;
67
+ export declare function useIsMcpDirectoryWidgetWriteEmbed(): boolean;
67
68
  export declare function isOpenAiMcpAppHost(): boolean;
68
69
  export declare function initializeMcpAppHost(): Promise<boolean>;
69
70
  export declare function sendMcpAppHostMessage(chat: McpAppHostChatMessage): Promise<boolean | null> | false;
@@ -2,7 +2,7 @@ import { useSyncExternalStore } from "react";
2
2
  import { EMBED_MODE_QUERY_PARAM, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
3
3
  import { MCP_APP_HOST_FILL_ATTRIBUTE, mcpAppHostFillsContainer, } from "../shared/mcp-app-display.js";
4
4
  import { MCP_APP_WIDGET_EMBED_ATTRIBUTE } from "../shared/mcp-app-widget-embed.js";
5
- import { getEmbedAuthToken, hasMcpDirectoryWidgetCapabilityToken, isEmbedAuthActive, markEmbedMcpChatBridgeActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, readEmbedMcpChatBridgeFlagFromUrl, } from "./embed-auth.js";
5
+ import { getEmbedAuthToken, hasMcpDirectoryWidgetCapabilityToken, isEmbedAuthActive, markEmbedMcpChatBridgeActive, isEmbedMcpChatBridgeActive, isMcpDirectoryWidgetReadOnlyEmbed, isMcpDirectoryWidgetWriteEmbed, readEmbedMcpChatBridgeFlagFromUrl, } from "./embed-auth.js";
6
6
  import { getFrameOrigin } from "./frame.js";
7
7
  export const AGENT_NATIVE_MCP_APP_HOST_MESSAGE_TYPES = {
8
8
  HOST_CONTEXT: "agentNative.mcpHostContext",
@@ -410,6 +410,9 @@ export function useIsMcpAppWidgetEmbed() {
410
410
  export function useIsMcpDirectoryWidgetReadOnlyEmbed() {
411
411
  return useSyncExternalStore(() => () => { }, isMcpDirectoryWidgetReadOnlyEmbed, () => false);
412
412
  }
413
+ export function useIsMcpDirectoryWidgetWriteEmbed() {
414
+ return useSyncExternalStore(() => () => { }, isMcpDirectoryWidgetWriteEmbed, () => false);
415
+ }
413
416
  export function isOpenAiMcpAppHost() {
414
417
  return (isInChildFrame() && isMcpAppBridgeEnabled() && readOpenAiBridge() !== null);
415
418
  }
@@ -2005,7 +2005,38 @@ function installSessionReplayIframeBridge(state, options) {
2005
2005
  const stopMessage = {
2006
2006
  type: SESSION_REPLAY_IFRAME_STOP,
2007
2007
  };
2008
- const sendStart = (iframe) => postSessionReplayIframeMessage(iframe, startMessage);
2008
+ const recordingFrames = new Set();
2009
+ const startFrame = (iframe, retry = false) => {
2010
+ if (!state.active ||
2011
+ !iframe.hasAttribute(SESSION_REPLAY_IFRAME_ATTRIBUTE)) {
2012
+ return;
2013
+ }
2014
+ if (recordingFrames.has(iframe)) {
2015
+ if (retry)
2016
+ postSessionReplayIframeMessage(iframe, startMessage);
2017
+ return;
2018
+ }
2019
+ recordingFrames.add(iframe);
2020
+ postSessionReplayIframeMessage(iframe, startMessage);
2021
+ };
2022
+ const stopFrame = (iframe) => {
2023
+ if (!recordingFrames.delete(iframe))
2024
+ return;
2025
+ postSessionReplayIframeMessage(iframe, stopMessage);
2026
+ };
2027
+ const iframesIn = (node) => {
2028
+ if (node.nodeType !== 1 && node.nodeType !== 11)
2029
+ return [];
2030
+ const element = node;
2031
+ const frames = [];
2032
+ if (element.nodeType === 1 && element.localName === "iframe") {
2033
+ frames.push(element);
2034
+ }
2035
+ if (typeof element.querySelectorAll === "function") {
2036
+ frames.push(...Array.from(element.querySelectorAll("iframe")));
2037
+ }
2038
+ return frames;
2039
+ };
2009
2040
  const onMessage = (event) => {
2010
2041
  if (!state.active)
2011
2042
  return;
@@ -2016,16 +2047,49 @@ function installSessionReplayIframeBridge(state, options) {
2016
2047
  }
2017
2048
  const iframe = markedSessionReplayIframeForSource(event.source);
2018
2049
  if (iframe)
2019
- sendStart(iframe);
2050
+ startFrame(iframe, true);
2020
2051
  };
2021
2052
  window.addEventListener("message", onMessage);
2053
+ const observer = typeof MutationObserver === "undefined" || !document.documentElement
2054
+ ? null
2055
+ : new MutationObserver((records) => {
2056
+ for (const record of records) {
2057
+ if (record.type === "attributes") {
2058
+ const target = record.target;
2059
+ if (target.localName !== "iframe")
2060
+ continue;
2061
+ const iframe = target;
2062
+ if (iframe.hasAttribute(SESSION_REPLAY_IFRAME_ATTRIBUTE)) {
2063
+ startFrame(iframe);
2064
+ }
2065
+ else {
2066
+ stopFrame(iframe);
2067
+ }
2068
+ continue;
2069
+ }
2070
+ for (const removed of Array.from(record.removedNodes)) {
2071
+ for (const iframe of iframesIn(removed))
2072
+ stopFrame(iframe);
2073
+ }
2074
+ for (const added of Array.from(record.addedNodes)) {
2075
+ for (const iframe of iframesIn(added))
2076
+ startFrame(iframe);
2077
+ }
2078
+ }
2079
+ });
2080
+ observer?.observe(document.documentElement, {
2081
+ attributes: true,
2082
+ attributeFilter: [SESSION_REPLAY_IFRAME_ATTRIBUTE],
2083
+ childList: true,
2084
+ subtree: true,
2085
+ });
2022
2086
  for (const iframe of markedSessionReplayIframes())
2023
- sendStart(iframe);
2087
+ startFrame(iframe);
2024
2088
  state.restoreIframeBridge = () => {
2025
2089
  window.removeEventListener("message", onMessage);
2026
- for (const iframe of markedSessionReplayIframes()) {
2027
- postSessionReplayIframeMessage(iframe, stopMessage);
2028
- }
2090
+ observer?.disconnect();
2091
+ for (const iframe of recordingFrames)
2092
+ stopFrame(iframe);
2029
2093
  state.restoreIframeBridge = null;
2030
2094
  };
2031
2095
  }