@agent-native/toolkit 0.198.7-nightly-20260930213124 → 0.198.7

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 (50) hide show
  1. package/dist/app/chat/AgentPanel.d.ts +4 -3
  2. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  3. package/dist/app/chat/AgentPanel.js +14 -24
  4. package/dist/app/chat/AgentPanel.js.map +1 -1
  5. package/dist/app/chat/ConnectBuilderCard.d.ts.map +1 -1
  6. package/dist/app/chat/ConnectBuilderCard.js +2 -1
  7. package/dist/app/chat/ConnectBuilderCard.js.map +1 -1
  8. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  9. package/dist/app/chat/MultiTabAssistantChat.js +21 -2
  10. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  11. package/dist/composer/ComposerContextMenu.d.ts +2 -2
  12. package/dist/composer/ComposerContextMenu.d.ts.map +1 -1
  13. package/dist/composer/ComposerContextMenu.js +54 -66
  14. package/dist/composer/ComposerContextMenu.js.map +1 -1
  15. package/dist/composer/ComposerPlusMenu.d.ts +2 -3
  16. package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
  17. package/dist/composer/ComposerPlusMenu.js +13 -278
  18. package/dist/composer/ComposerPlusMenu.js.map +1 -1
  19. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  20. package/dist/composer/TiptapComposer.js +3 -12
  21. package/dist/composer/TiptapComposer.js.map +1 -1
  22. package/dist/composer/use-composer-panel-placement.d.ts +0 -1
  23. package/dist/composer/use-composer-panel-placement.d.ts.map +1 -1
  24. package/dist/composer/use-composer-panel-placement.js +4 -5
  25. package/dist/composer/use-composer-panel-placement.js.map +1 -1
  26. package/package.json +4 -4
  27. package/src/app/chat/AgentPanel.header.spec.ts +25 -27
  28. package/src/app/chat/AgentPanel.overlay.spec.tsx +14 -51
  29. package/src/app/chat/AgentPanel.tsx +15 -26
  30. package/src/app/chat/ConnectBuilderCard.spec.tsx +45 -0
  31. package/src/app/chat/ConnectBuilderCard.tsx +3 -2
  32. package/src/app/chat/MultiTabAssistantChat.spec.tsx +12 -0
  33. package/src/app/chat/MultiTabAssistantChat.tsx +26 -2
  34. package/src/app/chat/composer/use-agentkit-integration-menu.spec.tsx +4 -2
  35. package/src/composer/ComposerContextMenu.spec.tsx +68 -60
  36. package/src/composer/ComposerContextMenu.tsx +93 -110
  37. package/src/composer/ComposerContextPicker.spec.tsx +16 -14
  38. package/src/composer/ComposerContextPickerDialog.spec.tsx +8 -6
  39. package/src/composer/ComposerPlusMenu.parity.spec.tsx +37 -75
  40. package/src/composer/ComposerPlusMenu.tsx +26 -450
  41. package/src/composer/PromptComposer.context.spec.tsx +112 -18
  42. package/src/composer/TiptapComposer.spec.ts +15 -0
  43. package/src/composer/TiptapComposer.tsx +2 -12
  44. package/src/composer/use-composer-panel-placement.ts +7 -5
  45. package/dist/composer/asset-picker-url.d.ts +0 -10
  46. package/dist/composer/asset-picker-url.d.ts.map +0 -1
  47. package/dist/composer/asset-picker-url.js +0 -56
  48. package/dist/composer/asset-picker-url.js.map +0 -1
  49. package/src/composer/asset-picker-url.spec.ts +0 -56
  50. package/src/composer/asset-picker-url.ts +0 -72
@@ -96,8 +96,9 @@ describe("controlled composer context", () => {
96
96
  ),
97
97
  );
98
98
  const menu = document.querySelector('[role="menu"]')!;
99
- expect(menu.querySelector('[role="searchbox"]')).not.toBeNull();
99
+ expect(menu.querySelector('[role="searchbox"]')).toBeNull();
100
100
  expect(menu.textContent).toContain("Upload File");
101
+ expect(menu.textContent).toContain("Add context");
101
102
  await act(async () =>
102
103
  document.dispatchEvent(
103
104
  new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
@@ -129,18 +130,39 @@ describe("controlled composer context", () => {
129
130
 
130
131
  const mentionMenu = document.querySelector<HTMLElement>('[role="menu"]');
131
132
  expect(mentionMenu?.textContent).toContain("Upload File");
132
- expect(mentionMenu?.textContent).toContain("Choose source");
133
+ expect(mentionMenu?.textContent).toContain("Add context");
134
+ expect(mentionMenu?.textContent).not.toContain("Choose source");
133
135
  expect(editor.textContent).toBe("");
134
- const mentionOptions = Array.from(
135
- mentionMenu!.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
136
- ).map((item) => item.textContent);
136
+ const openContext = async () => {
137
+ const trigger = Array.from(
138
+ document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
139
+ ).find((item) => item.textContent === "Add context")!;
140
+ await act(async () => {
141
+ trigger.focus();
142
+ trigger.dispatchEvent(
143
+ new KeyboardEvent("keydown", {
144
+ key: "ArrowRight",
145
+ bubbles: true,
146
+ cancelable: true,
147
+ }),
148
+ );
149
+ });
150
+ };
151
+ const nestedItems = () =>
152
+ Array.from(
153
+ Array.from(document.querySelectorAll<HTMLElement>('[role="menu"]'))
154
+ .at(-1)!
155
+ .querySelectorAll<HTMLElement>('[role^="menuitem"]'),
156
+ ).map((item) => item.textContent);
157
+ await openContext();
158
+ const mentionOptions = nestedItems();
137
159
 
138
160
  const plusButton = container.querySelector<HTMLButtonElement>(
139
161
  'button[aria-label="Add context"]',
140
162
  )!;
141
163
  await act(async () =>
142
- mentionMenu!
143
- .querySelector<HTMLElement>('[role^="menuitem"]')!
164
+ Array.from(document.querySelectorAll<HTMLElement>('[role^="menuitem"]'))
165
+ .find((item) => item.textContent === "Choose source")!
144
166
  .dispatchEvent(
145
167
  new KeyboardEvent("keydown", {
146
168
  key: "Escape",
@@ -149,6 +171,11 @@ describe("controlled composer context", () => {
149
171
  }),
150
172
  ),
151
173
  );
174
+ await act(async () =>
175
+ document.dispatchEvent(
176
+ new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
177
+ ),
178
+ );
152
179
  expect(document.querySelector('[role="menu"]')).toBeNull();
153
180
  await act(async () =>
154
181
  plusButton.dispatchEvent(
@@ -161,13 +188,11 @@ describe("controlled composer context", () => {
161
188
  );
162
189
 
163
190
  const plusMenu = document.querySelector<HTMLElement>('[role="menu"]');
164
- expect(
165
- Array.from(
166
- plusMenu!.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
167
- ).map((item) => item.textContent),
168
- ).toEqual(mentionOptions);
191
+ expect(plusMenu?.textContent).toContain("Add context");
192
+ await openContext();
193
+ expect(nestedItems()).toEqual(mentionOptions);
169
194
  const sourceAction = Array.from(
170
- plusMenu!.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
195
+ document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
171
196
  ).find((item) => item.textContent === "Choose source")!;
172
197
  await act(async () => sourceAction.click());
173
198
  expect(onSelect).toHaveBeenCalledOnce();
@@ -205,9 +230,22 @@ describe("controlled composer context", () => {
205
230
  }),
206
231
  );
207
232
  });
208
- const integrations = document.querySelector<HTMLElement>(
209
- '[role="menuitem"][aria-haspopup="menu"]',
210
- )!;
233
+ const addContext = Array.from(
234
+ document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
235
+ ).find((item) => item.textContent === "Add context")!;
236
+ await act(async () => {
237
+ addContext.focus();
238
+ addContext.dispatchEvent(
239
+ new KeyboardEvent("keydown", {
240
+ key: "ArrowRight",
241
+ bubbles: true,
242
+ cancelable: true,
243
+ }),
244
+ );
245
+ });
246
+ const integrations = Array.from(
247
+ document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
248
+ ).find((item) => item.textContent === "Integrations")!;
211
249
  expect(integrations.textContent).toBe("Integrations");
212
250
  await act(async () => {
213
251
  integrations.focus();
@@ -268,6 +306,36 @@ describe("controlled composer context", () => {
268
306
  }),
269
307
  );
270
308
  });
309
+ const addContext = Array.from(
310
+ document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
311
+ ).find((item) => item.textContent === "Add context");
312
+ if (addContext) {
313
+ await act(async () => {
314
+ addContext.focus();
315
+ addContext.dispatchEvent(
316
+ new KeyboardEvent("keydown", {
317
+ key: "ArrowRight",
318
+ bubbles: true,
319
+ cancelable: true,
320
+ }),
321
+ );
322
+ });
323
+ }
324
+ const section = Array.from(
325
+ document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
326
+ ).find((item) => item.textContent === "Connected Agents");
327
+ if (section) {
328
+ await act(async () => {
329
+ section.focus();
330
+ section.dispatchEvent(
331
+ new KeyboardEvent("keydown", {
332
+ key: "ArrowRight",
333
+ bubbles: true,
334
+ cancelable: true,
335
+ }),
336
+ );
337
+ });
338
+ }
271
339
  const row = Array.from(
272
340
  document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
273
341
  ).find(
@@ -398,15 +466,28 @@ describe("controlled composer context", () => {
398
466
  '[data-agent-native-composer-popover="true"]',
399
467
  )!;
400
468
  expect(panel).not.toBeNull();
401
- expect(panel.style.width).toBe("700px");
469
+ if (plusMenu) {
470
+ expect(panel.classList.contains("w-64")).toBe(true);
471
+ expect(panel.style.width).toBe("");
472
+ } else {
473
+ expect(panel.style.width).toBe("700px");
474
+ }
402
475
  const observation = observations.find(({ target }) => target === frame)!;
403
476
  expect(observation).toBeDefined();
404
477
  for (const width of [324, 280, 700]) {
405
478
  bounds = { ...bounds, width };
406
479
  await act(async () => observation.resize());
407
- expect(panel.style.width).toBe(`${width}px`);
480
+ if (plusMenu) {
481
+ expect(panel.style.width).toBe("");
482
+ expect(panel.classList.contains("w-64")).toBe(true);
483
+ } else {
484
+ expect(panel.style.width).toBe(`${width}px`);
485
+ }
408
486
  if (!plusMenu) expect(panel.style.left).toBe("100px");
409
487
  }
488
+ bounds = { ...bounds, y: window.innerHeight + 500 };
489
+ await act(async () => observation.resize());
490
+ if (plusMenu) expect(panel.style.maxHeight).toBe("280px");
410
491
  bounds = { ...bounds, x: 120, y: 160 };
411
492
  await act(async () => window.dispatchEvent(new Event("scroll")));
412
493
  if (!plusMenu) {
@@ -785,6 +866,19 @@ describe("controlled composer context", () => {
785
866
  new KeyboardEvent("keydown", { key: "ArrowDown", bubbles: true }),
786
867
  ),
787
868
  );
869
+ const addContext = Array.from(
870
+ document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
871
+ ).find((element) => element.textContent === "Add context")!;
872
+ await act(async () => {
873
+ addContext.focus();
874
+ addContext.dispatchEvent(
875
+ new KeyboardEvent("keydown", {
876
+ key: "ArrowRight",
877
+ bubbles: true,
878
+ cancelable: true,
879
+ }),
880
+ );
881
+ });
788
882
  const option = Array.from(
789
883
  document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
790
884
  ).find((element) => element.textContent?.includes("Choose brief"))!;
@@ -356,6 +356,17 @@ describe("createTiptapComposerExtensions", () => {
356
356
  }),
357
357
  );
358
358
  });
359
+ act(() => {
360
+ Array.from(document.querySelectorAll<HTMLElement>('[role="menuitem"]'))
361
+ .find((button) => button.textContent?.trim() === "Add context")
362
+ ?.dispatchEvent(
363
+ new KeyboardEvent("keydown", {
364
+ key: "ArrowRight",
365
+ bubbles: true,
366
+ cancelable: true,
367
+ }),
368
+ );
369
+ });
359
370
  act(() => {
360
371
  Array.from(document.querySelectorAll<HTMLElement>('[role="menuitem"]'))
361
372
  .find((button) => button.textContent?.trim() === "Schedule Task")
@@ -502,6 +513,10 @@ describe("createTiptapComposerExtensions", () => {
502
513
  );
503
514
  await new Promise((resolve) => setTimeout(resolve, 0));
504
515
  });
516
+ const addContextItem = Array.from(
517
+ document.querySelectorAll<HTMLElement>('[role="menuitem"]'),
518
+ ).find((item) => item.textContent?.trim() === "Add context");
519
+ await act(async () => addContextItem?.click());
505
520
  const clickMenuItem = async (label: string) => {
506
521
  const item = Array.from(
507
522
  document.querySelectorAll<HTMLElement>('[role^="menuitem"]'),
@@ -2634,7 +2634,6 @@ export function TiptapComposer({
2634
2634
  });
2635
2635
  const [popover, setPopover] = useState<PopoverState>(null);
2636
2636
  const [contextMenuOpen, setContextMenuOpen] = useState(false);
2637
- const [contextMenuSearch, setContextMenuSearch] = useState("");
2638
2637
  const popoverRef = useRef<MentionPopoverRef>(null);
2639
2638
  const composerRuntime = useComposerRuntime();
2640
2639
  const lastComposerRuntimeSyncRef = useRef<{
@@ -2748,18 +2747,10 @@ export function TiptapComposer({
2748
2747
  error: mentionsError,
2749
2748
  retry: retryMentions,
2750
2749
  } = useMentionSearch(
2751
- contextMenuOpen
2752
- ? contextMenuSearch
2753
- : popover?.type === "@"
2754
- ? popover.query
2755
- : "",
2750
+ popover?.type === "@" ? popover.query : "",
2756
2751
  includeDefaultMentionSearch && (contextMenuOpen || popover?.type === "@"),
2757
2752
  );
2758
- const mentionQuery = contextMenuOpen
2759
- ? contextMenuSearch
2760
- : popover?.type === "@"
2761
- ? popover.query
2762
- : "";
2753
+ const mentionQuery = popover?.type === "@" ? popover.query : "";
2763
2754
  const filteredMentionItems = useMemo(
2764
2755
  () =>
2765
2756
  filterMentionItemsForSlots(
@@ -5302,7 +5293,6 @@ export function TiptapComposer({
5302
5293
  attachmentsEnabled={attachmentsEnabled}
5303
5294
  open={contextMenuOpen}
5304
5295
  onOpenChange={setContextMenuOpen}
5305
- onSearchChange={setContextMenuSearch}
5306
5296
  loading={mentionsLoading}
5307
5297
  error={
5308
5298
  mentionsError
@@ -1,15 +1,15 @@
1
1
  import { useLayoutEffect, useState, type RefObject } from "react";
2
2
 
3
- // Leave room for the search field, group label, and at least one action.
3
+ // Keep the compact menu's actions reachable in short viewports.
4
4
  const MIN_USABLE_PANEL_HEIGHT = 120;
5
+ const MAX_COMPOSER_PANEL_HEIGHT = 280;
5
6
 
6
7
  export function useComposerPanelPlacement(
7
8
  triggerRef: RefObject<HTMLElement | null>,
8
9
  open: boolean,
9
10
  ) {
10
11
  const [placement, setPlacement] = useState({
11
- width: 320,
12
- maxHeight: 440,
12
+ maxHeight: MAX_COMPOSER_PANEL_HEIGHT,
13
13
  side: "top" as "top" | "bottom",
14
14
  sideOffset: 8,
15
15
  alignOffset: 0,
@@ -37,10 +37,12 @@ export function useComposerPanelPlacement(
37
37
  ? "bottom"
38
38
  : "top";
39
39
  setPlacement({
40
- width: bounds.width,
41
40
  direction: rtl ? "rtl" : "ltr",
42
41
  side,
43
- maxHeight: Math.min(440, side === "top" ? spaceAbove : spaceBelow),
42
+ maxHeight: Math.min(
43
+ MAX_COMPOSER_PANEL_HEIGHT,
44
+ side === "top" ? spaceAbove : spaceBelow,
45
+ ),
44
46
  sideOffset:
45
47
  side === "top"
46
48
  ? button.top - bounds.top + 8
@@ -1,10 +0,0 @@
1
- export declare const ASSET_PICKER_HANDOFF_PARAM = "__an_asset_picker_handoff";
2
- export declare const ASSET_PICKER_RETURN_ORIGIN_PARAM = "__an_asset_picker_return_origin";
3
- export interface StandaloneAssetPickerOptions {
4
- handoffId?: string;
5
- returnOrigin?: string;
6
- }
7
- export declare function createAssetPickerHandoffId(): string;
8
- export declare function isExternalAssetPickerUrl(value: string, currentOrigin: string): boolean;
9
- export declare function standaloneAssetPickerUrl(value: string, baseUrl?: string, options?: StandaloneAssetPickerOptions): string;
10
- //# sourceMappingURL=asset-picker-url.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asset-picker-url.d.ts","sourceRoot":"","sources":["../../src/composer/asset-picker-url.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,0BAA0B,8BAA8B,CAAC;AACtE,eAAO,MAAM,gCAAgC,oCACV,CAAC;AAEpC,MAAM,WAAW,4BAA4B;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,0BAA0B,IAAI,MAAM,CAWnD;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAMT;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,EACb,OAAO,SAAoB,EAC3B,OAAO,GAAE,4BAAiC,GACzC,MAAM,CAgCR"}
@@ -1,56 +0,0 @@
1
- const FALLBACK_BASE_URL = "http://agent-native.invalid";
2
- export const ASSET_PICKER_HANDOFF_PARAM = "__an_asset_picker_handoff";
3
- export const ASSET_PICKER_RETURN_ORIGIN_PARAM = "__an_asset_picker_return_origin";
4
- export function createAssetPickerHandoffId() {
5
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
6
- return crypto.randomUUID();
7
- }
8
- if (typeof crypto !== "undefined" && crypto.getRandomValues) {
9
- const bytes = crypto.getRandomValues(new Uint8Array(16));
10
- return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
11
- }
12
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
13
- }
14
- export function isExternalAssetPickerUrl(value, currentOrigin) {
15
- try {
16
- return new URL(value, currentOrigin).origin !== currentOrigin;
17
- }
18
- catch {
19
- return true;
20
- }
21
- }
22
- export function standaloneAssetPickerUrl(value, baseUrl = FALLBACK_BASE_URL, options = {}) {
23
- try {
24
- const parsed = new URL(value, baseUrl);
25
- parsed.searchParams.delete("embedded");
26
- parsed.searchParams.delete("__an_embed_token");
27
- parsed.searchParams.set("mediaType", "image");
28
- let returnOrigin = null;
29
- if (options.returnOrigin) {
30
- try {
31
- const parsedOrigin = new URL(options.returnOrigin);
32
- if (parsedOrigin.protocol === "http:" ||
33
- parsedOrigin.protocol === "https:") {
34
- returnOrigin = parsedOrigin.origin;
35
- }
36
- }
37
- catch {
38
- // Omit an invalid return target rather than emitting an unverified
39
- // cross-origin callback URL.
40
- }
41
- }
42
- if (options.handoffId && returnOrigin) {
43
- parsed.searchParams.set(ASSET_PICKER_HANDOFF_PARAM, options.handoffId);
44
- parsed.searchParams.set(ASSET_PICKER_RETURN_ORIGIN_PARAM, returnOrigin);
45
- }
46
- else {
47
- parsed.searchParams.delete(ASSET_PICKER_HANDOFF_PARAM);
48
- parsed.searchParams.delete(ASSET_PICKER_RETURN_ORIGIN_PARAM);
49
- }
50
- return parsed.toString();
51
- }
52
- catch {
53
- return value;
54
- }
55
- }
56
- //# sourceMappingURL=asset-picker-url.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"asset-picker-url.js","sourceRoot":"","sources":["../../src/composer/asset-picker-url.ts"],"names":[],"mappings":"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"}
@@ -1,56 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import {
4
- isExternalAssetPickerUrl,
5
- standaloneAssetPickerUrl,
6
- } from "./asset-picker-url.js";
7
-
8
- describe("asset picker auth handoff", () => {
9
- it("treats the hosted Assets picker as external to the host app", () => {
10
- expect(
11
- isExternalAssetPickerUrl(
12
- "https://assets.agent-native.com/picker",
13
- "https://clips.agent-native.com",
14
- ),
15
- ).toBe(true);
16
- });
17
-
18
- it("keeps same-origin pickers eligible for inline rendering", () => {
19
- expect(
20
- isExternalAssetPickerUrl("/picker", "https://clips.agent-native.com"),
21
- ).toBe(false);
22
- });
23
-
24
- it("removes iframe flags from the top-level fallback URL", () => {
25
- expect(
26
- standaloneAssetPickerUrl(
27
- "https://assets.agent-native.com/picker?embedded=1&__an_embed_token=fake-token",
28
- ),
29
- ).toBe("https://assets.agent-native.com/picker?mediaType=image");
30
- });
31
-
32
- it("adds a nonce-bound exact-origin callback to the top-level URL", () => {
33
- expect(
34
- standaloneAssetPickerUrl(
35
- "https://assets.agent-native.com/picker?embedded=1",
36
- "https://clips.agent-native.com",
37
- {
38
- handoffId: "handoff-123",
39
- returnOrigin: "https://clips.agent-native.com/chat",
40
- },
41
- ),
42
- ).toBe(
43
- "https://assets.agent-native.com/picker?mediaType=image&__an_asset_picker_handoff=handoff-123&__an_asset_picker_return_origin=https%3A%2F%2Fclips.agent-native.com",
44
- );
45
- });
46
-
47
- it("omits an invalid callback target", () => {
48
- expect(
49
- standaloneAssetPickerUrl(
50
- "https://assets.agent-native.com/picker",
51
- "https://clips.agent-native.com",
52
- { handoffId: "handoff-123", returnOrigin: "javascript:alert(1)" },
53
- ),
54
- ).toBe("https://assets.agent-native.com/picker?mediaType=image");
55
- });
56
- });
@@ -1,72 +0,0 @@
1
- const FALLBACK_BASE_URL = "http://agent-native.invalid";
2
-
3
- export const ASSET_PICKER_HANDOFF_PARAM = "__an_asset_picker_handoff";
4
- export const ASSET_PICKER_RETURN_ORIGIN_PARAM =
5
- "__an_asset_picker_return_origin";
6
-
7
- export interface StandaloneAssetPickerOptions {
8
- handoffId?: string;
9
- returnOrigin?: string;
10
- }
11
-
12
- export function createAssetPickerHandoffId(): string {
13
- if (typeof crypto !== "undefined" && crypto.randomUUID) {
14
- return crypto.randomUUID();
15
- }
16
- if (typeof crypto !== "undefined" && crypto.getRandomValues) {
17
- const bytes = crypto.getRandomValues(new Uint8Array(16));
18
- return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(
19
- "",
20
- );
21
- }
22
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
23
- }
24
-
25
- export function isExternalAssetPickerUrl(
26
- value: string,
27
- currentOrigin: string,
28
- ): boolean {
29
- try {
30
- return new URL(value, currentOrigin).origin !== currentOrigin;
31
- } catch {
32
- return true;
33
- }
34
- }
35
-
36
- export function standaloneAssetPickerUrl(
37
- value: string,
38
- baseUrl = FALLBACK_BASE_URL,
39
- options: StandaloneAssetPickerOptions = {},
40
- ): string {
41
- try {
42
- const parsed = new URL(value, baseUrl);
43
- parsed.searchParams.delete("embedded");
44
- parsed.searchParams.delete("__an_embed_token");
45
- parsed.searchParams.set("mediaType", "image");
46
- let returnOrigin: string | null = null;
47
- if (options.returnOrigin) {
48
- try {
49
- const parsedOrigin = new URL(options.returnOrigin);
50
- if (
51
- parsedOrigin.protocol === "http:" ||
52
- parsedOrigin.protocol === "https:"
53
- ) {
54
- returnOrigin = parsedOrigin.origin;
55
- }
56
- } catch {
57
- // Omit an invalid return target rather than emitting an unverified
58
- // cross-origin callback URL.
59
- }
60
- }
61
- if (options.handoffId && returnOrigin) {
62
- parsed.searchParams.set(ASSET_PICKER_HANDOFF_PARAM, options.handoffId);
63
- parsed.searchParams.set(ASSET_PICKER_RETURN_ORIGIN_PARAM, returnOrigin);
64
- } else {
65
- parsed.searchParams.delete(ASSET_PICKER_HANDOFF_PARAM);
66
- parsed.searchParams.delete(ASSET_PICKER_RETURN_ORIGIN_PARAM);
67
- }
68
- return parsed.toString();
69
- } catch {
70
- return value;
71
- }
72
- }