@frontmcp/ui 1.3.0 → 1.4.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 (42) hide show
  1. package/auth/AuthPageWrapper.d.ts +56 -0
  2. package/auth/AuthPageWrapper.d.ts.map +1 -0
  3. package/auth/context.d.ts +23 -0
  4. package/auth/context.d.ts.map +1 -0
  5. package/auth/contract.d.ts +276 -0
  6. package/auth/contract.d.ts.map +1 -0
  7. package/auth/hooks.d.ts +83 -0
  8. package/auth/hooks.d.ts.map +1 -0
  9. package/auth/hydrate.d.ts +50 -0
  10. package/auth/hydrate.d.ts.map +1 -0
  11. package/auth/index.d.ts +23 -0
  12. package/auth/index.d.ts.map +1 -0
  13. package/auth/index.js +421 -0
  14. package/auth/vanilla/auth-flow.d.ts +96 -0
  15. package/auth/vanilla/auth-flow.d.ts.map +1 -0
  16. package/auth/vanilla/index.d.ts +14 -0
  17. package/auth/vanilla/index.d.ts.map +1 -0
  18. package/auth/vanilla/index.js +251 -0
  19. package/bridge/adapters/base-adapter.d.ts +2 -1
  20. package/bridge/adapters/base-adapter.d.ts.map +1 -1
  21. package/bridge/adapters/ext-apps.adapter.d.ts +6 -1
  22. package/bridge/adapters/ext-apps.adapter.d.ts.map +1 -1
  23. package/bridge/adapters/openai.adapter.d.ts +9 -1
  24. package/bridge/adapters/openai.adapter.d.ts.map +1 -1
  25. package/bridge/core/bridge-factory.d.ts +7 -2
  26. package/bridge/core/bridge-factory.d.ts.map +1 -1
  27. package/bridge/index.d.ts +1 -1
  28. package/bridge/index.d.ts.map +1 -1
  29. package/bridge/index.js +38 -0
  30. package/bridge/runtime/iife-generator.d.ts.map +1 -1
  31. package/bridge/types.d.ts +24 -0
  32. package/bridge/types.d.ts.map +1 -1
  33. package/components/Modal/Modal.d.ts +1 -1
  34. package/esm/auth/index.mjs +398 -0
  35. package/esm/auth/vanilla/index.mjs +228 -0
  36. package/esm/bridge/index.mjs +38 -0
  37. package/esm/index.mjs +8 -0
  38. package/esm/package.json +22 -2
  39. package/esm/react/index.mjs +8 -0
  40. package/index.js +8 -0
  41. package/package.json +22 -2
  42. package/react/index.js +8 -0
@@ -0,0 +1,398 @@
1
+ // libs/ui/src/auth/contract.ts
2
+ var AUTH_FLOW_GLOBAL_KEY = "__FRONTMCP_AUTH__";
3
+ var DEFAULT_SUBMIT_METHOD = "GET";
4
+ var AUTH_WIRE_FIELDS = {
5
+ /** Pending authorization id. */
6
+ pendingAuthId: "pending_auth_id",
7
+ /** Anti-CSRF token. */
8
+ csrf: "csrf",
9
+ /** Marks a consent-form submission (distinguishes empty-select from first visit). */
10
+ consentSubmitted: "consent_submitted",
11
+ /** Repeated checkbox field carrying selected tool ids on the consent slot. */
12
+ tools: "tools",
13
+ /** Marks a federated submission. */
14
+ federated: "federated",
15
+ /** Repeated checkbox field carrying selected provider ids on the federated slot. */
16
+ providers: "providers",
17
+ /** Marks an incremental authorization. */
18
+ incremental: "incremental",
19
+ /** Target app id for incremental authorization. */
20
+ appId: "app_id",
21
+ /** Generic action discriminator (e.g. consent `authorize`/`skip`, or an extra name). */
22
+ action: "action"
23
+ };
24
+ var CONSENT_SUBMITTED_VALUE = "1";
25
+ var WIRE_TRUE = "true";
26
+ var AUTH_EXTRA_FIELD = AUTH_WIRE_FIELDS.action;
27
+ var DEFAULT_AUTH_MOUNT_ID = "frontmcp-auth-root";
28
+
29
+ // libs/ui/src/auth/vanilla/auth-flow.ts
30
+ function getGlobalCarrier() {
31
+ if (typeof window !== "undefined") {
32
+ return window;
33
+ }
34
+ return globalThis;
35
+ }
36
+ function getAuthFlow() {
37
+ const state = tryGetAuthFlow();
38
+ if (!state) {
39
+ throw new Error(
40
+ `[auth-ui] No injected auth flow state found on window.${AUTH_FLOW_GLOBAL_KEY}. This page must be server-rendered by a FrontMCP @AuthUi slot.`
41
+ );
42
+ }
43
+ return state;
44
+ }
45
+ function tryGetAuthFlow() {
46
+ const carrier = getGlobalCarrier();
47
+ const state = carrier?.[AUTH_FLOW_GLOBAL_KEY];
48
+ if (!state || typeof state !== "object") {
49
+ return void 0;
50
+ }
51
+ return state;
52
+ }
53
+ function getAddedItems(name) {
54
+ const items = tryGetAuthFlow()?.addedItems?.[name];
55
+ return Array.isArray(items) ? items : [];
56
+ }
57
+ function toEntries(input) {
58
+ if (!input) {
59
+ return [];
60
+ }
61
+ if (isFormElement(input)) {
62
+ return formDataToEntries(new FormData(input));
63
+ }
64
+ if (typeof FormData !== "undefined" && input instanceof FormData) {
65
+ return formDataToEntries(input);
66
+ }
67
+ const entries = [];
68
+ for (const [key, value] of Object.entries(input)) {
69
+ if (value === void 0 || value === null) {
70
+ continue;
71
+ }
72
+ if (Array.isArray(value)) {
73
+ for (const v of value) {
74
+ if (v !== void 0 && v !== null) {
75
+ entries.push([key, String(v)]);
76
+ }
77
+ }
78
+ } else {
79
+ entries.push([key, String(value)]);
80
+ }
81
+ }
82
+ return entries;
83
+ }
84
+ function formDataToEntries(fd) {
85
+ const entries = [];
86
+ fd.forEach((value, key) => {
87
+ if (typeof value === "string") {
88
+ entries.push([key, value]);
89
+ }
90
+ });
91
+ return entries;
92
+ }
93
+ function isFormElement(input) {
94
+ return typeof input === "object" && // `nodeName` + a `FormData`-constructible shape is enough; avoids requiring
95
+ // a live `HTMLFormElement` global (jsdom provides one, node does not).
96
+ input.nodeName === "FORM";
97
+ }
98
+ function buildGetUrl(base, entries) {
99
+ const origin = typeof window !== "undefined" && window.location ? window.location.origin : void 0;
100
+ const url = origin ? new URL(base, origin) : new URL(base, "http://localhost");
101
+ for (const [key, value] of entries) {
102
+ url.searchParams.append(key, value);
103
+ }
104
+ if (!origin && !/^https?:\/\//i.test(base)) {
105
+ return `${url.pathname}${url.search}`;
106
+ }
107
+ return url.toString();
108
+ }
109
+ function buildUrlEncodedBody(entries) {
110
+ const params = new URLSearchParams();
111
+ for (const [key, value] of entries) {
112
+ params.append(key, value);
113
+ }
114
+ return params.toString();
115
+ }
116
+ function withControlFields(state, entries, markers) {
117
+ const present = new Set(entries.map(([k]) => k));
118
+ const merged = [...entries];
119
+ const ensure = (key, value) => {
120
+ if (value !== void 0 && !present.has(key)) {
121
+ merged.push([key, value]);
122
+ present.add(key);
123
+ }
124
+ };
125
+ ensure(AUTH_WIRE_FIELDS.pendingAuthId, state.pendingAuthId);
126
+ ensure(AUTH_WIRE_FIELDS.csrf, state.csrfToken);
127
+ for (const [key, value] of markers) {
128
+ ensure(key, value);
129
+ }
130
+ return merged;
131
+ }
132
+ function finishMarkers(state) {
133
+ switch (state.slot) {
134
+ case "consent":
135
+ return [[AUTH_WIRE_FIELDS.consentSubmitted, CONSENT_SUBMITTED_VALUE]];
136
+ default:
137
+ return [];
138
+ }
139
+ }
140
+ async function dispatch(method, url, entries) {
141
+ if (typeof fetch !== "function") {
142
+ throw new Error("[auth-ui] global fetch is unavailable in this environment");
143
+ }
144
+ if (method === "GET") {
145
+ return fetch(buildGetUrl(url, entries), {
146
+ method: "GET",
147
+ credentials: "same-origin",
148
+ headers: { Accept: "application/json, text/html" }
149
+ });
150
+ }
151
+ return fetch(url, {
152
+ method: "POST",
153
+ credentials: "same-origin",
154
+ headers: {
155
+ "Content-Type": "application/x-www-form-urlencoded",
156
+ Accept: "application/json, text/html"
157
+ },
158
+ body: buildUrlEncodedBody(entries)
159
+ });
160
+ }
161
+ var defaultNavigator = (url) => {
162
+ if (typeof window !== "undefined" && window.location) {
163
+ window.location.assign(url);
164
+ }
165
+ };
166
+ var currentNavigator = defaultNavigator;
167
+ function setAuthNavigator(navigator) {
168
+ currentNavigator = navigator ?? defaultNavigator;
169
+ }
170
+ async function submitFinish(formOrData, options = {}) {
171
+ const state = options.state ?? getAuthFlow();
172
+ if (!state.submitUrl) {
173
+ throw new Error("[auth-ui] submitFinish called but the injected flow state has no submitUrl");
174
+ }
175
+ const method = state.submitMethod ?? DEFAULT_SUBMIT_METHOD;
176
+ const entries = withControlFields(state, toEntries(formOrData), finishMarkers(state));
177
+ const response = await dispatch(method, state.submitUrl, entries);
178
+ const shouldNavigate = options.navigate ?? (typeof window !== "undefined" && !!window.location);
179
+ if (shouldNavigate && response.redirected && response.url) {
180
+ currentNavigator(response.url);
181
+ }
182
+ return response;
183
+ }
184
+ async function submitExtra(name, data, state) {
185
+ const flow = state ?? getAuthFlow();
186
+ const target = flow.extraUrl ?? flow.submitUrl;
187
+ if (!target) {
188
+ throw new Error("[auth-ui] submitExtra called but the injected flow state has no extraUrl/submitUrl");
189
+ }
190
+ const markers = flow.extraUrl ? [] : [[AUTH_EXTRA_FIELD, name]];
191
+ const entries = withControlFields(flow, toEntries(data), markers);
192
+ const response = await dispatch("POST", target, entries);
193
+ return parseExtraResponse(response);
194
+ }
195
+ async function parseExtraResponse(response) {
196
+ const contentType = response.headers.get("content-type") ?? "";
197
+ if (contentType.includes("application/json")) {
198
+ try {
199
+ const body = await response.json();
200
+ return {
201
+ ok: body.ok ?? response.ok,
202
+ error: typeof body.error === "string" ? body.error : void 0,
203
+ addedItems: body.addedItems && typeof body.addedItems === "object" ? body.addedItems : void 0,
204
+ sideEffects: body.sideEffects && typeof body.sideEffects === "object" ? body.sideEffects : void 0
205
+ };
206
+ } catch {
207
+ }
208
+ }
209
+ if (response.ok) {
210
+ return { ok: true };
211
+ }
212
+ return { ok: false, error: `Request failed with status ${response.status}` };
213
+ }
214
+
215
+ // libs/ui/src/auth/context.tsx
216
+ import { createContext, useContext } from "react";
217
+ var AuthFlowContext = createContext(null);
218
+ function useAuthFlowContext() {
219
+ const ctx = useContext(AuthFlowContext);
220
+ if (!ctx) {
221
+ throw new Error("[auth-ui] useAuthFlow* hooks must be used inside <AuthPageWrapper> / <AuthFlowProvider>");
222
+ }
223
+ return ctx;
224
+ }
225
+
226
+ // libs/ui/src/auth/AuthPageWrapper.tsx
227
+ import { useContext as useContext2, useMemo, useState } from "react";
228
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
229
+ function AuthFlowProvider({
230
+ children,
231
+ state: stateOverride
232
+ }) {
233
+ const injected = stateOverride ?? tryGetAuthFlow();
234
+ const [state, setState] = useState(
235
+ injected ?? { slot: "error", error: "No authorization flow state was provided." }
236
+ );
237
+ const value = useMemo(
238
+ () => ({
239
+ state,
240
+ update: (patch) => setState((prev) => ({ ...prev, ...patch }))
241
+ }),
242
+ [state]
243
+ );
244
+ return /* @__PURE__ */ jsx(AuthFlowContext.Provider, { value, children });
245
+ }
246
+ function ControlFields({ state }) {
247
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
248
+ state.pendingAuthId !== void 0 && /* @__PURE__ */ jsx("input", { type: "hidden", name: AUTH_WIRE_FIELDS.pendingAuthId, value: state.pendingAuthId }),
249
+ state.csrfToken !== void 0 && /* @__PURE__ */ jsx("input", { type: "hidden", name: AUTH_WIRE_FIELDS.csrf, value: state.csrfToken }),
250
+ state.slot === "consent" && /* @__PURE__ */ jsx("input", { type: "hidden", name: AUTH_WIRE_FIELDS.consentSubmitted, value: CONSENT_SUBMITTED_VALUE })
251
+ ] });
252
+ }
253
+ function AuthPageWrapper({
254
+ children,
255
+ state: stateOverride,
256
+ renderForm = true,
257
+ className
258
+ }) {
259
+ return /* @__PURE__ */ jsx(AuthFlowProvider, { state: stateOverride, children: /* @__PURE__ */ jsx(AuthPageWrapperInner, { renderForm, className, children }) });
260
+ }
261
+ function AuthPageWrapperInner({
262
+ children,
263
+ renderForm,
264
+ className
265
+ }) {
266
+ const ctx = useAuthFlowContextSafe();
267
+ const state = ctx?.state;
268
+ if (!renderForm || !state?.submitUrl) {
269
+ return /* @__PURE__ */ jsx("div", { className: className ?? "frontmcp-auth-page", children });
270
+ }
271
+ const method = (state.submitMethod ?? DEFAULT_SUBMIT_METHOD).toLowerCase();
272
+ return /* @__PURE__ */ jsx("div", { className: className ?? "frontmcp-auth-page", children: /* @__PURE__ */ jsxs("form", { action: state.submitUrl, method, children: [
273
+ /* @__PURE__ */ jsx(ControlFields, { state }),
274
+ children
275
+ ] }) });
276
+ }
277
+ function useAuthFlowContextSafe() {
278
+ return useContext2(AuthFlowContext);
279
+ }
280
+
281
+ // libs/ui/src/auth/hooks.ts
282
+ import { useCallback, useState as useState2 } from "react";
283
+ function normalizeFormArg(arg) {
284
+ if (!arg) {
285
+ return void 0;
286
+ }
287
+ const maybeEvent = arg;
288
+ if (typeof maybeEvent.preventDefault === "function" && maybeEvent.currentTarget) {
289
+ maybeEvent.preventDefault();
290
+ return maybeEvent.currentTarget;
291
+ }
292
+ return arg;
293
+ }
294
+ function useAuthFlow() {
295
+ const { state } = useAuthFlowContext();
296
+ const submitFinish2 = useCallback(
297
+ (formOrEvent) => submitFinish(normalizeFormArg(formOrEvent), { state }),
298
+ [state]
299
+ );
300
+ return {
301
+ slot: state.slot,
302
+ pendingAuthId: state.pendingAuthId,
303
+ clientName: state.clientName,
304
+ clientId: state.clientId,
305
+ scopes: state.scopes ?? [],
306
+ redirectUri: state.redirectUri,
307
+ resource: state.resource,
308
+ error: state.error,
309
+ providers: state.providers ?? [],
310
+ tools: state.tools ?? [],
311
+ extras: state.extras ?? {},
312
+ state,
313
+ submitFinish: submitFinish2
314
+ };
315
+ }
316
+ function useAddedItems(name) {
317
+ const { state } = useAuthFlowContext();
318
+ const items = state.addedItems?.[name];
319
+ return Array.isArray(items) ? items : [];
320
+ }
321
+ function useExtraField(name) {
322
+ const { state, update } = useAuthFlowContext();
323
+ const [result, setResult] = useState2(void 0);
324
+ const [pending, setPending] = useState2(false);
325
+ const onSubmit = useCallback(
326
+ async (formOrEvent) => {
327
+ const data = normalizeFormArg(formOrEvent);
328
+ setPending(true);
329
+ try {
330
+ const res = await submitExtra(name, data, state);
331
+ setResult(res);
332
+ if (res.ok && res.addedItems) {
333
+ update({ addedItems: res.addedItems });
334
+ }
335
+ return res;
336
+ } catch (err) {
337
+ const failure = {
338
+ ok: false,
339
+ error: err instanceof Error ? err.message : "Failed to submit field"
340
+ };
341
+ setResult(failure);
342
+ return failure;
343
+ } finally {
344
+ setPending(false);
345
+ }
346
+ },
347
+ [name, state, update]
348
+ );
349
+ return { onSubmit, result, pending };
350
+ }
351
+
352
+ // libs/ui/src/auth/hydrate.tsx
353
+ import { createRoot } from "react-dom/client";
354
+ import { jsx as jsx2 } from "react/jsx-runtime";
355
+ function resolveContainer(container) {
356
+ if (container && typeof container !== "string") {
357
+ return container;
358
+ }
359
+ if (typeof document === "undefined") {
360
+ throw new Error("[auth-ui] mountAuthPage requires a DOM (document is undefined)");
361
+ }
362
+ const selector = container ?? `#${DEFAULT_AUTH_MOUNT_ID}`;
363
+ const el = document.querySelector(selector);
364
+ if (!el) {
365
+ throw new Error(`[auth-ui] mountAuthPage could not find a container matching "${selector}"`);
366
+ }
367
+ return el;
368
+ }
369
+ function mountAuthPage(Component, options = {}) {
370
+ const container = resolveContainer(options.container);
371
+ const root = createRoot(container);
372
+ root.render(
373
+ /* @__PURE__ */ jsx2(AuthPageWrapper, { state: options.state, renderForm: options.renderForm, children: /* @__PURE__ */ jsx2(Component, {}) })
374
+ );
375
+ return root;
376
+ }
377
+ export {
378
+ AUTH_EXTRA_FIELD,
379
+ AUTH_FLOW_GLOBAL_KEY,
380
+ AUTH_WIRE_FIELDS,
381
+ AuthFlowContext,
382
+ AuthFlowProvider,
383
+ AuthPageWrapper,
384
+ CONSENT_SUBMITTED_VALUE,
385
+ DEFAULT_AUTH_MOUNT_ID,
386
+ DEFAULT_SUBMIT_METHOD,
387
+ WIRE_TRUE,
388
+ getAddedItems,
389
+ getAuthFlow,
390
+ mountAuthPage,
391
+ setAuthNavigator,
392
+ submitExtra,
393
+ submitFinish,
394
+ tryGetAuthFlow,
395
+ useAddedItems,
396
+ useAuthFlow,
397
+ useExtraField
398
+ };
@@ -0,0 +1,228 @@
1
+ // libs/ui/src/auth/contract.ts
2
+ var AUTH_FLOW_GLOBAL_KEY = "__FRONTMCP_AUTH__";
3
+ var DEFAULT_SUBMIT_METHOD = "GET";
4
+ var AUTH_WIRE_FIELDS = {
5
+ /** Pending authorization id. */
6
+ pendingAuthId: "pending_auth_id",
7
+ /** Anti-CSRF token. */
8
+ csrf: "csrf",
9
+ /** Marks a consent-form submission (distinguishes empty-select from first visit). */
10
+ consentSubmitted: "consent_submitted",
11
+ /** Repeated checkbox field carrying selected tool ids on the consent slot. */
12
+ tools: "tools",
13
+ /** Marks a federated submission. */
14
+ federated: "federated",
15
+ /** Repeated checkbox field carrying selected provider ids on the federated slot. */
16
+ providers: "providers",
17
+ /** Marks an incremental authorization. */
18
+ incremental: "incremental",
19
+ /** Target app id for incremental authorization. */
20
+ appId: "app_id",
21
+ /** Generic action discriminator (e.g. consent `authorize`/`skip`, or an extra name). */
22
+ action: "action"
23
+ };
24
+ var CONSENT_SUBMITTED_VALUE = "1";
25
+ var WIRE_TRUE = "true";
26
+ var AUTH_EXTRA_FIELD = AUTH_WIRE_FIELDS.action;
27
+ var DEFAULT_AUTH_MOUNT_ID = "frontmcp-auth-root";
28
+
29
+ // libs/ui/src/auth/vanilla/auth-flow.ts
30
+ function getGlobalCarrier() {
31
+ if (typeof window !== "undefined") {
32
+ return window;
33
+ }
34
+ return globalThis;
35
+ }
36
+ function getAuthFlow() {
37
+ const state = tryGetAuthFlow();
38
+ if (!state) {
39
+ throw new Error(
40
+ `[auth-ui] No injected auth flow state found on window.${AUTH_FLOW_GLOBAL_KEY}. This page must be server-rendered by a FrontMCP @AuthUi slot.`
41
+ );
42
+ }
43
+ return state;
44
+ }
45
+ function tryGetAuthFlow() {
46
+ const carrier = getGlobalCarrier();
47
+ const state = carrier?.[AUTH_FLOW_GLOBAL_KEY];
48
+ if (!state || typeof state !== "object") {
49
+ return void 0;
50
+ }
51
+ return state;
52
+ }
53
+ function getAddedItems(name) {
54
+ const items = tryGetAuthFlow()?.addedItems?.[name];
55
+ return Array.isArray(items) ? items : [];
56
+ }
57
+ function toEntries(input) {
58
+ if (!input) {
59
+ return [];
60
+ }
61
+ if (isFormElement(input)) {
62
+ return formDataToEntries(new FormData(input));
63
+ }
64
+ if (typeof FormData !== "undefined" && input instanceof FormData) {
65
+ return formDataToEntries(input);
66
+ }
67
+ const entries = [];
68
+ for (const [key, value] of Object.entries(input)) {
69
+ if (value === void 0 || value === null) {
70
+ continue;
71
+ }
72
+ if (Array.isArray(value)) {
73
+ for (const v of value) {
74
+ if (v !== void 0 && v !== null) {
75
+ entries.push([key, String(v)]);
76
+ }
77
+ }
78
+ } else {
79
+ entries.push([key, String(value)]);
80
+ }
81
+ }
82
+ return entries;
83
+ }
84
+ function formDataToEntries(fd) {
85
+ const entries = [];
86
+ fd.forEach((value, key) => {
87
+ if (typeof value === "string") {
88
+ entries.push([key, value]);
89
+ }
90
+ });
91
+ return entries;
92
+ }
93
+ function isFormElement(input) {
94
+ return typeof input === "object" && // `nodeName` + a `FormData`-constructible shape is enough; avoids requiring
95
+ // a live `HTMLFormElement` global (jsdom provides one, node does not).
96
+ input.nodeName === "FORM";
97
+ }
98
+ function buildGetUrl(base, entries) {
99
+ const origin = typeof window !== "undefined" && window.location ? window.location.origin : void 0;
100
+ const url = origin ? new URL(base, origin) : new URL(base, "http://localhost");
101
+ for (const [key, value] of entries) {
102
+ url.searchParams.append(key, value);
103
+ }
104
+ if (!origin && !/^https?:\/\//i.test(base)) {
105
+ return `${url.pathname}${url.search}`;
106
+ }
107
+ return url.toString();
108
+ }
109
+ function buildUrlEncodedBody(entries) {
110
+ const params = new URLSearchParams();
111
+ for (const [key, value] of entries) {
112
+ params.append(key, value);
113
+ }
114
+ return params.toString();
115
+ }
116
+ function withControlFields(state, entries, markers) {
117
+ const present = new Set(entries.map(([k]) => k));
118
+ const merged = [...entries];
119
+ const ensure = (key, value) => {
120
+ if (value !== void 0 && !present.has(key)) {
121
+ merged.push([key, value]);
122
+ present.add(key);
123
+ }
124
+ };
125
+ ensure(AUTH_WIRE_FIELDS.pendingAuthId, state.pendingAuthId);
126
+ ensure(AUTH_WIRE_FIELDS.csrf, state.csrfToken);
127
+ for (const [key, value] of markers) {
128
+ ensure(key, value);
129
+ }
130
+ return merged;
131
+ }
132
+ function finishMarkers(state) {
133
+ switch (state.slot) {
134
+ case "consent":
135
+ return [[AUTH_WIRE_FIELDS.consentSubmitted, CONSENT_SUBMITTED_VALUE]];
136
+ default:
137
+ return [];
138
+ }
139
+ }
140
+ async function dispatch(method, url, entries) {
141
+ if (typeof fetch !== "function") {
142
+ throw new Error("[auth-ui] global fetch is unavailable in this environment");
143
+ }
144
+ if (method === "GET") {
145
+ return fetch(buildGetUrl(url, entries), {
146
+ method: "GET",
147
+ credentials: "same-origin",
148
+ headers: { Accept: "application/json, text/html" }
149
+ });
150
+ }
151
+ return fetch(url, {
152
+ method: "POST",
153
+ credentials: "same-origin",
154
+ headers: {
155
+ "Content-Type": "application/x-www-form-urlencoded",
156
+ Accept: "application/json, text/html"
157
+ },
158
+ body: buildUrlEncodedBody(entries)
159
+ });
160
+ }
161
+ var defaultNavigator = (url) => {
162
+ if (typeof window !== "undefined" && window.location) {
163
+ window.location.assign(url);
164
+ }
165
+ };
166
+ var currentNavigator = defaultNavigator;
167
+ function setAuthNavigator(navigator) {
168
+ currentNavigator = navigator ?? defaultNavigator;
169
+ }
170
+ async function submitFinish(formOrData, options = {}) {
171
+ const state = options.state ?? getAuthFlow();
172
+ if (!state.submitUrl) {
173
+ throw new Error("[auth-ui] submitFinish called but the injected flow state has no submitUrl");
174
+ }
175
+ const method = state.submitMethod ?? DEFAULT_SUBMIT_METHOD;
176
+ const entries = withControlFields(state, toEntries(formOrData), finishMarkers(state));
177
+ const response = await dispatch(method, state.submitUrl, entries);
178
+ const shouldNavigate = options.navigate ?? (typeof window !== "undefined" && !!window.location);
179
+ if (shouldNavigate && response.redirected && response.url) {
180
+ currentNavigator(response.url);
181
+ }
182
+ return response;
183
+ }
184
+ async function submitExtra(name, data, state) {
185
+ const flow = state ?? getAuthFlow();
186
+ const target = flow.extraUrl ?? flow.submitUrl;
187
+ if (!target) {
188
+ throw new Error("[auth-ui] submitExtra called but the injected flow state has no extraUrl/submitUrl");
189
+ }
190
+ const markers = flow.extraUrl ? [] : [[AUTH_EXTRA_FIELD, name]];
191
+ const entries = withControlFields(flow, toEntries(data), markers);
192
+ const response = await dispatch("POST", target, entries);
193
+ return parseExtraResponse(response);
194
+ }
195
+ async function parseExtraResponse(response) {
196
+ const contentType = response.headers.get("content-type") ?? "";
197
+ if (contentType.includes("application/json")) {
198
+ try {
199
+ const body = await response.json();
200
+ return {
201
+ ok: body.ok ?? response.ok,
202
+ error: typeof body.error === "string" ? body.error : void 0,
203
+ addedItems: body.addedItems && typeof body.addedItems === "object" ? body.addedItems : void 0,
204
+ sideEffects: body.sideEffects && typeof body.sideEffects === "object" ? body.sideEffects : void 0
205
+ };
206
+ } catch {
207
+ }
208
+ }
209
+ if (response.ok) {
210
+ return { ok: true };
211
+ }
212
+ return { ok: false, error: `Request failed with status ${response.status}` };
213
+ }
214
+ export {
215
+ AUTH_EXTRA_FIELD,
216
+ AUTH_FLOW_GLOBAL_KEY,
217
+ AUTH_WIRE_FIELDS,
218
+ CONSENT_SUBMITTED_VALUE,
219
+ DEFAULT_AUTH_MOUNT_ID,
220
+ DEFAULT_SUBMIT_METHOD,
221
+ WIRE_TRUE,
222
+ getAddedItems,
223
+ getAuthFlow,
224
+ setAuthNavigator,
225
+ submitExtra,
226
+ submitFinish,
227
+ tryGetAuthFlow
228
+ };
@@ -455,6 +455,14 @@ var FrontMcpBridge = class {
455
455
  const adapter = this._ensureInitialized();
456
456
  return adapter.requestDisplayMode(mode);
457
457
  }
458
+ /**
459
+ * Report a desired widget size to the host.
460
+ * @param size - Desired widget dimensions
461
+ */
462
+ async setSize(size) {
463
+ const adapter = this._ensureInitialized();
464
+ return adapter.setSize(size);
465
+ }
458
466
  /**
459
467
  * Request widget close.
460
468
  */
@@ -673,6 +681,8 @@ var BaseAdapter = class {
673
681
  }
674
682
  throw new Error("requestDisplayMode not implemented");
675
683
  }
684
+ async setSize(_size) {
685
+ }
676
686
  async requestClose() {
677
687
  }
678
688
  setWidgetState(state) {
@@ -951,6 +961,23 @@ var OpenAIAdapter = class extends BaseAdapter {
951
961
  await this._openai.canvas.setDisplayMode(mode);
952
962
  this._hostContext = { ...this._hostContext, displayMode: mode };
953
963
  }
964
+ /**
965
+ * Report a desired widget size to ChatGPT.
966
+ *
967
+ * The Apps SDK normally measures DOM height itself, so this forwards to the
968
+ * SDK's sizing API only when one is exposed (`setWidgetHeight`) and otherwise
969
+ * no-ops. A `displayMode` hint is forwarded to `setDisplayMode`.
970
+ */
971
+ async setSize(size) {
972
+ const canvas = this._openai?.canvas;
973
+ if (!canvas) return;
974
+ if (size.displayMode && canvas.setDisplayMode) {
975
+ await canvas.setDisplayMode(size.displayMode);
976
+ }
977
+ if (typeof size.height === "number" && canvas.setWidgetHeight) {
978
+ await canvas.setWidgetHeight(size.height);
979
+ }
980
+ }
954
981
  async requestClose() {
955
982
  if (this._openai?.canvas?.close) {
956
983
  await this._openai.canvas.close();
@@ -1103,6 +1130,17 @@ var ExtAppsAdapter = class extends BaseAdapter {
1103
1130
  await this._sendRequest("ui/setDisplayMode", { mode });
1104
1131
  this._hostContext = { ...this._hostContext, displayMode: mode };
1105
1132
  }
1133
+ /**
1134
+ * Report a desired widget size to the host via the FrontMCP `ui/setSize`
1135
+ * request (parallels `ui/setDisplayMode`).
1136
+ */
1137
+ async setSize(size) {
1138
+ await this._sendRequest("ui/setSize", {
1139
+ height: size.height,
1140
+ width: size.width,
1141
+ aspectRatio: size.aspectRatio
1142
+ });
1143
+ }
1106
1144
  async requestClose() {
1107
1145
  await this._sendRequest("ui/close", {});
1108
1146
  }
package/esm/index.mjs CHANGED
@@ -449,6 +449,14 @@ var FrontMcpBridge = class {
449
449
  const adapter = this._ensureInitialized();
450
450
  return adapter.requestDisplayMode(mode);
451
451
  }
452
+ /**
453
+ * Report a desired widget size to the host.
454
+ * @param size - Desired widget dimensions
455
+ */
456
+ async setSize(size) {
457
+ const adapter = this._ensureInitialized();
458
+ return adapter.setSize(size);
459
+ }
452
460
  /**
453
461
  * Request widget close.
454
462
  */