@athenaintel/react 0.10.35 → 0.10.37

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/README.md CHANGED
@@ -160,6 +160,11 @@ function AssetView() {
160
160
  onCitationClick={({ reference, sourceAssetId }) => {
161
161
  console.log('Citation clicked', { reference, sourceAssetId });
162
162
  }}
163
+ onCitationAction={({ action, reference }) => {
164
+ if (action === 'open-in-spaces') {
165
+ openAssetInMyApp(reference);
166
+ }
167
+ }}
163
168
  />
164
169
  </div>
165
170
  </AthenaProvider>
@@ -167,7 +172,9 @@ function AssetView() {
167
172
  }
168
173
  ```
169
174
 
170
- When `onCitationClick` is provided, citation navigation is delegated to the host by default. Set `citationBehavior="browser"` to observe the event while preserving Athena's default new-tab navigation, or `citationBehavior="host"` to delegate navigation without providing a callback.
175
+ When `onCitationClick` is provided, citation navigation is delegated to the host by default. Set `citationBehavior="browser"` to observe the event while preserving Athena's default new-tab navigation. Host mode requires `onCitationClick`; without a handler, the SDK safely falls back to browser navigation.
176
+
177
+ `onCitationAction` handles actions initiated from a citation popover. It currently receives `action: 'open-in-spaces'` when the user clicks the (+) button. Providing the handler delegates that action to the host by default. Set `citationActionBehavior="browser"` to observe the event while preserving Athena's default navigation; requesting host mode without a handler safely falls back to browser behavior.
171
178
 
172
179
  ## Theming
173
180
 
@@ -1,5 +1,5 @@
1
1
  import { type FC, type IframeHTMLAttributes } from "react";
2
- import { type AthenaAssetCitationBehavior, type AthenaAssetCitationClickEvent } from "./asset-embed-messages";
2
+ import { type AthenaAssetCitationActionEvent, type AthenaAssetCitationBehavior, type AthenaAssetCitationClickEvent } from "./asset-embed-messages";
3
3
  export interface AthenaAssetEmbedProps extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, "src" | "onError" | "onLoad"> {
4
4
  assetId: string;
5
5
  /** Defaults to the native full asset experience. */
@@ -9,6 +9,9 @@ export interface AthenaAssetEmbedProps extends Omit<IframeHTMLAttributes<HTMLIFr
9
9
  expiresInSeconds?: number;
10
10
  citationBehavior?: AthenaAssetCitationBehavior;
11
11
  onCitationClick?: (event: AthenaAssetCitationClickEvent) => void;
12
+ /** Controls the citation popover's actions, including the (+) Open in Spaces button. */
13
+ citationActionBehavior?: AthenaAssetCitationBehavior;
14
+ onCitationAction?: (event: AthenaAssetCitationActionEvent) => void;
12
15
  onLoad?: () => void;
13
16
  onError?: (error: Error) => void;
14
17
  loadingFallback?: React.ReactNode;
@@ -1,7 +1,9 @@
1
1
  export declare const ATHENA_ASSET_EMBED_READY_MESSAGE: "athena:asset-embed-ready";
2
2
  export declare const ATHENA_ASSET_EMBED_CONFIG_MESSAGE: "athena:asset-embed-config";
3
3
  export declare const ATHENA_ASSET_CITATION_CLICK_MESSAGE: "athena:asset-citation-click";
4
+ export declare const ATHENA_ASSET_CITATION_ACTION_MESSAGE: "athena:asset-citation-action";
4
5
  export type AthenaAssetCitationBehavior = "browser" | "host";
6
+ export type AthenaAssetCitationAction = "open-in-spaces";
5
7
  export interface AthenaAssetCitationReference {
6
8
  id: string;
7
9
  anchor?: unknown;
@@ -13,6 +15,12 @@ export interface AthenaAssetCitationClickEvent {
13
15
  sourceAssetId?: string;
14
16
  reference: AthenaAssetCitationReference;
15
17
  }
18
+ export interface AthenaAssetCitationActionEvent {
19
+ type: typeof ATHENA_ASSET_CITATION_ACTION_MESSAGE;
20
+ version: 1;
21
+ action: AthenaAssetCitationAction;
22
+ reference: AthenaAssetCitationReference;
23
+ }
16
24
  export interface AthenaAssetEmbedReadyEvent {
17
25
  type: typeof ATHENA_ASSET_EMBED_READY_MESSAGE;
18
26
  version: 1;
@@ -22,6 +30,16 @@ export interface AthenaAssetEmbedConfigEvent {
22
30
  version: 1;
23
31
  token?: string;
24
32
  citationBehavior: AthenaAssetCitationBehavior;
33
+ citationActionBehavior?: AthenaAssetCitationBehavior;
25
34
  }
35
+ export declare function resolveAthenaAssetCitationBehavior({ citationBehavior, hasCitationClickHandler, }: {
36
+ citationBehavior?: AthenaAssetCitationBehavior;
37
+ hasCitationClickHandler: boolean;
38
+ }): AthenaAssetCitationBehavior;
39
+ export declare function resolveAthenaAssetCitationActionBehavior({ citationActionBehavior, hasCitationActionHandler, }: {
40
+ citationActionBehavior?: AthenaAssetCitationBehavior;
41
+ hasCitationActionHandler: boolean;
42
+ }): AthenaAssetCitationBehavior;
26
43
  export declare function isAthenaAssetEmbedReadyEvent(value: unknown): value is AthenaAssetEmbedReadyEvent;
27
44
  export declare function isAthenaAssetCitationClickEvent(value: unknown): value is AthenaAssetCitationClickEvent;
45
+ export declare function isAthenaAssetCitationActionEvent(value: unknown): value is AthenaAssetCitationActionEvent;
@@ -26,6 +26,11 @@ export declare function resolveAssetEmbedUrl({ embedUrl, appUrl, backendUrl, cur
26
26
  backendUrl?: string | null;
27
27
  currentOrigin?: string | null;
28
28
  }): string;
29
+ export declare function buildSdkAssetEmbedUrl(embedUrl: string): string;
30
+ export declare function getAssetEmbedAuthContext({ token, apiKey }: {
31
+ token?: string | null;
32
+ apiKey?: string;
33
+ }): string;
29
34
  /**
30
35
  * Hook to generate an embed URL for rendering an asset in an iframe.
31
36
  * Calls the Athena embed token endpoint.
package/dist/index.cjs CHANGED
@@ -56254,12 +56254,30 @@ const AthenaUserMessage = ({
56254
56254
  const ATHENA_ASSET_EMBED_READY_MESSAGE = "athena:asset-embed-ready";
56255
56255
  const ATHENA_ASSET_EMBED_CONFIG_MESSAGE = "athena:asset-embed-config";
56256
56256
  const ATHENA_ASSET_CITATION_CLICK_MESSAGE = "athena:asset-citation-click";
56257
+ const ATHENA_ASSET_CITATION_ACTION_MESSAGE = "athena:asset-citation-action";
56258
+ function resolveAthenaAssetCitationBehavior({
56259
+ citationBehavior,
56260
+ hasCitationClickHandler
56261
+ }) {
56262
+ if (citationBehavior === "host" && !hasCitationClickHandler) return "browser";
56263
+ return citationBehavior ?? (hasCitationClickHandler ? "host" : "browser");
56264
+ }
56265
+ function resolveAthenaAssetCitationActionBehavior({
56266
+ citationActionBehavior,
56267
+ hasCitationActionHandler
56268
+ }) {
56269
+ if (citationActionBehavior === "host" && !hasCitationActionHandler) return "browser";
56270
+ return citationActionBehavior ?? (hasCitationActionHandler ? "host" : "browser");
56271
+ }
56257
56272
  function isAthenaAssetEmbedReadyEvent(value) {
56258
56273
  return typeof value === "object" && value !== null && "type" in value && value.type === ATHENA_ASSET_EMBED_READY_MESSAGE && "version" in value && value.version === 1;
56259
56274
  }
56260
56275
  function isAthenaAssetCitationClickEvent(value) {
56261
56276
  return typeof value === "object" && value !== null && "type" in value && value.type === ATHENA_ASSET_CITATION_CLICK_MESSAGE && "version" in value && value.version === 1 && "reference" in value && typeof value.reference === "object" && value.reference !== null && "id" in value.reference && typeof value.reference.id === "string";
56262
56277
  }
56278
+ function isAthenaAssetCitationActionEvent(value) {
56279
+ return typeof value === "object" && value !== null && "type" in value && value.type === ATHENA_ASSET_CITATION_ACTION_MESSAGE && "version" in value && value.version === 1 && "action" in value && value.action === "open-in-spaces" && "reference" in value && typeof value.reference === "object" && value.reference !== null && "id" in value.reference && typeof value.reference.id === "string";
56280
+ }
56263
56281
  function buildAssetEmbedRequestBody({
56264
56282
  assetId,
56265
56283
  readOnly,
@@ -56356,7 +56374,11 @@ function resolveAssetEmbedUrl({
56356
56374
  if (!resolvedEmbedUrl.pathname.startsWith("/embed/")) {
56357
56375
  return embedUrl;
56358
56376
  }
56359
- const embedAppUrl = resolveEmbedAppUrl({ appUrl, backendUrl, currentOrigin });
56377
+ const embedAppUrl = resolveEmbedAppUrl({
56378
+ appUrl,
56379
+ backendUrl,
56380
+ currentOrigin
56381
+ });
56360
56382
  if (!embedAppUrl) {
56361
56383
  return embedUrl;
56362
56384
  }
@@ -56368,6 +56390,41 @@ function resolveAssetEmbedUrl({
56368
56390
  return embedUrl;
56369
56391
  }
56370
56392
  }
56393
+ function buildSdkAssetEmbedUrl(embedUrl) {
56394
+ try {
56395
+ const url = new URL(embedUrl);
56396
+ url.searchParams.set("athena_sdk_bridge", "1");
56397
+ return url.toString();
56398
+ } catch {
56399
+ return embedUrl;
56400
+ }
56401
+ }
56402
+ const getJwtPayload = (token) => {
56403
+ try {
56404
+ const payloadSegment = token.split(".")[1];
56405
+ if (!payloadSegment || typeof atob !== "function") return null;
56406
+ const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
56407
+ const payload = JSON.parse(
56408
+ atob(normalized.padEnd(normalized.length + (4 - normalized.length % 4) % 4, "="))
56409
+ );
56410
+ return payload && typeof payload === "object" ? payload : null;
56411
+ } catch {
56412
+ return null;
56413
+ }
56414
+ };
56415
+ function getAssetEmbedAuthContext({ token, apiKey }) {
56416
+ if (!token) return apiKey ? `api-key:${apiKey}` : "anonymous";
56417
+ const payload = getJwtPayload(token);
56418
+ const userId = (payload == null ? void 0 : payload.user_id) ?? (payload == null ? void 0 : payload.sub);
56419
+ if (typeof userId !== "string" || !userId) return `opaque-token:${token}`;
56420
+ return `jwt:${JSON.stringify({
56421
+ issuer: (payload == null ? void 0 : payload.iss) ?? null,
56422
+ userId,
56423
+ activeOrgId: (payload == null ? void 0 : payload.active_org_id) ?? (payload == null ? void 0 : payload.org_id) ?? null,
56424
+ workspaceId: (payload == null ? void 0 : payload.workspace_id) ?? null,
56425
+ orgMemberships: (payload == null ? void 0 : payload.org_id_to_org_member_info) ?? null
56426
+ })}`;
56427
+ }
56371
56428
  function useAssetEmbed(assetId, options = {
56372
56429
  backendUrl: ""
56373
56430
  }) {
@@ -56386,7 +56443,7 @@ function useAssetEmbed(assetId, options = {
56386
56443
  const abortRef = React.useRef(null);
56387
56444
  const tokenRef = React.useRef(token);
56388
56445
  tokenRef.current = token;
56389
- const hasToken = !!token;
56446
+ const authContext = getAssetEmbedAuthContext({ token, apiKey });
56390
56447
  React.useEffect(() => {
56391
56448
  var _a2;
56392
56449
  if (!assetId || !backendUrl) {
@@ -56395,7 +56452,6 @@ function useAssetEmbed(assetId, options = {
56395
56452
  setError(null);
56396
56453
  return;
56397
56454
  }
56398
- const authContext = hasToken ? "token" : apiKey ?? "anon";
56399
56455
  const cacheKey = `${assetId}:${readOnly}:${displayMode}:${authContext}:${backendUrl}:${appUrl ?? ""}`;
56400
56456
  const cached2 = embedCache.get(cacheKey);
56401
56457
  if (cached2 && cached2.expiresAt > Date.now() / 1e3) {
@@ -56411,10 +56467,12 @@ function useAssetEmbed(assetId, options = {
56411
56467
  abortRef.current = controller;
56412
56468
  setIsLoading(true);
56413
56469
  setError(null);
56414
- const headers = { "Content-Type": "application/json" };
56470
+ const headers = {
56471
+ "Content-Type": "application/json"
56472
+ };
56415
56473
  const currentToken = tokenRef.current;
56416
56474
  if (currentToken) {
56417
- headers["Authorization"] = `Bearer ${currentToken}`;
56475
+ headers.Authorization = `Bearer ${currentToken}`;
56418
56476
  } else if (apiKey) {
56419
56477
  headers["X-API-KEY"] = apiKey;
56420
56478
  }
@@ -56422,7 +56480,12 @@ function useAssetEmbed(assetId, options = {
56422
56480
  method: "POST",
56423
56481
  headers,
56424
56482
  body: JSON.stringify(
56425
- buildAssetEmbedRequestBody({ assetId, readOnly, expiresInSeconds, displayMode })
56483
+ buildAssetEmbedRequestBody({
56484
+ assetId,
56485
+ readOnly,
56486
+ expiresInSeconds,
56487
+ displayMode
56488
+ })
56426
56489
  ),
56427
56490
  signal: controller.signal
56428
56491
  }).then(async (res) => {
@@ -56437,7 +56500,10 @@ function useAssetEmbed(assetId, options = {
56437
56500
  appUrl,
56438
56501
  backendUrl
56439
56502
  });
56440
- embedCache.set(cacheKey, { url: resolvedEmbedUrl, expiresAt: data.expires_at });
56503
+ embedCache.set(cacheKey, {
56504
+ url: resolvedEmbedUrl,
56505
+ expiresAt: data.expires_at
56506
+ });
56441
56507
  setEmbedUrl(resolvedEmbedUrl);
56442
56508
  setIsLoading(false);
56443
56509
  }).catch((err) => {
@@ -56446,7 +56512,7 @@ function useAssetEmbed(assetId, options = {
56446
56512
  setIsLoading(false);
56447
56513
  });
56448
56514
  return () => controller.abort();
56449
- }, [assetId, readOnly, expiresInSeconds, displayMode, backendUrl, appUrl, apiKey, hasToken]);
56515
+ }, [assetId, readOnly, expiresInSeconds, displayMode, backendUrl, appUrl, authContext, apiKey]);
56450
56516
  return { embedUrl, isLoading, error: error2 };
56451
56517
  }
56452
56518
  const DEFAULT_STYLE = {
@@ -56462,6 +56528,8 @@ const AthenaAssetEmbed = ({
56462
56528
  expiresInSeconds,
56463
56529
  citationBehavior,
56464
56530
  onCitationClick,
56531
+ citationActionBehavior,
56532
+ onCitationAction,
56465
56533
  onLoad,
56466
56534
  onError,
56467
56535
  loadingFallback,
@@ -56492,7 +56560,15 @@ const AthenaAssetEmbed = ({
56492
56560
  return null;
56493
56561
  }
56494
56562
  }, [embedUrl]);
56495
- const effectiveCitationBehavior = citationBehavior ?? (onCitationClick ? "host" : "browser");
56563
+ const iframeSrc = React.useMemo(() => embedUrl ? buildSdkAssetEmbedUrl(embedUrl) : null, [embedUrl]);
56564
+ const effectiveCitationBehavior = resolveAthenaAssetCitationBehavior({
56565
+ citationBehavior,
56566
+ hasCitationClickHandler: !!onCitationClick
56567
+ });
56568
+ const effectiveCitationActionBehavior = resolveAthenaAssetCitationActionBehavior({
56569
+ citationActionBehavior,
56570
+ hasCitationActionHandler: !!onCitationAction
56571
+ });
56496
56572
  const sendConfig = React.useCallback(() => {
56497
56573
  var _a2, _b;
56498
56574
  if (!targetOrigin) return;
@@ -56501,28 +56577,32 @@ const AthenaAssetEmbed = ({
56501
56577
  type: ATHENA_ASSET_EMBED_CONFIG_MESSAGE,
56502
56578
  version: 1,
56503
56579
  ...athenaConfig.token ? { token: athenaConfig.token } : {},
56504
- citationBehavior: effectiveCitationBehavior
56580
+ citationBehavior: effectiveCitationBehavior,
56581
+ citationActionBehavior: effectiveCitationActionBehavior
56505
56582
  },
56506
56583
  targetOrigin
56507
56584
  );
56508
- }, [athenaConfig, effectiveCitationBehavior, targetOrigin]);
56585
+ }, [athenaConfig, effectiveCitationActionBehavior, effectiveCitationBehavior, targetOrigin]);
56509
56586
  React.useEffect(() => {
56510
56587
  if (!embedUrl || !targetOrigin) return;
56511
56588
  const handleMessage = (event) => {
56512
56589
  var _a2;
56513
- if (event.origin !== targetOrigin || event.source !== ((_a2 = iframeRef.current) == null ? void 0 : _a2.contentWindow))
56514
- return;
56590
+ if (event.origin !== targetOrigin || event.source !== ((_a2 = iframeRef.current) == null ? void 0 : _a2.contentWindow)) return;
56515
56591
  if (isAthenaAssetEmbedReadyEvent(event.data)) {
56516
56592
  sendConfig();
56517
56593
  return;
56518
56594
  }
56519
56595
  if (isAthenaAssetCitationClickEvent(event.data)) {
56520
56596
  onCitationClick == null ? void 0 : onCitationClick(event.data);
56597
+ return;
56598
+ }
56599
+ if (isAthenaAssetCitationActionEvent(event.data)) {
56600
+ onCitationAction == null ? void 0 : onCitationAction(event.data);
56521
56601
  }
56522
56602
  };
56523
56603
  window.addEventListener("message", handleMessage);
56524
56604
  return () => window.removeEventListener("message", handleMessage);
56525
- }, [embedUrl, onCitationClick, sendConfig, targetOrigin]);
56605
+ }, [embedUrl, onCitationAction, onCitationClick, sendConfig, targetOrigin]);
56526
56606
  React.useEffect(() => {
56527
56607
  if (!embedUrl) return;
56528
56608
  sendConfig();
@@ -56559,7 +56639,7 @@ const AthenaAssetEmbed = ({
56559
56639
  {
56560
56640
  ...iframeProps,
56561
56641
  ref: iframeRef,
56562
- src: embedUrl,
56642
+ src: iframeSrc ?? void 0,
56563
56643
  title: title ?? `Athena asset ${assetId}`,
56564
56644
  allow,
56565
56645
  className: cn("block h-full w-full", className),