@agent-native/core 0.101.7 → 0.101.10
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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +22 -0
- package/corpus/core/docs/content/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
- package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
- package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
- package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
- package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
- package/corpus/core/src/server/auth.ts +25 -1
- package/corpus/core/src/server/builder-browser.ts +353 -1
- package/corpus/core/src/server/core-routes-plugin.ts +601 -223
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
- package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
- package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
- package/corpus/templates/content/AGENTS.md +3 -2
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
- package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
- package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
- package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +1041 -154
- package/corpus/templates/content/actions/_database-utils.ts +89 -12
- package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
- package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
- package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
- package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
- package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2353 -576
- package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
- package/corpus/templates/content/app/global.css +18 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
- package/corpus/templates/content/app/i18n/zh-TW.ts +25 -0
- package/corpus/templates/content/app/i18n-data.ts +287 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
- package/corpus/templates/content/parity/matrix.md +27 -25
- package/corpus/templates/content/parity/matrix.ts +50 -2
- package/corpus/templates/content/server/db/schema.ts +47 -0
- package/corpus/templates/content/server/plugins/auth.ts +1 -0
- package/corpus/templates/content/server/plugins/db.ts +53 -0
- package/corpus/templates/content/shared/api.ts +70 -1
- package/corpus/templates/content/shared/builder-mdx.ts +705 -15
- package/corpus/templates/content/shared/nfm.ts +3 -3
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
- package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
- package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/design/app/i18n/en-US.ts +1 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
- package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
- package/corpus/templates/design/shared/review-anchor.ts +48 -0
- package/corpus/templates/plan/server/plan-mdx.ts +53 -7
- package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
- package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
- package/dist/client/review/ReviewThreadPanel.js +6 -4
- package/dist/client/review/ReviewThreadPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +11 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +8 -4
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +19 -6
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
- package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +19 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +78 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +246 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +41 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +260 -14
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/docs/content/deployment.mdx +9 -7
- package/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/package.json +2 -2
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
TooltipTrigger,
|
|
46
46
|
} from "../components/ui/tooltip.js";
|
|
47
47
|
import { TeamPage } from "../org/TeamPage.js";
|
|
48
|
+
import { BuilderConnectCard } from "../setup-connections/BuilderConnectCard.js";
|
|
48
49
|
import { callAction } from "../use-action.js";
|
|
49
50
|
import { uploadAvatar, useAvatarUrl } from "../use-avatar.js";
|
|
50
51
|
import { useDevMode } from "../use-dev-mode.js";
|
|
@@ -2578,6 +2579,7 @@ interface SettingsPanelContentProps extends SettingsPanelProps {
|
|
|
2578
2579
|
showCapabilityStrip?: boolean;
|
|
2579
2580
|
className?: string;
|
|
2580
2581
|
surface?: SettingsSurface;
|
|
2582
|
+
builderConnectionOwnedExternally?: boolean;
|
|
2581
2583
|
}
|
|
2582
2584
|
|
|
2583
2585
|
function SettingsPanelContent({
|
|
@@ -2591,8 +2593,11 @@ function SettingsPanelContent({
|
|
|
2591
2593
|
showCapabilityStrip = true,
|
|
2592
2594
|
className,
|
|
2593
2595
|
surface = "sidebar",
|
|
2596
|
+
builderConnectionOwnedExternally = false,
|
|
2594
2597
|
}: SettingsPanelContentProps) {
|
|
2595
|
-
const { status: builder, loading: builderLoading } = useBuilderStatus(
|
|
2598
|
+
const { status: builder, loading: builderLoading } = useBuilderStatus({
|
|
2599
|
+
enabled: !builderConnectionOwnedExternally,
|
|
2600
|
+
});
|
|
2596
2601
|
const connected = builder?.configured ?? false;
|
|
2597
2602
|
const connectUrl = builder?.cliAuthUrl ?? builder?.connectUrl;
|
|
2598
2603
|
const orgName = builder?.orgName;
|
|
@@ -2600,6 +2605,7 @@ function SettingsPanelContent({
|
|
|
2600
2605
|
const credentialSource = builder?.credentialSource;
|
|
2601
2606
|
const builderBranchesAvailable = !!builder?.builderEnabled;
|
|
2602
2607
|
const builderFlow = useBuilderConnectFlow({
|
|
2608
|
+
enabled: !builderConnectionOwnedExternally,
|
|
2603
2609
|
popupUrl: connectUrl,
|
|
2604
2610
|
trackingSource: "settings_panel_builder_card",
|
|
2605
2611
|
});
|
|
@@ -2965,20 +2971,22 @@ function SettingsPanelContent({
|
|
|
2965
2971
|
icon={<IconBrowser size={14} />}
|
|
2966
2972
|
title="Browser Automation"
|
|
2967
2973
|
subtitle="Let agents control a real browser for web tasks."
|
|
2968
|
-
connected={connected}
|
|
2974
|
+
connected={builderConnectionOwnedExternally ? undefined : connected}
|
|
2969
2975
|
open={openSection === "browser"}
|
|
2970
2976
|
onToggle={() => toggle("browser")}
|
|
2971
2977
|
>
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2978
|
+
{!builderConnectionOwnedExternally ? (
|
|
2979
|
+
<UseBuilderCard
|
|
2980
|
+
builderFlow={builderFlow}
|
|
2981
|
+
connectUrl={connectUrl}
|
|
2982
|
+
connected={connected}
|
|
2983
|
+
orgName={orgName}
|
|
2984
|
+
envManaged={envManaged}
|
|
2985
|
+
credentialSource={credentialSource}
|
|
2986
|
+
trackingSource="browser_settings"
|
|
2987
|
+
trackingFlow="browser_automation"
|
|
2988
|
+
/>
|
|
2989
|
+
) : null}
|
|
2982
2990
|
</SettingsSection>
|
|
2983
2991
|
)}
|
|
2984
2992
|
|
|
@@ -3057,6 +3065,26 @@ export function SettingsPanel(props: SettingsPanelProps) {
|
|
|
3057
3065
|
return <SettingsPanelContent {...props} />;
|
|
3058
3066
|
}
|
|
3059
3067
|
|
|
3068
|
+
export function ConnectionsSettingsContent({
|
|
3069
|
+
settingsPanelProps,
|
|
3070
|
+
}: {
|
|
3071
|
+
settingsPanelProps: SettingsPanelProps;
|
|
3072
|
+
}) {
|
|
3073
|
+
return (
|
|
3074
|
+
<div className="mx-auto w-full max-w-2xl space-y-4">
|
|
3075
|
+
<BuilderConnectCard trackingSource="settings_connections" />
|
|
3076
|
+
<SettingsPanelContent
|
|
3077
|
+
{...settingsPanelProps}
|
|
3078
|
+
surface="page"
|
|
3079
|
+
sections={CONNECTION_SETTINGS_SECTIONS}
|
|
3080
|
+
showCapabilityStrip={false}
|
|
3081
|
+
className="w-full"
|
|
3082
|
+
builderConnectionOwnedExternally
|
|
3083
|
+
/>
|
|
3084
|
+
</div>
|
|
3085
|
+
);
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3060
3088
|
export function useAgentSettingsTabs(): SettingsTabItem[] {
|
|
3061
3089
|
const { isDevMode, canToggle, setDevMode } = useDevMode();
|
|
3062
3090
|
const baseProps = useMemo<SettingsPanelProps>(
|
|
@@ -3102,15 +3130,7 @@ export function useAgentSettingsTabs(): SettingsTabItem[] {
|
|
|
3102
3130
|
...connections,
|
|
3103
3131
|
icon: IconPlugConnected,
|
|
3104
3132
|
group: "agent",
|
|
3105
|
-
content:
|
|
3106
|
-
<SettingsPanelContent
|
|
3107
|
-
{...baseProps}
|
|
3108
|
-
surface="page"
|
|
3109
|
-
sections={CONNECTION_SETTINGS_SECTIONS}
|
|
3110
|
-
showCapabilityStrip={false}
|
|
3111
|
-
className="mx-auto w-full max-w-2xl"
|
|
3112
|
-
/>
|
|
3113
|
-
),
|
|
3133
|
+
content: <ConnectionsSettingsContent settingsPanelProps={baseProps} />,
|
|
3114
3134
|
},
|
|
3115
3135
|
{
|
|
3116
3136
|
...organization,
|
|
@@ -53,10 +53,14 @@ export interface BuilderStatus {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
* Fetches Builder connection status from
|
|
56
|
+
* Fetches Builder connection status from the neutral connection-status route.
|
|
57
|
+
* The legacy /_agent-native/builder/status route remains available for older
|
|
58
|
+
* clients.
|
|
57
59
|
* Re-fetches on window focus to detect post-redirect state changes.
|
|
58
60
|
*/
|
|
59
|
-
export function useBuilderStatus(
|
|
61
|
+
export function useBuilderStatus({
|
|
62
|
+
enabled = true,
|
|
63
|
+
}: { enabled?: boolean } = {}) {
|
|
60
64
|
const [status, setStatus] = useState<BuilderStatus | null>(null);
|
|
61
65
|
const [loading, setLoading] = useState(true);
|
|
62
66
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -64,6 +68,7 @@ export function useBuilderStatus() {
|
|
|
64
68
|
const lastGoodStatusRef = useRef<BuilderStatus | null>(null);
|
|
65
69
|
|
|
66
70
|
const fetchStatus = useCallback(async () => {
|
|
71
|
+
if (!enabled) return;
|
|
67
72
|
const keepLastGoodStatus = (message: string) => {
|
|
68
73
|
const lastGoodStatus = lastGoodStatusRef.current;
|
|
69
74
|
setStatus(lastGoodStatus);
|
|
@@ -72,7 +77,9 @@ export function useBuilderStatus() {
|
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
try {
|
|
75
|
-
const res = await fetch(
|
|
80
|
+
const res = await fetch(
|
|
81
|
+
agentNativePath("/_agent-native/connection-status/builder"),
|
|
82
|
+
);
|
|
76
83
|
if (!res.ok) {
|
|
77
84
|
keepLastGoodStatus(`Builder status unavailable (${res.status})`);
|
|
78
85
|
return;
|
|
@@ -91,9 +98,18 @@ export function useBuilderStatus() {
|
|
|
91
98
|
} finally {
|
|
92
99
|
setLoading(false);
|
|
93
100
|
}
|
|
94
|
-
}, []);
|
|
101
|
+
}, [enabled]);
|
|
95
102
|
|
|
96
103
|
useEffect(() => {
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
setStatus(null);
|
|
106
|
+
setLoading(false);
|
|
107
|
+
setError(null);
|
|
108
|
+
setStale(false);
|
|
109
|
+
lastGoodStatusRef.current = null;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
setLoading(true);
|
|
97
113
|
fetchStatus();
|
|
98
114
|
|
|
99
115
|
function onFocus() {
|
|
@@ -115,7 +131,7 @@ export function useBuilderStatus() {
|
|
|
115
131
|
fetchStatus,
|
|
116
132
|
);
|
|
117
133
|
};
|
|
118
|
-
}, [fetchStatus]);
|
|
134
|
+
}, [enabled, fetchStatus]);
|
|
119
135
|
|
|
120
136
|
return { status, loading, error, stale, refetch: fetchStatus };
|
|
121
137
|
}
|
|
@@ -123,7 +139,7 @@ export function useBuilderStatus() {
|
|
|
123
139
|
// ─── useBuilderConnectFlow ──────────────────────────────────────────────────
|
|
124
140
|
//
|
|
125
141
|
// Shared state machine for the "open Builder CLI-auth popup + poll
|
|
126
|
-
// /builder
|
|
142
|
+
// /connection-status/builder until credentials land" interaction. Replaces three
|
|
127
143
|
// near-duplicate inline implementations: `BuilderCliAuthMethod` in
|
|
128
144
|
// OnboardingPanel, `ConnectBuilderCard`, and `BuilderConnectCta` in
|
|
129
145
|
// AssistantChat. Each consumer supplies its own popup URL / completion
|
|
@@ -158,7 +174,7 @@ export interface BuilderConnectStartOptions {
|
|
|
158
174
|
|
|
159
175
|
export interface BuilderConnectFlow {
|
|
160
176
|
configured: boolean;
|
|
161
|
-
/** True after at least one successful
|
|
177
|
+
/** True after at least one successful Builder connection-status response. */
|
|
162
178
|
statusResolved: boolean;
|
|
163
179
|
/**
|
|
164
180
|
* True when the deploy has BUILDER_PRIVATE_KEY set as a fallback. Connect
|
|
@@ -176,7 +192,7 @@ export interface BuilderConnectFlow {
|
|
|
176
192
|
connecting: boolean;
|
|
177
193
|
error: string | null;
|
|
178
194
|
/**
|
|
179
|
-
* True once the first
|
|
195
|
+
* True once the first Builder connection-status fetch has completed (successfully
|
|
180
196
|
* or not). Consumers that accept an `initialConfigured` prop (e.g. agent
|
|
181
197
|
* tool-call results rendered with server-side state) should treat
|
|
182
198
|
* `configured`/`orgName` as authoritative only once this flips true —
|
|
@@ -579,7 +595,10 @@ export function useBuilderConnectFlow(
|
|
|
579
595
|
const origin = getCallbackOrigin() || window.location.origin;
|
|
580
596
|
try {
|
|
581
597
|
const r = await fetch(
|
|
582
|
-
new URL(
|
|
598
|
+
new URL(
|
|
599
|
+
agentNativePath("/_agent-native/connection-status/builder"),
|
|
600
|
+
origin,
|
|
601
|
+
).href,
|
|
583
602
|
);
|
|
584
603
|
if (!r.ok) return null;
|
|
585
604
|
return (await r.json()) as {
|
|
@@ -30,9 +30,7 @@ export function BuilderConnectCard({
|
|
|
30
30
|
? "Checking"
|
|
31
31
|
: flow.configured
|
|
32
32
|
? connectedLabel
|
|
33
|
-
:
|
|
34
|
-
? "Ready to connect"
|
|
35
|
-
: "Project not configured";
|
|
33
|
+
: "Ready to connect";
|
|
36
34
|
|
|
37
35
|
return (
|
|
38
36
|
<section
|
|
@@ -73,10 +71,7 @@ export function BuilderConnectCard({
|
|
|
73
71
|
<button
|
|
74
72
|
type="button"
|
|
75
73
|
onClick={() => flow.start()}
|
|
76
|
-
disabled={
|
|
77
|
-
flow.connecting ||
|
|
78
|
-
(!flow.builderEnabled && flow.hasFetchedStatus)
|
|
79
|
-
}
|
|
74
|
+
disabled={flow.connecting}
|
|
80
75
|
className="mt-3 inline-flex h-8 items-center gap-2 rounded-md border border-border bg-foreground px-3 text-xs font-medium text-background transition-colors hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60"
|
|
81
76
|
>
|
|
82
77
|
{flow.connecting ? (
|
|
@@ -104,9 +104,12 @@ import type { BetterAuthConfig } from "./better-auth-instance.js";
|
|
|
104
104
|
import {
|
|
105
105
|
BUILDER_CONNECT_OWNER_COOKIE,
|
|
106
106
|
BUILDER_CONNECT_PARAM,
|
|
107
|
+
BUILDER_RELAY_PATH,
|
|
108
|
+
BUILDER_RELAY_STATE_PARAM,
|
|
107
109
|
BUILDER_STATE_PARAM,
|
|
108
110
|
verifyBuilderCallbackStateAndGetOwner,
|
|
109
111
|
verifyBuilderConnectTokenAndGetOwner,
|
|
112
|
+
verifyBuilderPreviewRelayStateForCallback,
|
|
110
113
|
} from "./builder-browser.js";
|
|
111
114
|
import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
|
|
112
115
|
import {
|
|
@@ -1522,7 +1525,15 @@ function verifiedBuilderConnectOwnerFromUrl(url: string): string | null {
|
|
|
1522
1525
|
return verifyBuilderConnectTokenAndGetOwner(token);
|
|
1523
1526
|
}
|
|
1524
1527
|
|
|
1525
|
-
function shouldBypassAuthForBuilderConnect(
|
|
1528
|
+
export function shouldBypassAuthForBuilderConnect(
|
|
1529
|
+
event: H3Event,
|
|
1530
|
+
p: string,
|
|
1531
|
+
): boolean {
|
|
1532
|
+
// The preview-safe second hop is authenticated by its timestamped HMAC and
|
|
1533
|
+
// one-shot pending row. It cannot carry a browser session from the corporate
|
|
1534
|
+
// callback deployment, so let the route perform that stronger check itself.
|
|
1535
|
+
if (p === BUILDER_RELAY_PATH) return true;
|
|
1536
|
+
|
|
1526
1537
|
if (p === "/_agent-native/builder/connect") {
|
|
1527
1538
|
const url = event.node?.req?.url ?? event.path ?? "/";
|
|
1528
1539
|
return Boolean(verifiedBuilderConnectOwnerFromUrl(url));
|
|
@@ -1537,6 +1548,19 @@ function shouldBypassAuthForBuilderConnect(event: H3Event, p: string): boolean {
|
|
|
1537
1548
|
BUILDER_STATE_PARAM,
|
|
1538
1549
|
)
|
|
1539
1550
|
: null;
|
|
1551
|
+
const relayState =
|
|
1552
|
+
queryStart >= 0
|
|
1553
|
+
? new URLSearchParams(url.slice(queryStart + 1)).get(
|
|
1554
|
+
BUILDER_RELAY_STATE_PARAM,
|
|
1555
|
+
)
|
|
1556
|
+
: null;
|
|
1557
|
+
if (relayState) {
|
|
1558
|
+
try {
|
|
1559
|
+
if (verifyBuilderPreviewRelayStateForCallback(relayState)) return true;
|
|
1560
|
+
} catch {
|
|
1561
|
+
// Dedicated relay secret missing: let the auth guard fail closed.
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1540
1564
|
// The signed `_an_state` authenticates this specific Builder callback
|
|
1541
1565
|
// flow back to our app. A stale localhost session cookie can otherwise
|
|
1542
1566
|
// make the global guard reject the callback before the handler gets to
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createHash,
|
|
3
|
+
createHmac,
|
|
4
|
+
randomBytes,
|
|
5
|
+
timingSafeEqual,
|
|
6
|
+
} from "node:crypto";
|
|
2
7
|
|
|
3
8
|
import type { H3Event } from "h3";
|
|
4
9
|
import { getHeader } from "h3";
|
|
@@ -15,6 +20,349 @@ const BUILDER_BROWSER_HOST = "agent-native-browser";
|
|
|
15
20
|
const BUILDER_BROWSER_CLIENT_ID = "Agent Native Browser";
|
|
16
21
|
|
|
17
22
|
export const BUILDER_CALLBACK_PATH = "/_agent-native/builder/callback";
|
|
23
|
+
export const BUILDER_RELAY_PATH = "/_agent-native/builder/relay";
|
|
24
|
+
export const BUILDER_RELAY_STATE_PARAM = "_an_relay";
|
|
25
|
+
export const BUILDER_RELAY_SECRET_ENV = "AGENT_NATIVE_BUILDER_RELAY_SECRET";
|
|
26
|
+
export const BUILDER_RELAY_TARGET_ORIGINS_ENV =
|
|
27
|
+
"AGENT_NATIVE_BUILDER_RELAY_TARGET_ORIGINS";
|
|
28
|
+
export const BUILDER_RELAY_TIMESTAMP_HEADER = "x-agent-native-relay-timestamp";
|
|
29
|
+
export const BUILDER_RELAY_FLOW_HEADER = "x-agent-native-relay-flow";
|
|
30
|
+
export const BUILDER_RELAY_SIGNATURE_HEADER = "x-agent-native-relay-signature";
|
|
31
|
+
|
|
32
|
+
const BUILDER_RELAY_PURPOSE = "builder-preview-callback-relay";
|
|
33
|
+
const BUILDER_RELAY_STATE_VERSION = 1;
|
|
34
|
+
const BUILDER_RELAY_TTL_MS = 10 * 60 * 1000;
|
|
35
|
+
const BUILDER_RELAY_REQUEST_SKEW_MS = 2 * 60 * 1000;
|
|
36
|
+
|
|
37
|
+
export interface BuilderPreviewRelayState {
|
|
38
|
+
v: 1;
|
|
39
|
+
purpose: typeof BUILDER_RELAY_PURPOSE;
|
|
40
|
+
flowId: string;
|
|
41
|
+
ownerEmail: string;
|
|
42
|
+
targetOrigin: string;
|
|
43
|
+
basePath: string;
|
|
44
|
+
iat: number;
|
|
45
|
+
exp: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface BuilderRelayCredentials {
|
|
49
|
+
privateKey: string;
|
|
50
|
+
publicKey: string;
|
|
51
|
+
userId: string | null;
|
|
52
|
+
orgName: string | null;
|
|
53
|
+
orgKind: string | null;
|
|
54
|
+
subscription: string | null;
|
|
55
|
+
subscriptionLevel: string | null;
|
|
56
|
+
subscriptionName: string | null;
|
|
57
|
+
isEnterprise: boolean | null;
|
|
58
|
+
isFreeAccount: boolean | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BuilderRelayRequestBody {
|
|
62
|
+
relayState: string;
|
|
63
|
+
credentials: BuilderRelayCredentials;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function builderRelaySecret(): string {
|
|
67
|
+
const secret = process.env[BUILDER_RELAY_SECRET_ENV]?.trim();
|
|
68
|
+
if (!secret) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${BUILDER_RELAY_SECRET_ENV} is required for Builder preview authorization relay.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (secret.length < 32) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${BUILDER_RELAY_SECRET_ENV} must be at least 32 characters long.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return secret;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function builderRelayMac(value: string): string {
|
|
82
|
+
return createHmac("sha256", builderRelaySecret())
|
|
83
|
+
.update(value)
|
|
84
|
+
.digest("base64url");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function safeEqualText(expected: string, actual: string): boolean {
|
|
88
|
+
const expectedBuffer = Buffer.from(expected);
|
|
89
|
+
const actualBuffer = Buffer.from(actual);
|
|
90
|
+
return (
|
|
91
|
+
expectedBuffer.length === actualBuffer.length &&
|
|
92
|
+
timingSafeEqual(expectedBuffer, actualBuffer)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeBuilderRelayBasePath(value: string): string | null {
|
|
97
|
+
if (!value) return "";
|
|
98
|
+
if (!value.startsWith("/") || value.includes("?") || value.includes("#")) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const normalized = value.replace(/\/+$/, "");
|
|
102
|
+
if (normalized.split("/").some((part) => part === "." || part === "..")) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isSafeBuilderRelayTargetOrigin(value: string): boolean {
|
|
109
|
+
try {
|
|
110
|
+
const url = new URL(value);
|
|
111
|
+
if (
|
|
112
|
+
url.origin !== value ||
|
|
113
|
+
url.username ||
|
|
114
|
+
url.password ||
|
|
115
|
+
url.hostname.includes("*")
|
|
116
|
+
)
|
|
117
|
+
return false;
|
|
118
|
+
const hostname = url.hostname.toLowerCase();
|
|
119
|
+
const loopback =
|
|
120
|
+
hostname === "localhost" ||
|
|
121
|
+
hostname === "127.0.0.1" ||
|
|
122
|
+
hostname === "::1" ||
|
|
123
|
+
hostname === "[::1]";
|
|
124
|
+
if (loopback)
|
|
125
|
+
return url.protocol === "http:" && process.env.NODE_ENV !== "production";
|
|
126
|
+
if (url.protocol !== "https:") return false;
|
|
127
|
+
return (
|
|
128
|
+
hostname.endsWith(".netlify.app") ||
|
|
129
|
+
hostname.endsWith(".vercel.app") ||
|
|
130
|
+
hostname === "agent-native.com" ||
|
|
131
|
+
hostname.endsWith(".agent-native.com") ||
|
|
132
|
+
hostname.endsWith(".builder.io") ||
|
|
133
|
+
hostname.endsWith(".builderio.xyz") ||
|
|
134
|
+
hostname.endsWith(".builderio.dev") ||
|
|
135
|
+
hostname.endsWith(".builder.codes")
|
|
136
|
+
);
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isTrustedBuilderRelayTargetOrigin(value: string): boolean {
|
|
143
|
+
if (!isSafeBuilderRelayTargetOrigin(value)) return false;
|
|
144
|
+
const hostname = new URL(value).hostname.toLowerCase();
|
|
145
|
+
if (
|
|
146
|
+
hostname.endsWith(".netlify.app") &&
|
|
147
|
+
!/^[a-f0-9]{24}--[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.netlify\.app$/.test(
|
|
148
|
+
hostname,
|
|
149
|
+
)
|
|
150
|
+
) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const configured = process.env[BUILDER_RELAY_TARGET_ORIGINS_ENV];
|
|
154
|
+
if (!configured) return false;
|
|
155
|
+
return configured
|
|
156
|
+
.split(",")
|
|
157
|
+
.map((origin) => origin.trim())
|
|
158
|
+
.filter(Boolean)
|
|
159
|
+
.some((origin) => origin === value && !origin.includes("*"));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function signBuilderPreviewRelayState(input: {
|
|
163
|
+
ownerEmail: string;
|
|
164
|
+
targetOrigin: string;
|
|
165
|
+
basePath?: string;
|
|
166
|
+
flowId?: string;
|
|
167
|
+
now?: number;
|
|
168
|
+
}): { state: string; payload: BuilderPreviewRelayState } {
|
|
169
|
+
if (!isSafeBuilderRelayTargetOrigin(input.targetOrigin)) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"Builder relay target origin is not an approved preview origin.",
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const basePath = normalizeBuilderRelayBasePath(input.basePath ?? "");
|
|
175
|
+
if (basePath === null) throw new Error("Builder relay base path is invalid.");
|
|
176
|
+
const now = input.now ?? Date.now();
|
|
177
|
+
const payload: BuilderPreviewRelayState = {
|
|
178
|
+
v: BUILDER_RELAY_STATE_VERSION,
|
|
179
|
+
purpose: BUILDER_RELAY_PURPOSE,
|
|
180
|
+
flowId: input.flowId ?? randomBytes(24).toString("base64url"),
|
|
181
|
+
ownerEmail: input.ownerEmail,
|
|
182
|
+
targetOrigin: input.targetOrigin,
|
|
183
|
+
basePath,
|
|
184
|
+
iat: now,
|
|
185
|
+
exp: now + BUILDER_RELAY_TTL_MS,
|
|
186
|
+
};
|
|
187
|
+
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString(
|
|
188
|
+
"base64url",
|
|
189
|
+
);
|
|
190
|
+
return { state: `${encoded}.${builderRelayMac(encoded)}`, payload };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function verifyBuilderPreviewRelayState(
|
|
194
|
+
state: string | null | undefined,
|
|
195
|
+
options: { now?: number } = {},
|
|
196
|
+
): BuilderPreviewRelayState | null {
|
|
197
|
+
if (!state) return null;
|
|
198
|
+
const parts = state.split(".");
|
|
199
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
200
|
+
if (!safeEqualText(builderRelayMac(parts[0]), parts[1])) return null;
|
|
201
|
+
let value: unknown;
|
|
202
|
+
try {
|
|
203
|
+
value = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
if (!value || typeof value !== "object") return null;
|
|
208
|
+
const payload = value as Partial<BuilderPreviewRelayState>;
|
|
209
|
+
const now = options.now ?? Date.now();
|
|
210
|
+
if (
|
|
211
|
+
payload.v !== BUILDER_RELAY_STATE_VERSION ||
|
|
212
|
+
payload.purpose !== BUILDER_RELAY_PURPOSE ||
|
|
213
|
+
typeof payload.flowId !== "string" ||
|
|
214
|
+
!/^[A-Za-z0-9_-]{24,128}$/.test(payload.flowId) ||
|
|
215
|
+
typeof payload.ownerEmail !== "string" ||
|
|
216
|
+
!payload.ownerEmail.includes("@") ||
|
|
217
|
+
typeof payload.targetOrigin !== "string" ||
|
|
218
|
+
!isSafeBuilderRelayTargetOrigin(payload.targetOrigin) ||
|
|
219
|
+
typeof payload.basePath !== "string" ||
|
|
220
|
+
normalizeBuilderRelayBasePath(payload.basePath) !== payload.basePath ||
|
|
221
|
+
typeof payload.iat !== "number" ||
|
|
222
|
+
typeof payload.exp !== "number" ||
|
|
223
|
+
payload.exp <= payload.iat ||
|
|
224
|
+
payload.exp - payload.iat > BUILDER_RELAY_TTL_MS ||
|
|
225
|
+
payload.iat > now + BUILDER_RELAY_REQUEST_SKEW_MS ||
|
|
226
|
+
payload.exp < now
|
|
227
|
+
) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
return payload as BuilderPreviewRelayState;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Corporate callback trust check. Preview-side state verification and relay
|
|
235
|
+
* receipt deliberately do not require this callback-only allowlist.
|
|
236
|
+
*/
|
|
237
|
+
export function verifyBuilderPreviewRelayStateForCallback(
|
|
238
|
+
state: string | null | undefined,
|
|
239
|
+
options: { now?: number } = {},
|
|
240
|
+
): BuilderPreviewRelayState | null {
|
|
241
|
+
const payload = verifyBuilderPreviewRelayState(state, options);
|
|
242
|
+
return payload && isTrustedBuilderRelayTargetOrigin(payload.targetOrigin)
|
|
243
|
+
? payload
|
|
244
|
+
: null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getBuilderPreviewRelayUrl(
|
|
248
|
+
payload: BuilderPreviewRelayState,
|
|
249
|
+
): string {
|
|
250
|
+
return `${payload.targetOrigin}${payload.basePath}${BUILDER_RELAY_PATH}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function builderRelayBodyDigest(body: string): string {
|
|
254
|
+
return createHash("sha256").update(body).digest("base64url");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function builderRelayRequestSignature(
|
|
258
|
+
timestamp: number,
|
|
259
|
+
flowId: string,
|
|
260
|
+
body: string,
|
|
261
|
+
): string {
|
|
262
|
+
return builderRelayMac(
|
|
263
|
+
`v1.${timestamp}.${flowId}.${builderRelayBodyDigest(body)}`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function createBuilderRelayRequest(
|
|
268
|
+
relayState: string,
|
|
269
|
+
credentials: BuilderRelayCredentials,
|
|
270
|
+
options: { now?: number } = {},
|
|
271
|
+
): { body: string; headers: Record<string, string>; url: string } {
|
|
272
|
+
const payload = verifyBuilderPreviewRelayState(relayState, options);
|
|
273
|
+
if (!payload) throw new Error("Builder relay state is invalid or expired.");
|
|
274
|
+
const body = JSON.stringify({
|
|
275
|
+
relayState,
|
|
276
|
+
credentials,
|
|
277
|
+
} satisfies BuilderRelayRequestBody);
|
|
278
|
+
const timestamp = options.now ?? Date.now();
|
|
279
|
+
return {
|
|
280
|
+
body,
|
|
281
|
+
url: getBuilderPreviewRelayUrl(payload),
|
|
282
|
+
headers: {
|
|
283
|
+
"content-type": "application/json",
|
|
284
|
+
[BUILDER_RELAY_TIMESTAMP_HEADER]: String(timestamp),
|
|
285
|
+
[BUILDER_RELAY_FLOW_HEADER]: payload.flowId,
|
|
286
|
+
[BUILDER_RELAY_SIGNATURE_HEADER]: builderRelayRequestSignature(
|
|
287
|
+
timestamp,
|
|
288
|
+
payload.flowId,
|
|
289
|
+
body,
|
|
290
|
+
),
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function verifyBuilderRelayRequest(input: {
|
|
296
|
+
body: string;
|
|
297
|
+
timestamp: string | null | undefined;
|
|
298
|
+
flowId: string | null | undefined;
|
|
299
|
+
signature: string | null | undefined;
|
|
300
|
+
requestOrigin: string;
|
|
301
|
+
requestBasePath: string;
|
|
302
|
+
now?: number;
|
|
303
|
+
}): {
|
|
304
|
+
payload: BuilderPreviewRelayState;
|
|
305
|
+
body: BuilderRelayRequestBody;
|
|
306
|
+
} | null {
|
|
307
|
+
const timestamp = Number(input.timestamp);
|
|
308
|
+
const now = input.now ?? Date.now();
|
|
309
|
+
if (
|
|
310
|
+
!Number.isFinite(timestamp) ||
|
|
311
|
+
Math.abs(now - timestamp) > BUILDER_RELAY_REQUEST_SKEW_MS ||
|
|
312
|
+
!input.flowId ||
|
|
313
|
+
!input.signature ||
|
|
314
|
+
!safeEqualText(
|
|
315
|
+
builderRelayRequestSignature(timestamp, input.flowId, input.body),
|
|
316
|
+
input.signature,
|
|
317
|
+
)
|
|
318
|
+
) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
let body: BuilderRelayRequestBody;
|
|
322
|
+
try {
|
|
323
|
+
body = JSON.parse(input.body) as BuilderRelayRequestBody;
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const payload = verifyBuilderPreviewRelayState(body.relayState, { now });
|
|
328
|
+
if (
|
|
329
|
+
!payload ||
|
|
330
|
+
payload.flowId !== input.flowId ||
|
|
331
|
+
payload.targetOrigin !== input.requestOrigin ||
|
|
332
|
+
payload.basePath !== input.requestBasePath ||
|
|
333
|
+
!body.credentials ||
|
|
334
|
+
typeof body.credentials.privateKey !== "string" ||
|
|
335
|
+
typeof body.credentials.publicKey !== "string" ||
|
|
336
|
+
!body.credentials.privateKey ||
|
|
337
|
+
!body.credentials.publicKey
|
|
338
|
+
) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const nullableString = (value: unknown): string | null =>
|
|
342
|
+
typeof value === "string" ? value : null;
|
|
343
|
+
const nullableBoolean = (value: unknown): boolean | null =>
|
|
344
|
+
typeof value === "boolean" ? value : null;
|
|
345
|
+
return {
|
|
346
|
+
payload,
|
|
347
|
+
body: {
|
|
348
|
+
relayState: body.relayState,
|
|
349
|
+
// Explicitly rebuild the credential payload. Extra fields such as an
|
|
350
|
+
// attacker-supplied ownerEmail/orgId never reach the credential writer.
|
|
351
|
+
credentials: {
|
|
352
|
+
privateKey: body.credentials.privateKey,
|
|
353
|
+
publicKey: body.credentials.publicKey,
|
|
354
|
+
userId: nullableString(body.credentials.userId),
|
|
355
|
+
orgName: nullableString(body.credentials.orgName),
|
|
356
|
+
orgKind: nullableString(body.credentials.orgKind),
|
|
357
|
+
subscription: nullableString(body.credentials.subscription),
|
|
358
|
+
subscriptionLevel: nullableString(body.credentials.subscriptionLevel),
|
|
359
|
+
subscriptionName: nullableString(body.credentials.subscriptionName),
|
|
360
|
+
isEnterprise: nullableBoolean(body.credentials.isEnterprise),
|
|
361
|
+
isFreeAccount: nullableBoolean(body.credentials.isFreeAccount),
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
18
366
|
|
|
19
367
|
function escapeHtml(value: string): string {
|
|
20
368
|
return value.replace(/[&<>"']/g, (ch) => {
|
|
@@ -516,6 +864,7 @@ export function buildBuilderCliAuthUrl(
|
|
|
516
864
|
state: string | null = null,
|
|
517
865
|
options: {
|
|
518
866
|
previewOrigin?: string;
|
|
867
|
+
relayState?: string;
|
|
519
868
|
tracking?: BuilderConnectTrackingParams;
|
|
520
869
|
} = {},
|
|
521
870
|
): string {
|
|
@@ -536,6 +885,9 @@ export function buildBuilderCliAuthUrl(
|
|
|
536
885
|
if (state) {
|
|
537
886
|
callbackUrl.searchParams.set(BUILDER_STATE_PARAM, state);
|
|
538
887
|
}
|
|
888
|
+
if (options.relayState) {
|
|
889
|
+
callbackUrl.searchParams.set(BUILDER_RELAY_STATE_PARAM, options.relayState);
|
|
890
|
+
}
|
|
539
891
|
// When the cli-auth allow-list forces preview_url onto the gateway origin,
|
|
540
892
|
// the callback would otherwise lose the real opener origin and post its
|
|
541
893
|
// success message to the gateway instead of the preview tab. Embed the
|