@athenaintel/react 0.53.0 → 0.54.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 (137) hide show
  1. package/README.md +24 -1
  2. package/dist/agent-runtime/src/call-recap.cjs +2 -3
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -1
  4. package/dist/agent-runtime/src/call-recap.d.ts +1 -2
  5. package/dist/agent-runtime/src/call-recap.js +1 -2
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -1
  7. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs +7 -3
  8. package/dist/agent-runtime/src/deep-agent-transport-recipe.cjs.map +1 -1
  9. package/dist/agent-runtime/src/deep-agent-transport-recipe.d.ts +10 -2
  10. package/dist/agent-runtime/src/deep-agent-transport-recipe.js +8 -4
  11. package/dist/agent-runtime/src/deep-agent-transport-recipe.js.map +1 -1
  12. package/dist/agent-runtime/src/desktop-handoff.d.ts +7 -0
  13. package/dist/agent-runtime/src/dictation-session.d.ts +158 -0
  14. package/dist/agent-runtime/src/edit-command.cjs +6 -0
  15. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  16. package/dist/agent-runtime/src/edit-command.d.ts +8 -0
  17. package/dist/agent-runtime/src/edit-command.js +6 -0
  18. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  19. package/dist/agent-runtime/src/generated/runconfig-contract.cjs +6 -0
  20. package/dist/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  21. package/dist/agent-runtime/src/generated/runconfig-contract.d.ts +13 -0
  22. package/dist/agent-runtime/src/generated/runconfig-contract.js +6 -0
  23. package/dist/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  24. package/dist/agent-runtime/src/index.d.ts +6 -6
  25. package/dist/agent-runtime/src/message-window.cjs +111 -7
  26. package/dist/agent-runtime/src/message-window.cjs.map +1 -1
  27. package/dist/agent-runtime/src/message-window.d.ts +66 -6
  28. package/dist/agent-runtime/src/message-window.js +112 -8
  29. package/dist/agent-runtime/src/message-window.js.map +1 -1
  30. package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
  31. package/dist/agent-runtime/src/offline-status.d.ts +33 -0
  32. package/dist/agent-runtime/src/offline-status.js.map +1 -1
  33. package/dist/agent-runtime/src/runtime.d.ts +4 -3
  34. package/dist/agent-runtime/src/subagent-transcripts.cjs +23 -1
  35. package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
  36. package/dist/agent-runtime/src/subagent-transcripts.js +25 -3
  37. package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
  38. package/dist/agent-runtime/src/subagents.cjs +112 -0
  39. package/dist/agent-runtime/src/subagents.cjs.map +1 -1
  40. package/dist/agent-runtime/src/subagents.d.ts +103 -0
  41. package/dist/agent-runtime/src/subagents.js +113 -1
  42. package/dist/agent-runtime/src/subagents.js.map +1 -1
  43. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +37 -9
  44. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  45. package/dist/agent-runtime/src/tool-metadata.cjs +8 -0
  46. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  47. package/dist/agent-runtime/src/tool-metadata.js +8 -0
  48. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  49. package/dist/agent-runtime/src/use-deep-agent-thread.cjs +6 -0
  50. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  51. package/dist/agent-runtime/src/use-deep-agent-thread.js +7 -1
  52. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  53. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +39 -10
  54. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  55. package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +14 -4
  56. package/dist/agent-runtime/src/use-deep-agent-window-sync.js +41 -12
  57. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  58. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +5 -9
  59. package/dist/agent-runtime/src/voice-presentation.cjs +34 -0
  60. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  61. package/dist/agent-runtime/src/voice-presentation.d.ts +35 -0
  62. package/dist/agent-runtime/src/voice-presentation.js +34 -0
  63. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  64. package/dist/assets/AthenaAssetEmbed.cjs +4 -2
  65. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  66. package/dist/assets/AthenaAssetEmbed.d.ts +17 -8
  67. package/dist/assets/AthenaAssetEmbed.js +4 -2
  68. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  69. package/dist/assets/use-asset-embed.cjs.map +1 -1
  70. package/dist/assets/use-asset-embed.d.ts +7 -5
  71. package/dist/assets/use-asset-embed.js.map +1 -1
  72. package/dist/auth/account-identity.cjs +69 -0
  73. package/dist/auth/account-identity.cjs.map +1 -0
  74. package/dist/auth/account-identity.d.ts +57 -0
  75. package/dist/auth/account-identity.js +69 -0
  76. package/dist/auth/account-identity.js.map +1 -0
  77. package/dist/chat/AthenaChat.cjs +1 -1
  78. package/dist/chat/AthenaChat.cjs.map +1 -1
  79. package/dist/chat/AthenaChat.js +1 -1
  80. package/dist/chat/AthenaChat.js.map +1 -1
  81. package/dist/chat/super-grouping/toolkit-map.cjs +1 -0
  82. package/dist/chat/super-grouping/toolkit-map.cjs.map +1 -1
  83. package/dist/chat/super-grouping/toolkit-map.js +1 -0
  84. package/dist/chat/super-grouping/toolkit-map.js.map +1 -1
  85. package/dist/chat/use-statewire-approval-request.cjs +5 -5
  86. package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
  87. package/dist/chat/use-statewire-approval-request.d.ts +7 -0
  88. package/dist/chat/use-statewire-approval-request.js +5 -5
  89. package/dist/chat/use-statewire-approval-request.js.map +1 -1
  90. package/dist/chat-ui/mentions/use-account-workspace-id.cjs +18 -58
  91. package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
  92. package/dist/chat-ui/mentions/use-account-workspace-id.js +18 -58
  93. package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
  94. package/dist/chat-ui/messages/voice-delegation-card.cjs +93 -25
  95. package/dist/chat-ui/messages/voice-delegation-card.cjs.map +1 -1
  96. package/dist/chat-ui/messages/voice-delegation-card.d.ts +16 -7
  97. package/dist/chat-ui/messages/voice-delegation-card.js +95 -27
  98. package/dist/chat-ui/messages/voice-delegation-card.js.map +1 -1
  99. package/dist/chat-ui/thread/handoff-cursor.cjs +30 -5
  100. package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -1
  101. package/dist/chat-ui/thread/handoff-cursor.d.ts +13 -5
  102. package/dist/chat-ui/thread/handoff-cursor.js +32 -7
  103. package/dist/chat-ui/thread/handoff-cursor.js.map +1 -1
  104. package/dist/collab/client.cjs +94 -6
  105. package/dist/collab/client.cjs.map +1 -1
  106. package/dist/collab/client.d.ts +27 -0
  107. package/dist/collab/client.js +94 -6
  108. package/dist/collab/client.js.map +1 -1
  109. package/dist/collab/index.d.ts +1 -1
  110. package/dist/keryx-http/src/initial-sync.cjs +121 -0
  111. package/dist/keryx-http/src/initial-sync.cjs.map +1 -0
  112. package/dist/keryx-http/src/initial-sync.d.ts +77 -0
  113. package/dist/keryx-http/src/initial-sync.js +104 -0
  114. package/dist/keryx-http/src/initial-sync.js.map +1 -0
  115. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +8 -0
  116. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -1
  117. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +6 -0
  118. package/dist/keryx-http/src/unconfirmed-update-attributions.js +8 -0
  119. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -1
  120. package/dist/react/package.json.cjs +1 -1
  121. package/dist/react/package.json.js +1 -1
  122. package/dist/styles.css +1 -1
  123. package/dist/threads/api.cjs +1 -2
  124. package/dist/threads/api.cjs.map +1 -1
  125. package/dist/threads/api.d.ts +3 -2
  126. package/dist/threads/api.js +1 -2
  127. package/dist/threads/api.js.map +1 -1
  128. package/dist/threads/statewire-thread-list.cjs +5 -7
  129. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  130. package/dist/threads/statewire-thread-list.js +7 -9
  131. package/dist/threads/statewire-thread-list.js.map +1 -1
  132. package/dist/tools/subagent-task-tool-ui.cjs +15 -5
  133. package/dist/tools/subagent-task-tool-ui.cjs.map +1 -1
  134. package/dist/tools/subagent-task-tool-ui.d.ts +5 -4
  135. package/dist/tools/subagent-task-tool-ui.js +16 -6
  136. package/dist/tools/subagent-task-tool-ui.js.map +1 -1
  137. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"AthenaAssetEmbed.cjs","sources":["../../src/assets/AthenaAssetEmbed.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertCircle, Loader2 } from \"lucide-react\";\nimport { type CSSProperties, type FC, type IframeHTMLAttributes, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useAthenaConfig } from \"../provider/AthenaContext\";\nimport {\n ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n type AthenaAssetCitationActionEvent,\n type AthenaAssetCitationBehavior,\n type AthenaAssetCitationClickEvent,\n type AthenaAssetCitationReference,\n isAthenaAssetCitationActionEvent,\n isAthenaAssetCitationClickEvent,\n isAthenaAssetEmbedReadyEvent,\n isAthenaAssetEmbedContentReadyEvent,\n resolveAthenaAssetCitationActionBehavior,\n resolveAthenaAssetCitationBehavior,\n} from \"./asset-embed-messages\";\nimport {\n buildSdkAssetEmbedUrl,\n DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n DEFAULT_ASSET_EMBED_READ_ONLY,\n useAssetEmbed,\n} from \"./use-asset-embed\";\n\nexport interface AthenaAssetEmbedProps extends Omit<\n IframeHTMLAttributes<HTMLIFrameElement>,\n \"src\" | \"onError\" | \"onLoad\"\n> {\n assetId: string;\n /** Optional citation target to open inside the embedded asset, such as a PDF page. */\n reference?: AthenaAssetCitationReference;\n /** Defaults to the native full asset experience. */\n displayMode?: \"minimal\" | \"full\";\n /**\n * Defaults to false so authorized users can edit the embedded asset. A Word document, an Athena\n * document, a spreadsheet, a user interface or a deck edits only for a viewer who is signed in\n * to Athena: the provider's `token`, which this component hands the frame. Anyone else views it\n * read-only.\n */\n readOnly?: boolean;\n /**\n * Offer Quick Edit (⌘J) in a minimal embed (`displayMode=\"minimal\"`): spreadsheets, Word\n * documents and decks, on an editable link (`readOnly: false`) only, to a viewer who is signed in\n * to Athena (the provider's `token`). Billed to the asset's workspace, with an hourly cap per\n * link; the full mode's editors offer it to the signed-in person already. Defaults to false.\n * Ignored with `embedUrl`: a link the host minted says itself.\n */\n allowQuickEdit?: boolean;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it a minimal embed shows them only to a signed-in viewer with their\n * own access to the asset. No embed writes a comment. Defaults to false. Ignored with\n * `embedUrl`: a link the host minted says itself.\n */\n allowComments?: boolean;\n expiresInSeconds?: number;\n citationBehavior?: AthenaAssetCitationBehavior;\n onCitationClick?: (event: AthenaAssetCitationClickEvent) => void;\n /** Controls the citation popover's actions, including the (+) Open in Spaces button. */\n citationActionBehavior?: AthenaAssetCitationBehavior;\n onCitationAction?: (event: AthenaAssetCitationActionEvent) => void;\n /**\n * An embed link the host minted itself (`POST /api/embed/generate-token`, its `/embed/<token>/`\n * page): the component shows it and mints none. Without it the component mints one with the\n * provider's credentials, as before.\n */\n embedUrl?: string;\n /** The embedded page said it is ready (its bridge's first message): Athena runs in the frame. */\n onReady?: () => void;\n /** The matching asset viewer has committed usable content in this frame. */\n onContentReady?: () => void;\n onLoad?: () => void;\n onError?: (error: Error) => void;\n loadingFallback?: React.ReactNode;\n errorFallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n}\n\nconst DEFAULT_STYLE: CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n border: 0,\n};\nconst AUTH_SYNC_INTERVAL_MS = 60_000;\n\nexport const AthenaAssetEmbed: FC<AthenaAssetEmbedProps> = ({\n assetId,\n reference,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n expiresInSeconds,\n citationBehavior,\n onCitationClick,\n citationActionBehavior,\n onCitationAction,\n embedUrl: hostEmbedUrl,\n onReady,\n onContentReady,\n onLoad,\n onError,\n loadingFallback,\n errorFallback,\n className,\n style,\n title,\n allow = \"clipboard-read; clipboard-write; fullscreen\",\n ...iframeProps\n}) => {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const athenaConfig = useAthenaConfig();\n const { backendUrl, appUrl, apiKey } = athenaConfig;\n // A link the host minted is shown as given; the hook then mints nothing (no asset to mint for).\n const minted = useAssetEmbed(hostEmbedUrl ? null : assetId, {\n backendUrl,\n appUrl,\n apiKey,\n token: athenaConfig.token,\n getToken: athenaConfig.getToken,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n });\n const embedUrl = hostEmbedUrl ?? minted.embedUrl;\n const isLoading = hostEmbedUrl ? false : minted.isLoading;\n const error = hostEmbedUrl ? null : minted.error;\n const targetOrigin = useMemo(() => {\n if (!embedUrl) return null;\n try {\n return new URL(embedUrl, window.location.href).origin;\n } catch {\n return null;\n }\n }, [embedUrl]);\n const iframeSrc = useMemo(\n () => (embedUrl ? buildSdkAssetEmbedUrl(embedUrl, reference) : null),\n [embedUrl, reference],\n );\n const effectiveCitationBehavior = resolveAthenaAssetCitationBehavior({\n citationBehavior,\n hasCitationClickHandler: !!onCitationClick,\n });\n const effectiveCitationActionBehavior = resolveAthenaAssetCitationActionBehavior({\n citationActionBehavior,\n hasCitationActionHandler: !!onCitationAction,\n });\n\n const sendConfig = useCallback(() => {\n if (!targetOrigin) return;\n iframeRef.current?.contentWindow?.postMessage(\n {\n type: ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n version: 1,\n ...(athenaConfig.token ? { token: athenaConfig.token } : {}),\n citationBehavior: effectiveCitationBehavior,\n citationActionBehavior: effectiveCitationActionBehavior,\n },\n targetOrigin,\n );\n }, [athenaConfig, effectiveCitationActionBehavior, effectiveCitationBehavior, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl || !targetOrigin) return;\n const handleMessage = (event: MessageEvent<unknown>) => {\n if (event.origin !== targetOrigin || event.source !== iframeRef.current?.contentWindow) return;\n if (isAthenaAssetEmbedReadyEvent(event.data)) {\n sendConfig();\n onReady?.();\n return;\n }\n if (isAthenaAssetEmbedContentReadyEvent(event.data)) {\n if (event.data.assetId === assetId) onContentReady?.();\n return;\n }\n if (isAthenaAssetCitationClickEvent(event.data)) {\n onCitationClick?.(event.data);\n return;\n }\n if (isAthenaAssetCitationActionEvent(event.data)) {\n onCitationAction?.(event.data);\n }\n };\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [assetId, embedUrl, onCitationAction, onCitationClick, onReady, onContentReady, sendConfig, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl) return;\n sendConfig();\n const interval = window.setInterval(sendConfig, AUTH_SYNC_INTERVAL_MS);\n return () => window.clearInterval(interval);\n }, [embedUrl, sendConfig]);\n\n useEffect(() => {\n if (error) onError?.(new Error(error));\n }, [error, onError]);\n\n if (isLoading) {\n return (\n loadingFallback ?? (\n <output className=\"athena-sdk-chrome flex h-full w-full items-center justify-center\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n <span className=\"sr-only\">Loading asset</span>\n </output>\n )\n );\n }\n\n if (error) {\n const embedError = new Error(error);\n if (typeof errorFallback === \"function\") return errorFallback(embedError);\n return (\n errorFallback ?? (\n <div\n className=\"athena-sdk-chrome flex h-full w-full items-center justify-center gap-2 p-4 text-sm text-muted-foreground\"\n role=\"alert\"\n >\n <AlertCircle className=\"size-4 text-destructive\" />\n <span>{error}</span>\n </div>\n )\n );\n }\n\n if (!embedUrl) return null;\n\n return (\n <iframe\n {...iframeProps}\n ref={iframeRef}\n src={iframeSrc ?? undefined}\n title={title ?? `Athena asset ${assetId}`}\n allow={allow}\n className={cn(\"block h-full w-full\", className)}\n style={{ ...DEFAULT_STYLE, ...style }}\n onLoad={() => {\n sendConfig();\n onLoad?.();\n }}\n />\n );\n};\n"],"names":["DEFAULT_ASSET_EMBED_DISPLAY_MODE","DEFAULT_ASSET_EMBED_READ_ONLY","DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT","DEFAULT_ASSET_EMBED_ALLOW_COMMENTS","useRef","useAthenaConfig","useAssetEmbed","useMemo","buildSdkAssetEmbedUrl","resolveAthenaAssetCitationBehavior","resolveAthenaAssetCitationActionBehavior","useCallback","ATHENA_ASSET_EMBED_CONFIG_MESSAGE","useEffect","isAthenaAssetEmbedReadyEvent","isAthenaAssetEmbedContentReadyEvent","isAthenaAssetCitationClickEvent","isAthenaAssetCitationActionEvent","jsxs","jsx","Loader2","AlertCircle","cn"],"mappings":"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"}
1
+ {"version":3,"file":"AthenaAssetEmbed.cjs","sources":["../../src/assets/AthenaAssetEmbed.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertCircle, Loader2 } from \"lucide-react\";\nimport { type CSSProperties, type FC, type IframeHTMLAttributes, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useAthenaConfig } from \"../provider/AthenaContext\";\nimport {\n ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n type AthenaAssetCitationActionEvent,\n type AthenaAssetCitationBehavior,\n type AthenaAssetCitationClickEvent,\n type AthenaAssetCitationReference,\n isAthenaAssetCitationActionEvent,\n isAthenaAssetCitationClickEvent,\n isAthenaAssetEmbedReadyEvent,\n isAthenaAssetEmbedContentReadyEvent,\n resolveAthenaAssetCitationActionBehavior,\n resolveAthenaAssetCitationBehavior,\n} from \"./asset-embed-messages\";\nimport {\n buildSdkAssetEmbedUrl,\n DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n DEFAULT_ASSET_EMBED_READ_ONLY,\n useAssetEmbed,\n} from \"./use-asset-embed\";\n\nexport interface AthenaAssetEmbedProps extends Omit<\n IframeHTMLAttributes<HTMLIFrameElement>,\n \"src\" | \"onError\" | \"onLoad\"\n> {\n assetId: string;\n /** Optional citation target to open inside the embedded asset, such as a PDF page. */\n reference?: AthenaAssetCitationReference;\n /** Defaults to the native full asset experience. */\n displayMode?: \"minimal\" | \"full\";\n /**\n * Defaults to false so authorized users can edit the embedded asset. Any type that keeps comment\n * threads (a Word document, an Athena document, a spreadsheet, a deck, a user interface, a\n * notebook, a PDF, ...) edits only for a viewer signed in to Athena with their own access to the\n * asset: the provider's `token` (or this component's `token`), which this component hands the\n * frame. Anyone else views it read-only.\n */\n readOnly?: boolean;\n /**\n * Offer Quick Edit (⌘J) in a minimal embed (`displayMode=\"minimal\"`): spreadsheets, Word\n * documents and decks, on an editable link (`readOnly: false`) only, to a viewer signed in to\n * Athena with their own access to the asset (the provider's `token`). Billed to the asset's\n * workspace, with an hourly cap per link; the full mode's editors offer it to the signed-in person\n * already. Defaults to false. Ignored with `embedUrl`: a link the host minted says itself.\n */\n allowQuickEdit?: boolean;\n /**\n * The bearer to hand the frame instead of the provider's `token`: for a host that signs its person\n * in with a key it must never hand a frame (Athena's desktop app), the short-lived viewer token\n * agora mints for this link (`POST /api/embed/{token}/viewer-token`), so the frame is the signed-in\n * person. Sent only to the frame's own origin, never in its URL. Without it the provider's `token`\n * goes, as before.\n */\n token?: string;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it a minimal embed shows them only to a signed-in viewer with their\n * own access to the asset. No embed writes a comment. Defaults to false. Ignored with\n * `embedUrl`: a link the host minted says itself.\n */\n allowComments?: boolean;\n expiresInSeconds?: number;\n citationBehavior?: AthenaAssetCitationBehavior;\n onCitationClick?: (event: AthenaAssetCitationClickEvent) => void;\n /** Controls the citation popover's actions, including the (+) Open in Spaces button. */\n citationActionBehavior?: AthenaAssetCitationBehavior;\n onCitationAction?: (event: AthenaAssetCitationActionEvent) => void;\n /**\n * An embed link the host minted itself (`POST /api/embed/generate-token`, its `/embed/<token>/`\n * page): the component shows it and mints none. Without it the component mints one with the\n * provider's credentials, as before.\n */\n embedUrl?: string;\n /** The embedded page said it is ready (its bridge's first message): Athena runs in the frame. */\n onReady?: () => void;\n /** The matching asset viewer has committed usable content in this frame. */\n onContentReady?: () => void;\n onLoad?: () => void;\n onError?: (error: Error) => void;\n loadingFallback?: React.ReactNode;\n errorFallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n}\n\nconst DEFAULT_STYLE: CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n border: 0,\n};\nconst AUTH_SYNC_INTERVAL_MS = 60_000;\n\nexport const AthenaAssetEmbed: FC<AthenaAssetEmbedProps> = ({\n assetId,\n reference,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n expiresInSeconds,\n citationBehavior,\n onCitationClick,\n citationActionBehavior,\n onCitationAction,\n embedUrl: hostEmbedUrl,\n token: frameToken,\n onReady,\n onContentReady,\n onLoad,\n onError,\n loadingFallback,\n errorFallback,\n className,\n style,\n title,\n allow = \"clipboard-read; clipboard-write; fullscreen\",\n ...iframeProps\n}) => {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const athenaConfig = useAthenaConfig();\n const { backendUrl, appUrl, apiKey } = athenaConfig;\n // A link the host minted is shown as given; the hook then mints nothing (no asset to mint for).\n const minted = useAssetEmbed(hostEmbedUrl ? null : assetId, {\n backendUrl,\n appUrl,\n apiKey,\n token: athenaConfig.token,\n getToken: athenaConfig.getToken,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n });\n const embedUrl = hostEmbedUrl ?? minted.embedUrl;\n const isLoading = hostEmbedUrl ? false : minted.isLoading;\n const error = hostEmbedUrl ? null : minted.error;\n const targetOrigin = useMemo(() => {\n if (!embedUrl) return null;\n try {\n return new URL(embedUrl, window.location.href).origin;\n } catch {\n return null;\n }\n }, [embedUrl]);\n const iframeSrc = useMemo(\n () => (embedUrl ? buildSdkAssetEmbedUrl(embedUrl, reference) : null),\n [embedUrl, reference],\n );\n const effectiveCitationBehavior = resolveAthenaAssetCitationBehavior({\n citationBehavior,\n hasCitationClickHandler: !!onCitationClick,\n });\n const effectiveCitationActionBehavior = resolveAthenaAssetCitationActionBehavior({\n citationActionBehavior,\n hasCitationActionHandler: !!onCitationAction,\n });\n\n const sendConfig = useCallback(() => {\n if (!targetOrigin) return;\n // Read when sent: the provider renews its token behind a getter on a config that stays the\n // same object, so an embed that does not render again still sends the current one.\n const viewerToken = frameToken ?? athenaConfig.token;\n iframeRef.current?.contentWindow?.postMessage(\n {\n type: ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n version: 1,\n ...(viewerToken ? { token: viewerToken } : {}),\n citationBehavior: effectiveCitationBehavior,\n citationActionBehavior: effectiveCitationActionBehavior,\n },\n targetOrigin,\n );\n }, [frameToken, athenaConfig, effectiveCitationActionBehavior, effectiveCitationBehavior, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl || !targetOrigin) return;\n const handleMessage = (event: MessageEvent<unknown>) => {\n if (event.origin !== targetOrigin || event.source !== iframeRef.current?.contentWindow) return;\n if (isAthenaAssetEmbedReadyEvent(event.data)) {\n sendConfig();\n onReady?.();\n return;\n }\n if (isAthenaAssetEmbedContentReadyEvent(event.data)) {\n if (event.data.assetId === assetId) onContentReady?.();\n return;\n }\n if (isAthenaAssetCitationClickEvent(event.data)) {\n onCitationClick?.(event.data);\n return;\n }\n if (isAthenaAssetCitationActionEvent(event.data)) {\n onCitationAction?.(event.data);\n }\n };\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [assetId, embedUrl, onCitationAction, onCitationClick, onReady, onContentReady, sendConfig, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl) return;\n sendConfig();\n const interval = window.setInterval(sendConfig, AUTH_SYNC_INTERVAL_MS);\n return () => window.clearInterval(interval);\n }, [embedUrl, sendConfig]);\n\n useEffect(() => {\n if (error) onError?.(new Error(error));\n }, [error, onError]);\n\n if (isLoading) {\n return (\n loadingFallback ?? (\n <output className=\"athena-sdk-chrome flex h-full w-full items-center justify-center\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n <span className=\"sr-only\">Loading asset</span>\n </output>\n )\n );\n }\n\n if (error) {\n const embedError = new Error(error);\n if (typeof errorFallback === \"function\") return errorFallback(embedError);\n return (\n errorFallback ?? (\n <div\n className=\"athena-sdk-chrome flex h-full w-full items-center justify-center gap-2 p-4 text-sm text-muted-foreground\"\n role=\"alert\"\n >\n <AlertCircle className=\"size-4 text-destructive\" />\n <span>{error}</span>\n </div>\n )\n );\n }\n\n if (!embedUrl) return null;\n\n return (\n <iframe\n {...iframeProps}\n ref={iframeRef}\n src={iframeSrc ?? undefined}\n title={title ?? `Athena asset ${assetId}`}\n allow={allow}\n className={cn(\"block h-full w-full\", className)}\n style={{ ...DEFAULT_STYLE, ...style }}\n onLoad={() => {\n sendConfig();\n onLoad?.();\n }}\n />\n );\n};\n"],"names":["DEFAULT_ASSET_EMBED_DISPLAY_MODE","DEFAULT_ASSET_EMBED_READ_ONLY","DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT","DEFAULT_ASSET_EMBED_ALLOW_COMMENTS","useRef","useAthenaConfig","useAssetEmbed","useMemo","buildSdkAssetEmbedUrl","resolveAthenaAssetCitationBehavior","resolveAthenaAssetCitationActionBehavior","useCallback","ATHENA_ASSET_EMBED_CONFIG_MESSAGE","useEffect","isAthenaAssetEmbedReadyEvent","isAthenaAssetEmbedContentReadyEvent","isAthenaAssetCitationClickEvent","isAthenaAssetCitationActionEvent","jsxs","jsx","Loader2","AlertCircle","cn"],"mappings":";;;;;;;;;AA0FA,MAAM,gBAA+B;AAAA,EACnC,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;AACA,MAAM,wBAAwB;AAEvB,MAAM,mBAA8C,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,EACA,cAAcA,cAAAA;AAAAA,EACd,WAAWC,cAAAA;AAAAA,EACX,iBAAiBC,cAAAA;AAAAA,EACjB,gBAAgBC,cAAAA;AAAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,GAAG;AACL,MAAM;AACJ,QAAM,YAAYC,WAAAA,OAAiC,IAAI;AACvD,QAAM,eAAeC,cAAAA,gBAAA;AACrB,QAAM,EAAE,YAAY,QAAQ,OAAA,IAAW;AAEvC,QAAM,SAASC,cAAAA,cAAc,eAAe,OAAO,SAAS;AAAA,IAC1D;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,aAAa;AAAA,IACpB,UAAU,aAAa;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AACD,QAAM,WAAW,gBAAgB,OAAO;AACxC,QAAM,YAAY,eAAe,QAAQ,OAAO;AAChD,QAAM,QAAQ,eAAe,OAAO,OAAO;AAC3C,QAAM,eAAeC,WAAAA,QAAQ,MAAM;AACjC,QAAI,CAAC,SAAU,QAAO;AACtB,QAAI;AACF,aAAO,IAAI,IAAI,UAAU,OAAO,SAAS,IAAI,EAAE;AAAA,IACjD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AACb,QAAM,YAAYA,WAAAA;AAAAA,IAChB,MAAO,WAAWC,cAAAA,sBAAsB,UAAU,SAAS,IAAI;AAAA,IAC/D,CAAC,UAAU,SAAS;AAAA,EAAA;AAEtB,QAAM,4BAA4BC,mBAAAA,mCAAmC;AAAA,IACnE;AAAA,IACA,yBAAyB,CAAC,CAAC;AAAA,EAAA,CAC5B;AACD,QAAM,kCAAkCC,mBAAAA,yCAAyC;AAAA,IAC/E;AAAA,IACA,0BAA0B,CAAC,CAAC;AAAA,EAAA,CAC7B;AAED,QAAM,aAAaC,WAAAA,YAAY,MAAM;;AACnC,QAAI,CAAC,aAAc;AAGnB,UAAM,cAAc,cAAc,aAAa;AAC/C,0BAAU,YAAV,mBAAmB,kBAAnB,mBAAkC;AAAA,MAChC;AAAA,QACE,MAAMC,mBAAAA;AAAAA,QACN,SAAS;AAAA,QACT,GAAI,cAAc,EAAE,OAAO,YAAA,IAAgB,CAAA;AAAA,QAC3C,kBAAkB;AAAA,QAClB,wBAAwB;AAAA,MAAA;AAAA,MAE1B;AAAA;AAAA,EAEJ,GAAG,CAAC,YAAY,cAAc,iCAAiC,2BAA2B,YAAY,CAAC;AAEvGC,aAAAA,UAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,aAAc;AAChC,UAAM,gBAAgB,CAAC,UAAiC;;AACtD,UAAI,MAAM,WAAW,gBAAgB,MAAM,aAAW,eAAU,YAAV,mBAAmB,eAAe;AACxF,UAAIC,mBAAAA,6BAA6B,MAAM,IAAI,GAAG;AAC5C,mBAAA;AACA;AACA;AAAA,MACF;AACA,UAAIC,mBAAAA,oCAAoC,MAAM,IAAI,GAAG;AACnD,YAAI,MAAM,KAAK,YAAY,QAAS;AACpC;AAAA,MACF;AACA,UAAIC,mBAAAA,gCAAgC,MAAM,IAAI,GAAG;AAC/C,2DAAkB,MAAM;AACxB;AAAA,MACF;AACA,UAAIC,mBAAAA,iCAAiC,MAAM,IAAI,GAAG;AAChD,6DAAmB,MAAM;AAAA,MAC3B;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,aAAa;AAChD,WAAO,MAAM,OAAO,oBAAoB,WAAW,aAAa;AAAA,EAClE,GAAG,CAAC,SAAS,UAAU,kBAAkB,iBAAiB,SAAS,gBAAgB,YAAY,YAAY,CAAC;AAE5GJ,aAAAA,UAAU,MAAM;AACd,QAAI,CAAC,SAAU;AACf,eAAA;AACA,UAAM,WAAW,OAAO,YAAY,YAAY,qBAAqB;AACrE,WAAO,MAAM,OAAO,cAAc,QAAQ;AAAA,EAC5C,GAAG,CAAC,UAAU,UAAU,CAAC;AAEzBA,aAAAA,UAAU,MAAM;AACd,QAAI,MAAO,oCAAU,IAAI,MAAM,KAAK;AAAA,EACtC,GAAG,CAAC,OAAO,OAAO,CAAC;AAEnB,MAAI,WAAW;AACb,WACE,mBACEK,2BAAAA,KAAC,UAAA,EAAO,WAAU,oEAChB,UAAA;AAAA,MAAAC,2BAAAA,IAACC,YAAAA,SAAA,EAAQ,WAAU,4CAAA,CAA4C;AAAA,MAC/DD,2BAAAA,IAAC,QAAA,EAAK,WAAU,WAAU,UAAA,gBAAA,CAAa;AAAA,IAAA,GACzC;AAAA,EAGN;AAEA,MAAI,OAAO;AACT,UAAM,aAAa,IAAI,MAAM,KAAK;AAClC,QAAI,OAAO,kBAAkB,WAAY,QAAO,cAAc,UAAU;AACxE,WACE,iBACED,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAU;AAAA,QACV,MAAK;AAAA,QAEL,UAAA;AAAA,UAAAC,2BAAAA,IAACE,YAAAA,aAAA,EAAY,WAAU,0BAAA,CAA0B;AAAA,UACjDF,2BAAAA,IAAC,UAAM,UAAA,MAAA,CAAM;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIrB;AAEA,MAAI,CAAC,SAAU,QAAO;AAEtB,SACEA,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,KAAK;AAAA,MACL,KAAK,aAAa;AAAA,MAClB,OAAO,SAAS,gBAAgB,OAAO;AAAA,MACvC;AAAA,MACA,WAAWG,OAAAA,GAAG,uBAAuB,SAAS;AAAA,MAC9C,OAAO,EAAE,GAAG,eAAe,GAAG,MAAA;AAAA,MAC9B,QAAQ,MAAM;AACZ,mBAAA;AACA;AAAA,MACF;AAAA,IAAA;AAAA,EAAA;AAGN;;"}
@@ -7,20 +7,29 @@ export interface AthenaAssetEmbedProps extends Omit<IframeHTMLAttributes<HTMLIFr
7
7
  /** Defaults to the native full asset experience. */
8
8
  displayMode?: "minimal" | "full";
9
9
  /**
10
- * Defaults to false so authorized users can edit the embedded asset. A Word document, an Athena
11
- * document, a spreadsheet, a user interface or a deck edits only for a viewer who is signed in
12
- * to Athena: the provider's `token`, which this component hands the frame. Anyone else views it
13
- * read-only.
10
+ * Defaults to false so authorized users can edit the embedded asset. Any type that keeps comment
11
+ * threads (a Word document, an Athena document, a spreadsheet, a deck, a user interface, a
12
+ * notebook, a PDF, ...) edits only for a viewer signed in to Athena with their own access to the
13
+ * asset: the provider's `token` (or this component's `token`), which this component hands the
14
+ * frame. Anyone else views it read-only.
14
15
  */
15
16
  readOnly?: boolean;
16
17
  /**
17
18
  * Offer Quick Edit (⌘J) in a minimal embed (`displayMode="minimal"`): spreadsheets, Word
18
- * documents and decks, on an editable link (`readOnly: false`) only, to a viewer who is signed in
19
- * to Athena (the provider's `token`). Billed to the asset's workspace, with an hourly cap per
20
- * link; the full mode's editors offer it to the signed-in person already. Defaults to false.
21
- * Ignored with `embedUrl`: a link the host minted says itself.
19
+ * documents and decks, on an editable link (`readOnly: false`) only, to a viewer signed in to
20
+ * Athena with their own access to the asset (the provider's `token`). Billed to the asset's
21
+ * workspace, with an hourly cap per link; the full mode's editors offer it to the signed-in person
22
+ * already. Defaults to false. Ignored with `embedUrl`: a link the host minted says itself.
22
23
  */
23
24
  allowQuickEdit?: boolean;
25
+ /**
26
+ * The bearer to hand the frame instead of the provider's `token`: for a host that signs its person
27
+ * in with a key it must never hand a frame (Athena's desktop app), the short-lived viewer token
28
+ * agora mints for this link (`POST /api/embed/{token}/viewer-token`), so the frame is the signed-in
29
+ * person. Sent only to the frame's own origin, never in its URL. Without it the provider's `token`
30
+ * goes, as before.
31
+ */
32
+ token?: string;
24
33
  /**
25
34
  * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not
26
35
  * signed in included. Without it a minimal embed shows them only to a signed-in viewer with their
@@ -24,6 +24,7 @@ const AthenaAssetEmbed = ({
24
24
  citationActionBehavior,
25
25
  onCitationAction,
26
26
  embedUrl: hostEmbedUrl,
27
+ token: frameToken,
27
28
  onReady,
28
29
  onContentReady,
29
30
  onLoad,
@@ -77,17 +78,18 @@ const AthenaAssetEmbed = ({
77
78
  const sendConfig = useCallback(() => {
78
79
  var _a, _b;
79
80
  if (!targetOrigin) return;
81
+ const viewerToken = frameToken ?? athenaConfig.token;
80
82
  (_b = (_a = iframeRef.current) == null ? void 0 : _a.contentWindow) == null ? void 0 : _b.postMessage(
81
83
  {
82
84
  type: ATHENA_ASSET_EMBED_CONFIG_MESSAGE,
83
85
  version: 1,
84
- ...athenaConfig.token ? { token: athenaConfig.token } : {},
86
+ ...viewerToken ? { token: viewerToken } : {},
85
87
  citationBehavior: effectiveCitationBehavior,
86
88
  citationActionBehavior: effectiveCitationActionBehavior
87
89
  },
88
90
  targetOrigin
89
91
  );
90
- }, [athenaConfig, effectiveCitationActionBehavior, effectiveCitationBehavior, targetOrigin]);
92
+ }, [frameToken, athenaConfig, effectiveCitationActionBehavior, effectiveCitationBehavior, targetOrigin]);
91
93
  useEffect(() => {
92
94
  if (!embedUrl || !targetOrigin) return;
93
95
  const handleMessage = (event) => {
@@ -1 +1 @@
1
- {"version":3,"file":"AthenaAssetEmbed.js","sources":["../../src/assets/AthenaAssetEmbed.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertCircle, Loader2 } from \"lucide-react\";\nimport { type CSSProperties, type FC, type IframeHTMLAttributes, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useAthenaConfig } from \"../provider/AthenaContext\";\nimport {\n ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n type AthenaAssetCitationActionEvent,\n type AthenaAssetCitationBehavior,\n type AthenaAssetCitationClickEvent,\n type AthenaAssetCitationReference,\n isAthenaAssetCitationActionEvent,\n isAthenaAssetCitationClickEvent,\n isAthenaAssetEmbedReadyEvent,\n isAthenaAssetEmbedContentReadyEvent,\n resolveAthenaAssetCitationActionBehavior,\n resolveAthenaAssetCitationBehavior,\n} from \"./asset-embed-messages\";\nimport {\n buildSdkAssetEmbedUrl,\n DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n DEFAULT_ASSET_EMBED_READ_ONLY,\n useAssetEmbed,\n} from \"./use-asset-embed\";\n\nexport interface AthenaAssetEmbedProps extends Omit<\n IframeHTMLAttributes<HTMLIFrameElement>,\n \"src\" | \"onError\" | \"onLoad\"\n> {\n assetId: string;\n /** Optional citation target to open inside the embedded asset, such as a PDF page. */\n reference?: AthenaAssetCitationReference;\n /** Defaults to the native full asset experience. */\n displayMode?: \"minimal\" | \"full\";\n /**\n * Defaults to false so authorized users can edit the embedded asset. A Word document, an Athena\n * document, a spreadsheet, a user interface or a deck edits only for a viewer who is signed in\n * to Athena: the provider's `token`, which this component hands the frame. Anyone else views it\n * read-only.\n */\n readOnly?: boolean;\n /**\n * Offer Quick Edit (⌘J) in a minimal embed (`displayMode=\"minimal\"`): spreadsheets, Word\n * documents and decks, on an editable link (`readOnly: false`) only, to a viewer who is signed in\n * to Athena (the provider's `token`). Billed to the asset's workspace, with an hourly cap per\n * link; the full mode's editors offer it to the signed-in person already. Defaults to false.\n * Ignored with `embedUrl`: a link the host minted says itself.\n */\n allowQuickEdit?: boolean;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it a minimal embed shows them only to a signed-in viewer with their\n * own access to the asset. No embed writes a comment. Defaults to false. Ignored with\n * `embedUrl`: a link the host minted says itself.\n */\n allowComments?: boolean;\n expiresInSeconds?: number;\n citationBehavior?: AthenaAssetCitationBehavior;\n onCitationClick?: (event: AthenaAssetCitationClickEvent) => void;\n /** Controls the citation popover's actions, including the (+) Open in Spaces button. */\n citationActionBehavior?: AthenaAssetCitationBehavior;\n onCitationAction?: (event: AthenaAssetCitationActionEvent) => void;\n /**\n * An embed link the host minted itself (`POST /api/embed/generate-token`, its `/embed/<token>/`\n * page): the component shows it and mints none. Without it the component mints one with the\n * provider's credentials, as before.\n */\n embedUrl?: string;\n /** The embedded page said it is ready (its bridge's first message): Athena runs in the frame. */\n onReady?: () => void;\n /** The matching asset viewer has committed usable content in this frame. */\n onContentReady?: () => void;\n onLoad?: () => void;\n onError?: (error: Error) => void;\n loadingFallback?: React.ReactNode;\n errorFallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n}\n\nconst DEFAULT_STYLE: CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n border: 0,\n};\nconst AUTH_SYNC_INTERVAL_MS = 60_000;\n\nexport const AthenaAssetEmbed: FC<AthenaAssetEmbedProps> = ({\n assetId,\n reference,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n expiresInSeconds,\n citationBehavior,\n onCitationClick,\n citationActionBehavior,\n onCitationAction,\n embedUrl: hostEmbedUrl,\n onReady,\n onContentReady,\n onLoad,\n onError,\n loadingFallback,\n errorFallback,\n className,\n style,\n title,\n allow = \"clipboard-read; clipboard-write; fullscreen\",\n ...iframeProps\n}) => {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const athenaConfig = useAthenaConfig();\n const { backendUrl, appUrl, apiKey } = athenaConfig;\n // A link the host minted is shown as given; the hook then mints nothing (no asset to mint for).\n const minted = useAssetEmbed(hostEmbedUrl ? null : assetId, {\n backendUrl,\n appUrl,\n apiKey,\n token: athenaConfig.token,\n getToken: athenaConfig.getToken,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n });\n const embedUrl = hostEmbedUrl ?? minted.embedUrl;\n const isLoading = hostEmbedUrl ? false : minted.isLoading;\n const error = hostEmbedUrl ? null : minted.error;\n const targetOrigin = useMemo(() => {\n if (!embedUrl) return null;\n try {\n return new URL(embedUrl, window.location.href).origin;\n } catch {\n return null;\n }\n }, [embedUrl]);\n const iframeSrc = useMemo(\n () => (embedUrl ? buildSdkAssetEmbedUrl(embedUrl, reference) : null),\n [embedUrl, reference],\n );\n const effectiveCitationBehavior = resolveAthenaAssetCitationBehavior({\n citationBehavior,\n hasCitationClickHandler: !!onCitationClick,\n });\n const effectiveCitationActionBehavior = resolveAthenaAssetCitationActionBehavior({\n citationActionBehavior,\n hasCitationActionHandler: !!onCitationAction,\n });\n\n const sendConfig = useCallback(() => {\n if (!targetOrigin) return;\n iframeRef.current?.contentWindow?.postMessage(\n {\n type: ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n version: 1,\n ...(athenaConfig.token ? { token: athenaConfig.token } : {}),\n citationBehavior: effectiveCitationBehavior,\n citationActionBehavior: effectiveCitationActionBehavior,\n },\n targetOrigin,\n );\n }, [athenaConfig, effectiveCitationActionBehavior, effectiveCitationBehavior, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl || !targetOrigin) return;\n const handleMessage = (event: MessageEvent<unknown>) => {\n if (event.origin !== targetOrigin || event.source !== iframeRef.current?.contentWindow) return;\n if (isAthenaAssetEmbedReadyEvent(event.data)) {\n sendConfig();\n onReady?.();\n return;\n }\n if (isAthenaAssetEmbedContentReadyEvent(event.data)) {\n if (event.data.assetId === assetId) onContentReady?.();\n return;\n }\n if (isAthenaAssetCitationClickEvent(event.data)) {\n onCitationClick?.(event.data);\n return;\n }\n if (isAthenaAssetCitationActionEvent(event.data)) {\n onCitationAction?.(event.data);\n }\n };\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [assetId, embedUrl, onCitationAction, onCitationClick, onReady, onContentReady, sendConfig, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl) return;\n sendConfig();\n const interval = window.setInterval(sendConfig, AUTH_SYNC_INTERVAL_MS);\n return () => window.clearInterval(interval);\n }, [embedUrl, sendConfig]);\n\n useEffect(() => {\n if (error) onError?.(new Error(error));\n }, [error, onError]);\n\n if (isLoading) {\n return (\n loadingFallback ?? (\n <output className=\"athena-sdk-chrome flex h-full w-full items-center justify-center\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n <span className=\"sr-only\">Loading asset</span>\n </output>\n )\n );\n }\n\n if (error) {\n const embedError = new Error(error);\n if (typeof errorFallback === \"function\") return errorFallback(embedError);\n return (\n errorFallback ?? (\n <div\n className=\"athena-sdk-chrome flex h-full w-full items-center justify-center gap-2 p-4 text-sm text-muted-foreground\"\n role=\"alert\"\n >\n <AlertCircle className=\"size-4 text-destructive\" />\n <span>{error}</span>\n </div>\n )\n );\n }\n\n if (!embedUrl) return null;\n\n return (\n <iframe\n {...iframeProps}\n ref={iframeRef}\n src={iframeSrc ?? undefined}\n title={title ?? `Athena asset ${assetId}`}\n allow={allow}\n className={cn(\"block h-full w-full\", className)}\n style={{ ...DEFAULT_STYLE, ...style }}\n onLoad={() => {\n sendConfig();\n onLoad?.();\n }}\n />\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AthenaAssetEmbed.js","sources":["../../src/assets/AthenaAssetEmbed.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertCircle, Loader2 } from \"lucide-react\";\nimport { type CSSProperties, type FC, type IframeHTMLAttributes, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { useAthenaConfig } from \"../provider/AthenaContext\";\nimport {\n ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n type AthenaAssetCitationActionEvent,\n type AthenaAssetCitationBehavior,\n type AthenaAssetCitationClickEvent,\n type AthenaAssetCitationReference,\n isAthenaAssetCitationActionEvent,\n isAthenaAssetCitationClickEvent,\n isAthenaAssetEmbedReadyEvent,\n isAthenaAssetEmbedContentReadyEvent,\n resolveAthenaAssetCitationActionBehavior,\n resolveAthenaAssetCitationBehavior,\n} from \"./asset-embed-messages\";\nimport {\n buildSdkAssetEmbedUrl,\n DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n DEFAULT_ASSET_EMBED_READ_ONLY,\n useAssetEmbed,\n} from \"./use-asset-embed\";\n\nexport interface AthenaAssetEmbedProps extends Omit<\n IframeHTMLAttributes<HTMLIFrameElement>,\n \"src\" | \"onError\" | \"onLoad\"\n> {\n assetId: string;\n /** Optional citation target to open inside the embedded asset, such as a PDF page. */\n reference?: AthenaAssetCitationReference;\n /** Defaults to the native full asset experience. */\n displayMode?: \"minimal\" | \"full\";\n /**\n * Defaults to false so authorized users can edit the embedded asset. Any type that keeps comment\n * threads (a Word document, an Athena document, a spreadsheet, a deck, a user interface, a\n * notebook, a PDF, ...) edits only for a viewer signed in to Athena with their own access to the\n * asset: the provider's `token` (or this component's `token`), which this component hands the\n * frame. Anyone else views it read-only.\n */\n readOnly?: boolean;\n /**\n * Offer Quick Edit (⌘J) in a minimal embed (`displayMode=\"minimal\"`): spreadsheets, Word\n * documents and decks, on an editable link (`readOnly: false`) only, to a viewer signed in to\n * Athena with their own access to the asset (the provider's `token`). Billed to the asset's\n * workspace, with an hourly cap per link; the full mode's editors offer it to the signed-in person\n * already. Defaults to false. Ignored with `embedUrl`: a link the host minted says itself.\n */\n allowQuickEdit?: boolean;\n /**\n * The bearer to hand the frame instead of the provider's `token`: for a host that signs its person\n * in with a key it must never hand a frame (Athena's desktop app), the short-lived viewer token\n * agora mints for this link (`POST /api/embed/{token}/viewer-token`), so the frame is the signed-in\n * person. Sent only to the frame's own origin, never in its URL. Without it the provider's `token`\n * goes, as before.\n */\n token?: string;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it a minimal embed shows them only to a signed-in viewer with their\n * own access to the asset. No embed writes a comment. Defaults to false. Ignored with\n * `embedUrl`: a link the host minted says itself.\n */\n allowComments?: boolean;\n expiresInSeconds?: number;\n citationBehavior?: AthenaAssetCitationBehavior;\n onCitationClick?: (event: AthenaAssetCitationClickEvent) => void;\n /** Controls the citation popover's actions, including the (+) Open in Spaces button. */\n citationActionBehavior?: AthenaAssetCitationBehavior;\n onCitationAction?: (event: AthenaAssetCitationActionEvent) => void;\n /**\n * An embed link the host minted itself (`POST /api/embed/generate-token`, its `/embed/<token>/`\n * page): the component shows it and mints none. Without it the component mints one with the\n * provider's credentials, as before.\n */\n embedUrl?: string;\n /** The embedded page said it is ready (its bridge's first message): Athena runs in the frame. */\n onReady?: () => void;\n /** The matching asset viewer has committed usable content in this frame. */\n onContentReady?: () => void;\n onLoad?: () => void;\n onError?: (error: Error) => void;\n loadingFallback?: React.ReactNode;\n errorFallback?: React.ReactNode | ((error: Error) => React.ReactNode);\n}\n\nconst DEFAULT_STYLE: CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n border: 0,\n};\nconst AUTH_SYNC_INTERVAL_MS = 60_000;\n\nexport const AthenaAssetEmbed: FC<AthenaAssetEmbedProps> = ({\n assetId,\n reference,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n expiresInSeconds,\n citationBehavior,\n onCitationClick,\n citationActionBehavior,\n onCitationAction,\n embedUrl: hostEmbedUrl,\n token: frameToken,\n onReady,\n onContentReady,\n onLoad,\n onError,\n loadingFallback,\n errorFallback,\n className,\n style,\n title,\n allow = \"clipboard-read; clipboard-write; fullscreen\",\n ...iframeProps\n}) => {\n const iframeRef = useRef<HTMLIFrameElement | null>(null);\n const athenaConfig = useAthenaConfig();\n const { backendUrl, appUrl, apiKey } = athenaConfig;\n // A link the host minted is shown as given; the hook then mints nothing (no asset to mint for).\n const minted = useAssetEmbed(hostEmbedUrl ? null : assetId, {\n backendUrl,\n appUrl,\n apiKey,\n token: athenaConfig.token,\n getToken: athenaConfig.getToken,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n });\n const embedUrl = hostEmbedUrl ?? minted.embedUrl;\n const isLoading = hostEmbedUrl ? false : minted.isLoading;\n const error = hostEmbedUrl ? null : minted.error;\n const targetOrigin = useMemo(() => {\n if (!embedUrl) return null;\n try {\n return new URL(embedUrl, window.location.href).origin;\n } catch {\n return null;\n }\n }, [embedUrl]);\n const iframeSrc = useMemo(\n () => (embedUrl ? buildSdkAssetEmbedUrl(embedUrl, reference) : null),\n [embedUrl, reference],\n );\n const effectiveCitationBehavior = resolveAthenaAssetCitationBehavior({\n citationBehavior,\n hasCitationClickHandler: !!onCitationClick,\n });\n const effectiveCitationActionBehavior = resolveAthenaAssetCitationActionBehavior({\n citationActionBehavior,\n hasCitationActionHandler: !!onCitationAction,\n });\n\n const sendConfig = useCallback(() => {\n if (!targetOrigin) return;\n // Read when sent: the provider renews its token behind a getter on a config that stays the\n // same object, so an embed that does not render again still sends the current one.\n const viewerToken = frameToken ?? athenaConfig.token;\n iframeRef.current?.contentWindow?.postMessage(\n {\n type: ATHENA_ASSET_EMBED_CONFIG_MESSAGE,\n version: 1,\n ...(viewerToken ? { token: viewerToken } : {}),\n citationBehavior: effectiveCitationBehavior,\n citationActionBehavior: effectiveCitationActionBehavior,\n },\n targetOrigin,\n );\n }, [frameToken, athenaConfig, effectiveCitationActionBehavior, effectiveCitationBehavior, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl || !targetOrigin) return;\n const handleMessage = (event: MessageEvent<unknown>) => {\n if (event.origin !== targetOrigin || event.source !== iframeRef.current?.contentWindow) return;\n if (isAthenaAssetEmbedReadyEvent(event.data)) {\n sendConfig();\n onReady?.();\n return;\n }\n if (isAthenaAssetEmbedContentReadyEvent(event.data)) {\n if (event.data.assetId === assetId) onContentReady?.();\n return;\n }\n if (isAthenaAssetCitationClickEvent(event.data)) {\n onCitationClick?.(event.data);\n return;\n }\n if (isAthenaAssetCitationActionEvent(event.data)) {\n onCitationAction?.(event.data);\n }\n };\n window.addEventListener(\"message\", handleMessage);\n return () => window.removeEventListener(\"message\", handleMessage);\n }, [assetId, embedUrl, onCitationAction, onCitationClick, onReady, onContentReady, sendConfig, targetOrigin]);\n\n useEffect(() => {\n if (!embedUrl) return;\n sendConfig();\n const interval = window.setInterval(sendConfig, AUTH_SYNC_INTERVAL_MS);\n return () => window.clearInterval(interval);\n }, [embedUrl, sendConfig]);\n\n useEffect(() => {\n if (error) onError?.(new Error(error));\n }, [error, onError]);\n\n if (isLoading) {\n return (\n loadingFallback ?? (\n <output className=\"athena-sdk-chrome flex h-full w-full items-center justify-center\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n <span className=\"sr-only\">Loading asset</span>\n </output>\n )\n );\n }\n\n if (error) {\n const embedError = new Error(error);\n if (typeof errorFallback === \"function\") return errorFallback(embedError);\n return (\n errorFallback ?? (\n <div\n className=\"athena-sdk-chrome flex h-full w-full items-center justify-center gap-2 p-4 text-sm text-muted-foreground\"\n role=\"alert\"\n >\n <AlertCircle className=\"size-4 text-destructive\" />\n <span>{error}</span>\n </div>\n )\n );\n }\n\n if (!embedUrl) return null;\n\n return (\n <iframe\n {...iframeProps}\n ref={iframeRef}\n src={iframeSrc ?? undefined}\n title={title ?? `Athena asset ${assetId}`}\n allow={allow}\n className={cn(\"block h-full w-full\", className)}\n style={{ ...DEFAULT_STYLE, ...style }}\n onLoad={() => {\n sendConfig();\n onLoad?.();\n }}\n />\n );\n};\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-asset-embed.cjs","sources":["../../src/assets/use-asset-embed.ts"],"sourcesContent":["import { QueryClient, QueryClientContext, useQuery } from '@tanstack/react-query';\nimport { useContext, useRef } from 'react';\nimport { deriveAthenaAppUrl } from '../lib/athena-urls';\nimport { keyPartFingerprint } from '../lib/use-athena-rest-query';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaAssetCitationReference } from './asset-embed-messages';\n\nexport interface UseAssetEmbedOptions {\n /**\n * Mint a view-only link. A ceiling, not a grant: the link edits only what its creator may edit,\n * and a Word document, an Athena document, a spreadsheet, a user interface or a deck edits only\n * for a viewer who is signed in to Athena (the provider's `token`, which `AthenaAssetEmbed`\n * hands the frame). Anyone else views such a link read-only. Defaults to `false`.\n */\n readOnly?: boolean;\n expiresInSeconds?: number;\n /** Native Olympus surface rendered by the iframe. Defaults to `full`. */\n displayMode?: 'minimal' | 'full';\n /**\n * Offer Quick Edit (⌘J, an AI rewrite of the selection) in a minimal embed, which runs on the\n * link rather than an Athena sign-in: spreadsheets, Word documents and decks, on an editable\n * link (`readOnly: false`) only, and only to a viewer who is signed in to Athena (the provider's\n * `token`, which `AthenaAssetEmbed` hands the frame). Its runs are billed to the asset's\n * workspace under the link's creator, and each link has its own hourly cap. The full mode's\n * editors offer it to the signed-in person already. Defaults to `false`.\n */\n allowQuickEdit?: boolean;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it, a minimal embed shows them only to a signed-in viewer who has\n * their own access to the asset (the bearer this component hands the iframe, or an Athena\n * session). No embed ever writes a comment. Defaults to `false`.\n */\n allowComments?: boolean;\n}\n\nexport const DEFAULT_ASSET_EMBED_READ_ONLY = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_COMMENTS = false;\nexport const DEFAULT_ASSET_EMBED_DISPLAY_MODE = 'full' as const;\n\nexport interface UseAssetEmbedResult {\n embedUrl: string | null;\n isLoading: boolean;\n error: string | null;\n}\n\nexport function buildAssetEmbedRequestBody({\n assetId,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n}: {\n assetId: string;\n readOnly: boolean;\n expiresInSeconds: number;\n displayMode: 'minimal' | 'full';\n allowQuickEdit?: boolean;\n allowComments?: boolean;\n}) {\n return {\n asset_id: assetId,\n read_only: readOnly,\n expires_in_seconds: expiresInSeconds,\n display_mode: displayMode,\n // Sent only when asked: an agora that predates it is never handed an unknown field.\n ...(allowQuickEdit && { allow_quick_edit: true }),\n ...(allowComments && { allow_comments: true }),\n };\n}\n\n/**\n * The embed links' query client when the host provides none: one for the page, so every embed of\n * one asset, mode and identity shares one minted link until it expires.\n */\nconst embedQueryClient = new QueryClient();\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_PATTERN = /^.+--(\\d+)\\.(?:app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/;\nconst OLYMPUS_WORKSPACE_PORT = '8082';\n\nconst getUrl = (value?: string | null): URL | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value);\n } catch {\n return null;\n }\n};\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst getOrigin = (value?: string | null): string | null => getUrl(value)?.origin ?? null;\n\nconst getAthenaWorkspacePort = (url: URL): string | null => {\n const match = url.hostname.toLowerCase().match(ATHENA_WORKSPACE_HOSTNAME_PATTERN);\n return match?.[1] ?? null;\n};\n\nconst isEmbedCapableAthenaHost = (url: URL): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (ATHENA_APP_HOSTNAMES.has(hostname) || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)) {\n return true;\n }\n\n return getAthenaWorkspacePort(url) === OLYMPUS_WORKSPACE_PORT;\n};\n\nconst shouldPreferDerivedEmbedAppUrl = ({\n appUrl,\n derivedAppUrl,\n currentOrigin,\n}: {\n appUrl: string;\n derivedAppUrl: string | null;\n currentOrigin?: string | null;\n}): boolean => {\n if (!derivedAppUrl) {\n return false;\n }\n\n const resolvedAppUrl = getUrl(appUrl);\n if (!resolvedAppUrl) {\n return true;\n }\n\n const workspacePort = getAthenaWorkspacePort(resolvedAppUrl);\n if (workspacePort && workspacePort !== OLYMPUS_WORKSPACE_PORT) {\n return true;\n }\n\n if (isEmbedCapableAthenaHost(resolvedAppUrl)) {\n return false;\n }\n\n const resolvedCurrentOrigin = getOrigin(currentOrigin ?? getCurrentOrigin());\n return !!resolvedCurrentOrigin && resolvedAppUrl.origin === resolvedCurrentOrigin;\n};\n\nconst resolveEmbedAppUrl = ({\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string | null => {\n const derivedAppUrl = deriveAthenaAppUrl({ backendUrl });\n\n if (!appUrl) {\n return derivedAppUrl;\n }\n\n if (shouldPreferDerivedEmbedAppUrl({ appUrl, derivedAppUrl, currentOrigin })) {\n return derivedAppUrl;\n }\n\n return appUrl;\n};\n\nexport function resolveAssetEmbedUrl({\n embedUrl,\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n embedUrl: string;\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string {\n try {\n const resolvedEmbedUrl = new URL(embedUrl);\n if (!resolvedEmbedUrl.pathname.startsWith('/embed/')) {\n return embedUrl;\n }\n\n const embedAppUrl = resolveEmbedAppUrl({\n appUrl,\n backendUrl,\n currentOrigin,\n });\n if (!embedAppUrl) {\n return embedUrl;\n }\n\n const resolvedAppUrl = new URL(embedAppUrl);\n resolvedEmbedUrl.protocol = resolvedAppUrl.protocol;\n resolvedEmbedUrl.host = resolvedAppUrl.host;\n return resolvedEmbedUrl.toString();\n } catch {\n return embedUrl;\n }\n}\n\nfunction setFiniteNumberParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'number' && Number.isFinite(value)) {\n params.set(key, String(value));\n }\n}\n\nfunction setNonEmptyStringParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'string' && value.length > 0) {\n params.set(key, value);\n }\n}\n\n/** The SDK's own embed parameter: it marks the bridge, never a place, so no anchor writes it. */\nconst SDK_BRIDGE_PARAM = 'athena_sdk_bridge';\n\n/**\n * The pairs of an anchor given as its raw query — the string an Athena link, citation or search\n * result carries (`anchor=slide&slide_index=3&slide_id=…`, `anchor=time&time=0:42`), alone or\n * after the `?` of an asset ref (`asset_x?anchor=…`) — read as URL parameters, or `null` when the\n * string holds none, or a parameter with no name. The SDK never interprets the anchor: the embed\n * page parses it with the references grammar, so every kind of place travels as written.\n */\nfunction rawAnchorQueryPairs(query: string): Array<[string, string]> | null {\n const queryIndex = query.indexOf('?');\n const search = queryIndex === -1 ? query : query.slice(queryIndex + 1);\n const pairs = [...new URLSearchParams(search)].filter(([key]) => key !== SDK_BRIDGE_PARAM);\n if (pairs.length === 0 || pairs.some(([key]) => key.length === 0)) return null;\n return pairs;\n}\n\nfunction appendRawAnchorQuery({ params, query }: { params: URLSearchParams; query: string }): void {\n const pairs = rawAnchorQueryPairs(query);\n if (!pairs) return;\n // Verbatim: a key the URL already has is replaced, and a repeated key keeps every value.\n for (const [key] of pairs) params.delete(key);\n for (const [key, value] of pairs) params.append(key, value);\n}\n\nfunction appendTypedAnchorParams({\n params,\n anchor,\n}: {\n params: URLSearchParams;\n anchor: object & { type: string };\n}): void {\n switch (anchor.type) {\n case 'page':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n break;\n case 'page_range':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'start_page',\n value: 'startPage' in anchor ? anchor.startPage : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'end_page',\n value: 'endPage' in anchor ? anchor.endPage : undefined,\n });\n break;\n case 'page_rect':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'x',\n value: 'x' in anchor ? anchor.x : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'y',\n value: 'y' in anchor ? anchor.y : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'width',\n value: 'width' in anchor ? anchor.width : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'height',\n value: 'height' in anchor ? anchor.height : undefined,\n });\n if (\n 'unit' in anchor &&\n (anchor.unit === 'percent' || anchor.unit === 'point')\n ) {\n params.set('unit', anchor.unit);\n }\n break;\n case 'page_text':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n if ('text' in anchor && typeof anchor.text === 'string') {\n params.set('text', anchor.text);\n }\n // Optional context that disambiguates a repeated phrase, and the hash of\n // the cited unit's normalized text; the embed parses both with the shared\n // `@athenaintel/references` parser.\n setNonEmptyStringParam({ params, key: 'prefix', value: 'prefix' in anchor ? anchor.prefix : undefined });\n setNonEmptyStringParam({ params, key: 'suffix', value: 'suffix' in anchor ? anchor.suffix : undefined });\n setNonEmptyStringParam({ params, key: 'hash', value: 'hash' in anchor ? anchor.hash : undefined });\n break;\n // Sheet anchors ride the same query contract the embed page parses with\n // the shared `@athenaintel/references` parser.\n case 'sheet_cell':\n case 'sheet_range':\n case 'sheet_tab':\n case 'sheet_table':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'sheet_id',\n value: 'sheetId' in anchor ? anchor.sheetId : undefined,\n });\n if ('cell' in anchor && typeof anchor.cell === 'string') {\n params.set('cell', anchor.cell);\n }\n if ('range' in anchor && typeof anchor.range === 'string') {\n params.set('range', anchor.range);\n }\n if ('tableName' in anchor && typeof anchor.tableName === 'string') {\n params.set('table_name', anchor.tableName);\n }\n if ('fallbackRange' in anchor && typeof anchor.fallbackRange === 'string') {\n params.set('fallback_range', anchor.fallbackRange);\n }\n break;\n }\n}\n\n/**\n * Writes a citation's place into an embed URL: its anchor and its excerpt (the passage the embed\n * finds by its words — alone, when the citation names no anchor). The anchor is either its raw\n * query string, forwarded as written whatever kind of place it names, or one of the typed page and\n * sheet anchors. Athena on the web's Spaces links read the same grammar, so a host that opens a\n * cited place there writes it the same way.\n */\nexport function appendCitationReferenceParams({\n params,\n reference,\n}: {\n params: URLSearchParams;\n reference?: AthenaAssetCitationReference;\n}): void {\n const anchor = reference?.anchor;\n if (typeof anchor === 'string') {\n appendRawAnchorQuery({ params, query: anchor });\n } else if (anchor && typeof anchor === 'object' && 'type' in anchor && typeof anchor.type === 'string') {\n appendTypedAnchorParams({ params, anchor: { ...anchor, type: anchor.type } });\n }\n\n const excerpt = reference?.meta?.excerpt;\n if (typeof excerpt === 'string' && excerpt) {\n params.set('excerpt', excerpt);\n }\n}\n\nexport function buildSdkAssetEmbedUrl(embedUrl: string, reference?: AthenaAssetCitationReference): string {\n try {\n const url = new URL(embedUrl);\n url.searchParams.set('athena_sdk_bridge', '1');\n appendCitationReferenceParams({ params: url.searchParams, reference });\n return url.toString();\n } catch {\n return embedUrl;\n }\n}\n\nconst getJwtPayload = (token: string): Record<string, unknown> | null => {\n try {\n const payloadSegment = token.split('.')[1];\n if (!payloadSegment || typeof atob !== 'function') return null;\n const normalized = payloadSegment.replace(/-/g, '+').replace(/_/g, '/');\n const payload: unknown = JSON.parse(\n atob(normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=')),\n );\n return payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n};\n\nexport function getAssetEmbedAuthContext({ token, apiKey }: { token?: string | null; apiKey?: string }): string {\n if (!token) return apiKey ? `api-key:${apiKey}` : 'anonymous';\n\n const payload = getJwtPayload(token);\n const userId = payload?.user_id ?? payload?.sub;\n if (typeof userId !== 'string' || !userId) return `opaque-token:${token}`;\n\n return `jwt:${JSON.stringify({\n issuer: payload?.iss ?? null,\n userId,\n activeOrgId: payload?.active_org_id ?? payload?.org_id ?? null,\n workspaceId: payload?.workspace_id ?? null,\n orgMemberships: payload?.org_id_to_org_member_info ?? null,\n })}`;\n}\n\n/**\n * Hook to generate an embed URL for rendering an asset in an iframe.\n * Calls the Athena embed token endpoint through TanStack Query (the host's\n * QueryClient when there is one, else the page's embed client), keyed on the\n * asset, the mode and the identity.\n *\n * Note: The auth token is read via a ref and is intentionally NOT part\n * of the query key. PropelAuth and similar providers refresh the token value\n * periodically (e.g. on window focus), but the underlying session is the\n * same — minting again on every token rotation would force the iframe to\n * reload unnecessarily. The link is minted again only when the identity\n * (user or tenant), the asset or the mode changes.\n */\nexport function useAssetEmbed(\n assetId: string | null,\n options: UseAssetEmbedOptions & {\n backendUrl: string;\n appUrl?: string;\n apiKey?: string;\n token?: string | null;\n /**\n * Live token resolver (the provider's `config.getToken`) that renews the\n * held `token`: read before the embed-token request, and asked for a\n * renewed token (`force: true`) when the backend refuses it. Ignored when\n * no token is held, so the request always authenticates as the identity\n * the embed cache is keyed on.\n */\n getToken?: AthenaStatewireTokenGetter;\n } = {\n backendUrl: '',\n },\n): UseAssetEmbedResult {\n const {\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n expiresInSeconds = 60 * 60 * 24 * 30,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n backendUrl,\n appUrl,\n apiKey,\n token,\n getToken,\n } = options;\n // Keep the latest token in a ref so the query can read it without being\n // keyed on it: PropelAuth and similar providers rotate the token value.\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // Stable across normal JWT rotations, but changes when the user or tenant\n // changes so a signed URL can never be reused across auth boundaries.\n const authContext = getAssetEmbedAuthContext({ token, apiKey });\n const enabled = Boolean(assetId && backendUrl);\n\n const query = useQuery(\n {\n queryKey: [\n 'athena-sdk',\n 'asset-embed',\n assetId,\n readOnly,\n displayMode,\n allowQuickEdit,\n allowComments,\n // The identity, never the credential: an opaque token or a key is part of `authContext`.\n keyPartFingerprint(authContext),\n backendUrl,\n appUrl ?? '',\n ],\n queryFn: async ({ signal }): Promise<{ url: string; expiresAt: number }> => {\n // Derive the Athena API base from backendUrl before calling the embed endpoint.\n const apiBaseUrl = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n const heldToken = tokenRef.current;\n const res = await sendWithCredentialRenewal(\n { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : undefined },\n (headers) =>\n fetch(`${apiBaseUrl}/api/embed/generate-token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(\n buildAssetEmbedRequestBody({\n assetId: assetId ?? '',\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n }),\n ),\n signal,\n }),\n );\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Failed to generate embed token (${res.status}): ${text}`);\n }\n const data: { embed_url: string; expires_at: number } = await res.json();\n return {\n url: resolveAssetEmbedUrl({ embedUrl: data.embed_url, appUrl, backendUrl }),\n expiresAt: data.expires_at,\n };\n },\n enabled,\n // A minted link is good until it expires; a new one would reload the frame, so it is never\n // minted again while the embed shows (no refetch on focus or reconnect) and never retried.\n staleTime: (cached) =>\n cached.state.data ? Math.max(0, cached.state.data.expiresAt * 1000 - Date.now()) : 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n useContext(QueryClientContext) ?? embedQueryClient,\n );\n\n return {\n embedUrl: enabled ? (query.data?.url ?? null) : null,\n isLoading: enabled && query.isLoading,\n error: enabled && query.error ? query.error.message : null,\n };\n}\n"],"names":["QueryClient","deriveAthenaAppUrl","useRef","useQuery","keyPartFingerprint","sendWithCredentialRenewal","useContext","QueryClientContext"],"mappings":"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"}
1
+ {"version":3,"file":"use-asset-embed.cjs","sources":["../../src/assets/use-asset-embed.ts"],"sourcesContent":["import { QueryClient, QueryClientContext, useQuery } from '@tanstack/react-query';\nimport { useContext, useRef } from 'react';\nimport { deriveAthenaAppUrl } from '../lib/athena-urls';\nimport { keyPartFingerprint } from '../lib/use-athena-rest-query';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaAssetCitationReference } from './asset-embed-messages';\n\nexport interface UseAssetEmbedOptions {\n /**\n * Mint a view-only link. A ceiling, not a grant: the link edits only what its creator may edit,\n * and any type that keeps comment threads (a Word document, an Athena document, a spreadsheet, a\n * deck, a user interface, a notebook, a PDF, ...) edits only for a viewer signed in to Athena with\n * their own access to the asset (the provider's `token`, which `AthenaAssetEmbed` hands the\n * frame). Anyone else views such a link read-only. Defaults to `false`.\n */\n readOnly?: boolean;\n expiresInSeconds?: number;\n /** Native Olympus surface rendered by the iframe. Defaults to `full`. */\n displayMode?: 'minimal' | 'full';\n /**\n * Offer Quick Edit (⌘J, an AI rewrite of the selection) in a minimal embed, which runs on the\n * link rather than an Athena sign-in: spreadsheets, Word documents and decks, on an editable\n * link (`readOnly: false`) only, and only to a viewer signed in to Athena with their own access\n * to the asset (the provider's `token`, which `AthenaAssetEmbed` hands the frame). Its runs are\n * billed to the asset's\n * workspace under the link's creator, and each link has its own hourly cap. The full mode's\n * editors offer it to the signed-in person already. Defaults to `false`.\n */\n allowQuickEdit?: boolean;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it, a minimal embed shows them only to a signed-in viewer who has\n * their own access to the asset (the bearer this component hands the iframe, or an Athena\n * session). No embed ever writes a comment. Defaults to `false`.\n */\n allowComments?: boolean;\n}\n\nexport const DEFAULT_ASSET_EMBED_READ_ONLY = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_COMMENTS = false;\nexport const DEFAULT_ASSET_EMBED_DISPLAY_MODE = 'full' as const;\n\nexport interface UseAssetEmbedResult {\n embedUrl: string | null;\n isLoading: boolean;\n error: string | null;\n}\n\nexport function buildAssetEmbedRequestBody({\n assetId,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n}: {\n assetId: string;\n readOnly: boolean;\n expiresInSeconds: number;\n displayMode: 'minimal' | 'full';\n allowQuickEdit?: boolean;\n allowComments?: boolean;\n}) {\n return {\n asset_id: assetId,\n read_only: readOnly,\n expires_in_seconds: expiresInSeconds,\n display_mode: displayMode,\n // Sent only when asked: an agora that predates it is never handed an unknown field.\n ...(allowQuickEdit && { allow_quick_edit: true }),\n ...(allowComments && { allow_comments: true }),\n };\n}\n\n/**\n * The embed links' query client when the host provides none: one for the page, so every embed of\n * one asset, mode and identity shares one minted link until it expires.\n */\nconst embedQueryClient = new QueryClient();\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_PATTERN = /^.+--(\\d+)\\.(?:app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/;\nconst OLYMPUS_WORKSPACE_PORT = '8082';\n\nconst getUrl = (value?: string | null): URL | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value);\n } catch {\n return null;\n }\n};\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst getOrigin = (value?: string | null): string | null => getUrl(value)?.origin ?? null;\n\nconst getAthenaWorkspacePort = (url: URL): string | null => {\n const match = url.hostname.toLowerCase().match(ATHENA_WORKSPACE_HOSTNAME_PATTERN);\n return match?.[1] ?? null;\n};\n\nconst isEmbedCapableAthenaHost = (url: URL): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (ATHENA_APP_HOSTNAMES.has(hostname) || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)) {\n return true;\n }\n\n return getAthenaWorkspacePort(url) === OLYMPUS_WORKSPACE_PORT;\n};\n\nconst shouldPreferDerivedEmbedAppUrl = ({\n appUrl,\n derivedAppUrl,\n currentOrigin,\n}: {\n appUrl: string;\n derivedAppUrl: string | null;\n currentOrigin?: string | null;\n}): boolean => {\n if (!derivedAppUrl) {\n return false;\n }\n\n const resolvedAppUrl = getUrl(appUrl);\n if (!resolvedAppUrl) {\n return true;\n }\n\n const workspacePort = getAthenaWorkspacePort(resolvedAppUrl);\n if (workspacePort && workspacePort !== OLYMPUS_WORKSPACE_PORT) {\n return true;\n }\n\n if (isEmbedCapableAthenaHost(resolvedAppUrl)) {\n return false;\n }\n\n const resolvedCurrentOrigin = getOrigin(currentOrigin ?? getCurrentOrigin());\n return !!resolvedCurrentOrigin && resolvedAppUrl.origin === resolvedCurrentOrigin;\n};\n\nconst resolveEmbedAppUrl = ({\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string | null => {\n const derivedAppUrl = deriveAthenaAppUrl({ backendUrl });\n\n if (!appUrl) {\n return derivedAppUrl;\n }\n\n if (shouldPreferDerivedEmbedAppUrl({ appUrl, derivedAppUrl, currentOrigin })) {\n return derivedAppUrl;\n }\n\n return appUrl;\n};\n\nexport function resolveAssetEmbedUrl({\n embedUrl,\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n embedUrl: string;\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string {\n try {\n const resolvedEmbedUrl = new URL(embedUrl);\n if (!resolvedEmbedUrl.pathname.startsWith('/embed/')) {\n return embedUrl;\n }\n\n const embedAppUrl = resolveEmbedAppUrl({\n appUrl,\n backendUrl,\n currentOrigin,\n });\n if (!embedAppUrl) {\n return embedUrl;\n }\n\n const resolvedAppUrl = new URL(embedAppUrl);\n resolvedEmbedUrl.protocol = resolvedAppUrl.protocol;\n resolvedEmbedUrl.host = resolvedAppUrl.host;\n return resolvedEmbedUrl.toString();\n } catch {\n return embedUrl;\n }\n}\n\nfunction setFiniteNumberParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'number' && Number.isFinite(value)) {\n params.set(key, String(value));\n }\n}\n\nfunction setNonEmptyStringParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'string' && value.length > 0) {\n params.set(key, value);\n }\n}\n\n/** The SDK's own embed parameter: it marks the bridge, never a place, so no anchor writes it. */\nconst SDK_BRIDGE_PARAM = 'athena_sdk_bridge';\n\n/**\n * The pairs of an anchor given as its raw query — the string an Athena link, citation or search\n * result carries (`anchor=slide&slide_index=3&slide_id=…`, `anchor=time&time=0:42`), alone or\n * after the `?` of an asset ref (`asset_x?anchor=…`) — read as URL parameters, or `null` when the\n * string holds none, or a parameter with no name. The SDK never interprets the anchor: the embed\n * page parses it with the references grammar, so every kind of place travels as written.\n */\nfunction rawAnchorQueryPairs(query: string): Array<[string, string]> | null {\n const queryIndex = query.indexOf('?');\n const search = queryIndex === -1 ? query : query.slice(queryIndex + 1);\n const pairs = [...new URLSearchParams(search)].filter(([key]) => key !== SDK_BRIDGE_PARAM);\n if (pairs.length === 0 || pairs.some(([key]) => key.length === 0)) return null;\n return pairs;\n}\n\nfunction appendRawAnchorQuery({ params, query }: { params: URLSearchParams; query: string }): void {\n const pairs = rawAnchorQueryPairs(query);\n if (!pairs) return;\n // Verbatim: a key the URL already has is replaced, and a repeated key keeps every value.\n for (const [key] of pairs) params.delete(key);\n for (const [key, value] of pairs) params.append(key, value);\n}\n\nfunction appendTypedAnchorParams({\n params,\n anchor,\n}: {\n params: URLSearchParams;\n anchor: object & { type: string };\n}): void {\n switch (anchor.type) {\n case 'page':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n break;\n case 'page_range':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'start_page',\n value: 'startPage' in anchor ? anchor.startPage : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'end_page',\n value: 'endPage' in anchor ? anchor.endPage : undefined,\n });\n break;\n case 'page_rect':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'x',\n value: 'x' in anchor ? anchor.x : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'y',\n value: 'y' in anchor ? anchor.y : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'width',\n value: 'width' in anchor ? anchor.width : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'height',\n value: 'height' in anchor ? anchor.height : undefined,\n });\n if (\n 'unit' in anchor &&\n (anchor.unit === 'percent' || anchor.unit === 'point')\n ) {\n params.set('unit', anchor.unit);\n }\n break;\n case 'page_text':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n if ('text' in anchor && typeof anchor.text === 'string') {\n params.set('text', anchor.text);\n }\n // Optional context that disambiguates a repeated phrase, and the hash of\n // the cited unit's normalized text; the embed parses both with the shared\n // `@athenaintel/references` parser.\n setNonEmptyStringParam({ params, key: 'prefix', value: 'prefix' in anchor ? anchor.prefix : undefined });\n setNonEmptyStringParam({ params, key: 'suffix', value: 'suffix' in anchor ? anchor.suffix : undefined });\n setNonEmptyStringParam({ params, key: 'hash', value: 'hash' in anchor ? anchor.hash : undefined });\n break;\n // Sheet anchors ride the same query contract the embed page parses with\n // the shared `@athenaintel/references` parser.\n case 'sheet_cell':\n case 'sheet_range':\n case 'sheet_tab':\n case 'sheet_table':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'sheet_id',\n value: 'sheetId' in anchor ? anchor.sheetId : undefined,\n });\n if ('cell' in anchor && typeof anchor.cell === 'string') {\n params.set('cell', anchor.cell);\n }\n if ('range' in anchor && typeof anchor.range === 'string') {\n params.set('range', anchor.range);\n }\n if ('tableName' in anchor && typeof anchor.tableName === 'string') {\n params.set('table_name', anchor.tableName);\n }\n if ('fallbackRange' in anchor && typeof anchor.fallbackRange === 'string') {\n params.set('fallback_range', anchor.fallbackRange);\n }\n break;\n }\n}\n\n/**\n * Writes a citation's place into an embed URL: its anchor and its excerpt (the passage the embed\n * finds by its words — alone, when the citation names no anchor). The anchor is either its raw\n * query string, forwarded as written whatever kind of place it names, or one of the typed page and\n * sheet anchors. Athena on the web's Spaces links read the same grammar, so a host that opens a\n * cited place there writes it the same way.\n */\nexport function appendCitationReferenceParams({\n params,\n reference,\n}: {\n params: URLSearchParams;\n reference?: AthenaAssetCitationReference;\n}): void {\n const anchor = reference?.anchor;\n if (typeof anchor === 'string') {\n appendRawAnchorQuery({ params, query: anchor });\n } else if (anchor && typeof anchor === 'object' && 'type' in anchor && typeof anchor.type === 'string') {\n appendTypedAnchorParams({ params, anchor: { ...anchor, type: anchor.type } });\n }\n\n const excerpt = reference?.meta?.excerpt;\n if (typeof excerpt === 'string' && excerpt) {\n params.set('excerpt', excerpt);\n }\n}\n\nexport function buildSdkAssetEmbedUrl(embedUrl: string, reference?: AthenaAssetCitationReference): string {\n try {\n const url = new URL(embedUrl);\n url.searchParams.set('athena_sdk_bridge', '1');\n appendCitationReferenceParams({ params: url.searchParams, reference });\n return url.toString();\n } catch {\n return embedUrl;\n }\n}\n\nconst getJwtPayload = (token: string): Record<string, unknown> | null => {\n try {\n const payloadSegment = token.split('.')[1];\n if (!payloadSegment || typeof atob !== 'function') return null;\n const normalized = payloadSegment.replace(/-/g, '+').replace(/_/g, '/');\n const payload: unknown = JSON.parse(\n atob(normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=')),\n );\n return payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n};\n\nexport function getAssetEmbedAuthContext({ token, apiKey }: { token?: string | null; apiKey?: string }): string {\n if (!token) return apiKey ? `api-key:${apiKey}` : 'anonymous';\n\n const payload = getJwtPayload(token);\n const userId = payload?.user_id ?? payload?.sub;\n if (typeof userId !== 'string' || !userId) return `opaque-token:${token}`;\n\n return `jwt:${JSON.stringify({\n issuer: payload?.iss ?? null,\n userId,\n activeOrgId: payload?.active_org_id ?? payload?.org_id ?? null,\n workspaceId: payload?.workspace_id ?? null,\n orgMemberships: payload?.org_id_to_org_member_info ?? null,\n })}`;\n}\n\n/**\n * Hook to generate an embed URL for rendering an asset in an iframe.\n * Calls the Athena embed token endpoint through TanStack Query (the host's\n * QueryClient when there is one, else the page's embed client), keyed on the\n * asset, the mode and the identity.\n *\n * Note: The auth token is read via a ref and is intentionally NOT part\n * of the query key. PropelAuth and similar providers refresh the token value\n * periodically (e.g. on window focus), but the underlying session is the\n * same — minting again on every token rotation would force the iframe to\n * reload unnecessarily. The link is minted again only when the identity\n * (user or tenant), the asset or the mode changes.\n */\nexport function useAssetEmbed(\n assetId: string | null,\n options: UseAssetEmbedOptions & {\n backendUrl: string;\n appUrl?: string;\n apiKey?: string;\n token?: string | null;\n /**\n * Live token resolver (the provider's `config.getToken`) that renews the\n * held `token`: read before the embed-token request, and asked for a\n * renewed token (`force: true`) when the backend refuses it. Ignored when\n * no token is held, so the request always authenticates as the identity\n * the embed cache is keyed on.\n */\n getToken?: AthenaStatewireTokenGetter;\n } = {\n backendUrl: '',\n },\n): UseAssetEmbedResult {\n const {\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n expiresInSeconds = 60 * 60 * 24 * 30,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n backendUrl,\n appUrl,\n apiKey,\n token,\n getToken,\n } = options;\n // Keep the latest token in a ref so the query can read it without being\n // keyed on it: PropelAuth and similar providers rotate the token value.\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // Stable across normal JWT rotations, but changes when the user or tenant\n // changes so a signed URL can never be reused across auth boundaries.\n const authContext = getAssetEmbedAuthContext({ token, apiKey });\n const enabled = Boolean(assetId && backendUrl);\n\n const query = useQuery(\n {\n queryKey: [\n 'athena-sdk',\n 'asset-embed',\n assetId,\n readOnly,\n displayMode,\n allowQuickEdit,\n allowComments,\n // The identity, never the credential: an opaque token or a key is part of `authContext`.\n keyPartFingerprint(authContext),\n backendUrl,\n appUrl ?? '',\n ],\n queryFn: async ({ signal }): Promise<{ url: string; expiresAt: number }> => {\n // Derive the Athena API base from backendUrl before calling the embed endpoint.\n const apiBaseUrl = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n const heldToken = tokenRef.current;\n const res = await sendWithCredentialRenewal(\n { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : undefined },\n (headers) =>\n fetch(`${apiBaseUrl}/api/embed/generate-token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(\n buildAssetEmbedRequestBody({\n assetId: assetId ?? '',\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n }),\n ),\n signal,\n }),\n );\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Failed to generate embed token (${res.status}): ${text}`);\n }\n const data: { embed_url: string; expires_at: number } = await res.json();\n return {\n url: resolveAssetEmbedUrl({ embedUrl: data.embed_url, appUrl, backendUrl }),\n expiresAt: data.expires_at,\n };\n },\n enabled,\n // A minted link is good until it expires; a new one would reload the frame, so it is never\n // minted again while the embed shows (no refetch on focus or reconnect) and never retried.\n staleTime: (cached) =>\n cached.state.data ? Math.max(0, cached.state.data.expiresAt * 1000 - Date.now()) : 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n useContext(QueryClientContext) ?? embedQueryClient,\n );\n\n return {\n embedUrl: enabled ? (query.data?.url ?? null) : null,\n isLoading: enabled && query.isLoading,\n error: enabled && query.error ? query.error.message : null,\n };\n}\n"],"names":["QueryClient","deriveAthenaAppUrl","useRef","useQuery","keyPartFingerprint","sendWithCredentialRenewal","useContext","QueryClientContext"],"mappings":"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"}
@@ -3,9 +3,10 @@ import type { AthenaAssetCitationReference } from './asset-embed-messages';
3
3
  export interface UseAssetEmbedOptions {
4
4
  /**
5
5
  * Mint a view-only link. A ceiling, not a grant: the link edits only what its creator may edit,
6
- * and a Word document, an Athena document, a spreadsheet, a user interface or a deck edits only
7
- * for a viewer who is signed in to Athena (the provider's `token`, which `AthenaAssetEmbed`
8
- * hands the frame). Anyone else views such a link read-only. Defaults to `false`.
6
+ * and any type that keeps comment threads (a Word document, an Athena document, a spreadsheet, a
7
+ * deck, a user interface, a notebook, a PDF, ...) edits only for a viewer signed in to Athena with
8
+ * their own access to the asset (the provider's `token`, which `AthenaAssetEmbed` hands the
9
+ * frame). Anyone else views such a link read-only. Defaults to `false`.
9
10
  */
10
11
  readOnly?: boolean;
11
12
  expiresInSeconds?: number;
@@ -14,8 +15,9 @@ export interface UseAssetEmbedOptions {
14
15
  /**
15
16
  * Offer Quick Edit (⌘J, an AI rewrite of the selection) in a minimal embed, which runs on the
16
17
  * link rather than an Athena sign-in: spreadsheets, Word documents and decks, on an editable
17
- * link (`readOnly: false`) only, and only to a viewer who is signed in to Athena (the provider's
18
- * `token`, which `AthenaAssetEmbed` hands the frame). Its runs are billed to the asset's
18
+ * link (`readOnly: false`) only, and only to a viewer signed in to Athena with their own access
19
+ * to the asset (the provider's `token`, which `AthenaAssetEmbed` hands the frame). Its runs are
20
+ * billed to the asset's
19
21
  * workspace under the link's creator, and each link has its own hourly cap. The full mode's
20
22
  * editors offer it to the signed-in person already. Defaults to `false`.
21
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"use-asset-embed.js","sources":["../../src/assets/use-asset-embed.ts"],"sourcesContent":["import { QueryClient, QueryClientContext, useQuery } from '@tanstack/react-query';\nimport { useContext, useRef } from 'react';\nimport { deriveAthenaAppUrl } from '../lib/athena-urls';\nimport { keyPartFingerprint } from '../lib/use-athena-rest-query';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaAssetCitationReference } from './asset-embed-messages';\n\nexport interface UseAssetEmbedOptions {\n /**\n * Mint a view-only link. A ceiling, not a grant: the link edits only what its creator may edit,\n * and a Word document, an Athena document, a spreadsheet, a user interface or a deck edits only\n * for a viewer who is signed in to Athena (the provider's `token`, which `AthenaAssetEmbed`\n * hands the frame). Anyone else views such a link read-only. Defaults to `false`.\n */\n readOnly?: boolean;\n expiresInSeconds?: number;\n /** Native Olympus surface rendered by the iframe. Defaults to `full`. */\n displayMode?: 'minimal' | 'full';\n /**\n * Offer Quick Edit (⌘J, an AI rewrite of the selection) in a minimal embed, which runs on the\n * link rather than an Athena sign-in: spreadsheets, Word documents and decks, on an editable\n * link (`readOnly: false`) only, and only to a viewer who is signed in to Athena (the provider's\n * `token`, which `AthenaAssetEmbed` hands the frame). Its runs are billed to the asset's\n * workspace under the link's creator, and each link has its own hourly cap. The full mode's\n * editors offer it to the signed-in person already. Defaults to `false`.\n */\n allowQuickEdit?: boolean;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it, a minimal embed shows them only to a signed-in viewer who has\n * their own access to the asset (the bearer this component hands the iframe, or an Athena\n * session). No embed ever writes a comment. Defaults to `false`.\n */\n allowComments?: boolean;\n}\n\nexport const DEFAULT_ASSET_EMBED_READ_ONLY = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_COMMENTS = false;\nexport const DEFAULT_ASSET_EMBED_DISPLAY_MODE = 'full' as const;\n\nexport interface UseAssetEmbedResult {\n embedUrl: string | null;\n isLoading: boolean;\n error: string | null;\n}\n\nexport function buildAssetEmbedRequestBody({\n assetId,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n}: {\n assetId: string;\n readOnly: boolean;\n expiresInSeconds: number;\n displayMode: 'minimal' | 'full';\n allowQuickEdit?: boolean;\n allowComments?: boolean;\n}) {\n return {\n asset_id: assetId,\n read_only: readOnly,\n expires_in_seconds: expiresInSeconds,\n display_mode: displayMode,\n // Sent only when asked: an agora that predates it is never handed an unknown field.\n ...(allowQuickEdit && { allow_quick_edit: true }),\n ...(allowComments && { allow_comments: true }),\n };\n}\n\n/**\n * The embed links' query client when the host provides none: one for the page, so every embed of\n * one asset, mode and identity shares one minted link until it expires.\n */\nconst embedQueryClient = new QueryClient();\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_PATTERN = /^.+--(\\d+)\\.(?:app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/;\nconst OLYMPUS_WORKSPACE_PORT = '8082';\n\nconst getUrl = (value?: string | null): URL | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value);\n } catch {\n return null;\n }\n};\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst getOrigin = (value?: string | null): string | null => getUrl(value)?.origin ?? null;\n\nconst getAthenaWorkspacePort = (url: URL): string | null => {\n const match = url.hostname.toLowerCase().match(ATHENA_WORKSPACE_HOSTNAME_PATTERN);\n return match?.[1] ?? null;\n};\n\nconst isEmbedCapableAthenaHost = (url: URL): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (ATHENA_APP_HOSTNAMES.has(hostname) || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)) {\n return true;\n }\n\n return getAthenaWorkspacePort(url) === OLYMPUS_WORKSPACE_PORT;\n};\n\nconst shouldPreferDerivedEmbedAppUrl = ({\n appUrl,\n derivedAppUrl,\n currentOrigin,\n}: {\n appUrl: string;\n derivedAppUrl: string | null;\n currentOrigin?: string | null;\n}): boolean => {\n if (!derivedAppUrl) {\n return false;\n }\n\n const resolvedAppUrl = getUrl(appUrl);\n if (!resolvedAppUrl) {\n return true;\n }\n\n const workspacePort = getAthenaWorkspacePort(resolvedAppUrl);\n if (workspacePort && workspacePort !== OLYMPUS_WORKSPACE_PORT) {\n return true;\n }\n\n if (isEmbedCapableAthenaHost(resolvedAppUrl)) {\n return false;\n }\n\n const resolvedCurrentOrigin = getOrigin(currentOrigin ?? getCurrentOrigin());\n return !!resolvedCurrentOrigin && resolvedAppUrl.origin === resolvedCurrentOrigin;\n};\n\nconst resolveEmbedAppUrl = ({\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string | null => {\n const derivedAppUrl = deriveAthenaAppUrl({ backendUrl });\n\n if (!appUrl) {\n return derivedAppUrl;\n }\n\n if (shouldPreferDerivedEmbedAppUrl({ appUrl, derivedAppUrl, currentOrigin })) {\n return derivedAppUrl;\n }\n\n return appUrl;\n};\n\nexport function resolveAssetEmbedUrl({\n embedUrl,\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n embedUrl: string;\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string {\n try {\n const resolvedEmbedUrl = new URL(embedUrl);\n if (!resolvedEmbedUrl.pathname.startsWith('/embed/')) {\n return embedUrl;\n }\n\n const embedAppUrl = resolveEmbedAppUrl({\n appUrl,\n backendUrl,\n currentOrigin,\n });\n if (!embedAppUrl) {\n return embedUrl;\n }\n\n const resolvedAppUrl = new URL(embedAppUrl);\n resolvedEmbedUrl.protocol = resolvedAppUrl.protocol;\n resolvedEmbedUrl.host = resolvedAppUrl.host;\n return resolvedEmbedUrl.toString();\n } catch {\n return embedUrl;\n }\n}\n\nfunction setFiniteNumberParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'number' && Number.isFinite(value)) {\n params.set(key, String(value));\n }\n}\n\nfunction setNonEmptyStringParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'string' && value.length > 0) {\n params.set(key, value);\n }\n}\n\n/** The SDK's own embed parameter: it marks the bridge, never a place, so no anchor writes it. */\nconst SDK_BRIDGE_PARAM = 'athena_sdk_bridge';\n\n/**\n * The pairs of an anchor given as its raw query — the string an Athena link, citation or search\n * result carries (`anchor=slide&slide_index=3&slide_id=…`, `anchor=time&time=0:42`), alone or\n * after the `?` of an asset ref (`asset_x?anchor=…`) — read as URL parameters, or `null` when the\n * string holds none, or a parameter with no name. The SDK never interprets the anchor: the embed\n * page parses it with the references grammar, so every kind of place travels as written.\n */\nfunction rawAnchorQueryPairs(query: string): Array<[string, string]> | null {\n const queryIndex = query.indexOf('?');\n const search = queryIndex === -1 ? query : query.slice(queryIndex + 1);\n const pairs = [...new URLSearchParams(search)].filter(([key]) => key !== SDK_BRIDGE_PARAM);\n if (pairs.length === 0 || pairs.some(([key]) => key.length === 0)) return null;\n return pairs;\n}\n\nfunction appendRawAnchorQuery({ params, query }: { params: URLSearchParams; query: string }): void {\n const pairs = rawAnchorQueryPairs(query);\n if (!pairs) return;\n // Verbatim: a key the URL already has is replaced, and a repeated key keeps every value.\n for (const [key] of pairs) params.delete(key);\n for (const [key, value] of pairs) params.append(key, value);\n}\n\nfunction appendTypedAnchorParams({\n params,\n anchor,\n}: {\n params: URLSearchParams;\n anchor: object & { type: string };\n}): void {\n switch (anchor.type) {\n case 'page':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n break;\n case 'page_range':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'start_page',\n value: 'startPage' in anchor ? anchor.startPage : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'end_page',\n value: 'endPage' in anchor ? anchor.endPage : undefined,\n });\n break;\n case 'page_rect':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'x',\n value: 'x' in anchor ? anchor.x : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'y',\n value: 'y' in anchor ? anchor.y : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'width',\n value: 'width' in anchor ? anchor.width : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'height',\n value: 'height' in anchor ? anchor.height : undefined,\n });\n if (\n 'unit' in anchor &&\n (anchor.unit === 'percent' || anchor.unit === 'point')\n ) {\n params.set('unit', anchor.unit);\n }\n break;\n case 'page_text':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n if ('text' in anchor && typeof anchor.text === 'string') {\n params.set('text', anchor.text);\n }\n // Optional context that disambiguates a repeated phrase, and the hash of\n // the cited unit's normalized text; the embed parses both with the shared\n // `@athenaintel/references` parser.\n setNonEmptyStringParam({ params, key: 'prefix', value: 'prefix' in anchor ? anchor.prefix : undefined });\n setNonEmptyStringParam({ params, key: 'suffix', value: 'suffix' in anchor ? anchor.suffix : undefined });\n setNonEmptyStringParam({ params, key: 'hash', value: 'hash' in anchor ? anchor.hash : undefined });\n break;\n // Sheet anchors ride the same query contract the embed page parses with\n // the shared `@athenaintel/references` parser.\n case 'sheet_cell':\n case 'sheet_range':\n case 'sheet_tab':\n case 'sheet_table':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'sheet_id',\n value: 'sheetId' in anchor ? anchor.sheetId : undefined,\n });\n if ('cell' in anchor && typeof anchor.cell === 'string') {\n params.set('cell', anchor.cell);\n }\n if ('range' in anchor && typeof anchor.range === 'string') {\n params.set('range', anchor.range);\n }\n if ('tableName' in anchor && typeof anchor.tableName === 'string') {\n params.set('table_name', anchor.tableName);\n }\n if ('fallbackRange' in anchor && typeof anchor.fallbackRange === 'string') {\n params.set('fallback_range', anchor.fallbackRange);\n }\n break;\n }\n}\n\n/**\n * Writes a citation's place into an embed URL: its anchor and its excerpt (the passage the embed\n * finds by its words — alone, when the citation names no anchor). The anchor is either its raw\n * query string, forwarded as written whatever kind of place it names, or one of the typed page and\n * sheet anchors. Athena on the web's Spaces links read the same grammar, so a host that opens a\n * cited place there writes it the same way.\n */\nexport function appendCitationReferenceParams({\n params,\n reference,\n}: {\n params: URLSearchParams;\n reference?: AthenaAssetCitationReference;\n}): void {\n const anchor = reference?.anchor;\n if (typeof anchor === 'string') {\n appendRawAnchorQuery({ params, query: anchor });\n } else if (anchor && typeof anchor === 'object' && 'type' in anchor && typeof anchor.type === 'string') {\n appendTypedAnchorParams({ params, anchor: { ...anchor, type: anchor.type } });\n }\n\n const excerpt = reference?.meta?.excerpt;\n if (typeof excerpt === 'string' && excerpt) {\n params.set('excerpt', excerpt);\n }\n}\n\nexport function buildSdkAssetEmbedUrl(embedUrl: string, reference?: AthenaAssetCitationReference): string {\n try {\n const url = new URL(embedUrl);\n url.searchParams.set('athena_sdk_bridge', '1');\n appendCitationReferenceParams({ params: url.searchParams, reference });\n return url.toString();\n } catch {\n return embedUrl;\n }\n}\n\nconst getJwtPayload = (token: string): Record<string, unknown> | null => {\n try {\n const payloadSegment = token.split('.')[1];\n if (!payloadSegment || typeof atob !== 'function') return null;\n const normalized = payloadSegment.replace(/-/g, '+').replace(/_/g, '/');\n const payload: unknown = JSON.parse(\n atob(normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=')),\n );\n return payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n};\n\nexport function getAssetEmbedAuthContext({ token, apiKey }: { token?: string | null; apiKey?: string }): string {\n if (!token) return apiKey ? `api-key:${apiKey}` : 'anonymous';\n\n const payload = getJwtPayload(token);\n const userId = payload?.user_id ?? payload?.sub;\n if (typeof userId !== 'string' || !userId) return `opaque-token:${token}`;\n\n return `jwt:${JSON.stringify({\n issuer: payload?.iss ?? null,\n userId,\n activeOrgId: payload?.active_org_id ?? payload?.org_id ?? null,\n workspaceId: payload?.workspace_id ?? null,\n orgMemberships: payload?.org_id_to_org_member_info ?? null,\n })}`;\n}\n\n/**\n * Hook to generate an embed URL for rendering an asset in an iframe.\n * Calls the Athena embed token endpoint through TanStack Query (the host's\n * QueryClient when there is one, else the page's embed client), keyed on the\n * asset, the mode and the identity.\n *\n * Note: The auth token is read via a ref and is intentionally NOT part\n * of the query key. PropelAuth and similar providers refresh the token value\n * periodically (e.g. on window focus), but the underlying session is the\n * same — minting again on every token rotation would force the iframe to\n * reload unnecessarily. The link is minted again only when the identity\n * (user or tenant), the asset or the mode changes.\n */\nexport function useAssetEmbed(\n assetId: string | null,\n options: UseAssetEmbedOptions & {\n backendUrl: string;\n appUrl?: string;\n apiKey?: string;\n token?: string | null;\n /**\n * Live token resolver (the provider's `config.getToken`) that renews the\n * held `token`: read before the embed-token request, and asked for a\n * renewed token (`force: true`) when the backend refuses it. Ignored when\n * no token is held, so the request always authenticates as the identity\n * the embed cache is keyed on.\n */\n getToken?: AthenaStatewireTokenGetter;\n } = {\n backendUrl: '',\n },\n): UseAssetEmbedResult {\n const {\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n expiresInSeconds = 60 * 60 * 24 * 30,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n backendUrl,\n appUrl,\n apiKey,\n token,\n getToken,\n } = options;\n // Keep the latest token in a ref so the query can read it without being\n // keyed on it: PropelAuth and similar providers rotate the token value.\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // Stable across normal JWT rotations, but changes when the user or tenant\n // changes so a signed URL can never be reused across auth boundaries.\n const authContext = getAssetEmbedAuthContext({ token, apiKey });\n const enabled = Boolean(assetId && backendUrl);\n\n const query = useQuery(\n {\n queryKey: [\n 'athena-sdk',\n 'asset-embed',\n assetId,\n readOnly,\n displayMode,\n allowQuickEdit,\n allowComments,\n // The identity, never the credential: an opaque token or a key is part of `authContext`.\n keyPartFingerprint(authContext),\n backendUrl,\n appUrl ?? '',\n ],\n queryFn: async ({ signal }): Promise<{ url: string; expiresAt: number }> => {\n // Derive the Athena API base from backendUrl before calling the embed endpoint.\n const apiBaseUrl = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n const heldToken = tokenRef.current;\n const res = await sendWithCredentialRenewal(\n { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : undefined },\n (headers) =>\n fetch(`${apiBaseUrl}/api/embed/generate-token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(\n buildAssetEmbedRequestBody({\n assetId: assetId ?? '',\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n }),\n ),\n signal,\n }),\n );\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Failed to generate embed token (${res.status}): ${text}`);\n }\n const data: { embed_url: string; expires_at: number } = await res.json();\n return {\n url: resolveAssetEmbedUrl({ embedUrl: data.embed_url, appUrl, backendUrl }),\n expiresAt: data.expires_at,\n };\n },\n enabled,\n // A minted link is good until it expires; a new one would reload the frame, so it is never\n // minted again while the embed shows (no refetch on focus or reconnect) and never retried.\n staleTime: (cached) =>\n cached.state.data ? Math.max(0, cached.state.data.expiresAt * 1000 - Date.now()) : 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n useContext(QueryClientContext) ?? embedQueryClient,\n );\n\n return {\n embedUrl: enabled ? (query.data?.url ?? null) : null,\n isLoading: enabled && query.isLoading,\n error: enabled && query.error ? query.error.message : null,\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-asset-embed.js","sources":["../../src/assets/use-asset-embed.ts"],"sourcesContent":["import { QueryClient, QueryClientContext, useQuery } from '@tanstack/react-query';\nimport { useContext, useRef } from 'react';\nimport { deriveAthenaAppUrl } from '../lib/athena-urls';\nimport { keyPartFingerprint } from '../lib/use-athena-rest-query';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaAssetCitationReference } from './asset-embed-messages';\n\nexport interface UseAssetEmbedOptions {\n /**\n * Mint a view-only link. A ceiling, not a grant: the link edits only what its creator may edit,\n * and any type that keeps comment threads (a Word document, an Athena document, a spreadsheet, a\n * deck, a user interface, a notebook, a PDF, ...) edits only for a viewer signed in to Athena with\n * their own access to the asset (the provider's `token`, which `AthenaAssetEmbed` hands the\n * frame). Anyone else views such a link read-only. Defaults to `false`.\n */\n readOnly?: boolean;\n expiresInSeconds?: number;\n /** Native Olympus surface rendered by the iframe. Defaults to `full`. */\n displayMode?: 'minimal' | 'full';\n /**\n * Offer Quick Edit (⌘J, an AI rewrite of the selection) in a minimal embed, which runs on the\n * link rather than an Athena sign-in: spreadsheets, Word documents and decks, on an editable\n * link (`readOnly: false`) only, and only to a viewer signed in to Athena with their own access\n * to the asset (the provider's `token`, which `AthenaAssetEmbed` hands the frame). Its runs are\n * billed to the asset's\n * workspace under the link's creator, and each link has its own hourly cap. The full mode's\n * editors offer it to the signed-in person already. Defaults to `false`.\n */\n allowQuickEdit?: boolean;\n /**\n * Show the asset's comment threads, read-only, to everyone holding the link, a viewer who is not\n * signed in included. Without it, a minimal embed shows them only to a signed-in viewer who has\n * their own access to the asset (the bearer this component hands the iframe, or an Athena\n * session). No embed ever writes a comment. Defaults to `false`.\n */\n allowComments?: boolean;\n}\n\nexport const DEFAULT_ASSET_EMBED_READ_ONLY = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT = false;\nexport const DEFAULT_ASSET_EMBED_ALLOW_COMMENTS = false;\nexport const DEFAULT_ASSET_EMBED_DISPLAY_MODE = 'full' as const;\n\nexport interface UseAssetEmbedResult {\n embedUrl: string | null;\n isLoading: boolean;\n error: string | null;\n}\n\nexport function buildAssetEmbedRequestBody({\n assetId,\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n}: {\n assetId: string;\n readOnly: boolean;\n expiresInSeconds: number;\n displayMode: 'minimal' | 'full';\n allowQuickEdit?: boolean;\n allowComments?: boolean;\n}) {\n return {\n asset_id: assetId,\n read_only: readOnly,\n expires_in_seconds: expiresInSeconds,\n display_mode: displayMode,\n // Sent only when asked: an agora that predates it is never handed an unknown field.\n ...(allowQuickEdit && { allow_quick_edit: true }),\n ...(allowComments && { allow_comments: true }),\n };\n}\n\n/**\n * The embed links' query client when the host provides none: one for the page, so every embed of\n * one asset, mode and identity shares one minted link until it expires.\n */\nconst embedQueryClient = new QueryClient();\n\nconst ATHENA_APP_HOSTNAMES = new Set(['app.athenaintel.com', 'staging-app.athenaintel.com']);\nconst ATHENA_PREVIEW_HOSTNAME_SUFFIX = '.previews.athenaintel.com';\nconst ATHENA_WORKSPACE_HOSTNAME_PATTERN = /^.+--(\\d+)\\.(?:app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/;\nconst OLYMPUS_WORKSPACE_PORT = '8082';\n\nconst getUrl = (value?: string | null): URL | null => {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value);\n } catch {\n return null;\n }\n};\n\nconst getCurrentOrigin = (): string | null => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return window.location.origin;\n};\n\nconst getOrigin = (value?: string | null): string | null => getUrl(value)?.origin ?? null;\n\nconst getAthenaWorkspacePort = (url: URL): string | null => {\n const match = url.hostname.toLowerCase().match(ATHENA_WORKSPACE_HOSTNAME_PATTERN);\n return match?.[1] ?? null;\n};\n\nconst isEmbedCapableAthenaHost = (url: URL): boolean => {\n const hostname = url.hostname.toLowerCase();\n\n if (ATHENA_APP_HOSTNAMES.has(hostname) || hostname.endsWith(ATHENA_PREVIEW_HOSTNAME_SUFFIX)) {\n return true;\n }\n\n return getAthenaWorkspacePort(url) === OLYMPUS_WORKSPACE_PORT;\n};\n\nconst shouldPreferDerivedEmbedAppUrl = ({\n appUrl,\n derivedAppUrl,\n currentOrigin,\n}: {\n appUrl: string;\n derivedAppUrl: string | null;\n currentOrigin?: string | null;\n}): boolean => {\n if (!derivedAppUrl) {\n return false;\n }\n\n const resolvedAppUrl = getUrl(appUrl);\n if (!resolvedAppUrl) {\n return true;\n }\n\n const workspacePort = getAthenaWorkspacePort(resolvedAppUrl);\n if (workspacePort && workspacePort !== OLYMPUS_WORKSPACE_PORT) {\n return true;\n }\n\n if (isEmbedCapableAthenaHost(resolvedAppUrl)) {\n return false;\n }\n\n const resolvedCurrentOrigin = getOrigin(currentOrigin ?? getCurrentOrigin());\n return !!resolvedCurrentOrigin && resolvedAppUrl.origin === resolvedCurrentOrigin;\n};\n\nconst resolveEmbedAppUrl = ({\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string | null => {\n const derivedAppUrl = deriveAthenaAppUrl({ backendUrl });\n\n if (!appUrl) {\n return derivedAppUrl;\n }\n\n if (shouldPreferDerivedEmbedAppUrl({ appUrl, derivedAppUrl, currentOrigin })) {\n return derivedAppUrl;\n }\n\n return appUrl;\n};\n\nexport function resolveAssetEmbedUrl({\n embedUrl,\n appUrl,\n backendUrl,\n currentOrigin,\n}: {\n embedUrl: string;\n appUrl?: string;\n backendUrl?: string | null;\n currentOrigin?: string | null;\n}): string {\n try {\n const resolvedEmbedUrl = new URL(embedUrl);\n if (!resolvedEmbedUrl.pathname.startsWith('/embed/')) {\n return embedUrl;\n }\n\n const embedAppUrl = resolveEmbedAppUrl({\n appUrl,\n backendUrl,\n currentOrigin,\n });\n if (!embedAppUrl) {\n return embedUrl;\n }\n\n const resolvedAppUrl = new URL(embedAppUrl);\n resolvedEmbedUrl.protocol = resolvedAppUrl.protocol;\n resolvedEmbedUrl.host = resolvedAppUrl.host;\n return resolvedEmbedUrl.toString();\n } catch {\n return embedUrl;\n }\n}\n\nfunction setFiniteNumberParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'number' && Number.isFinite(value)) {\n params.set(key, String(value));\n }\n}\n\nfunction setNonEmptyStringParam({ params, key, value }: { params: URLSearchParams; key: string; value: unknown }): void {\n if (typeof value === 'string' && value.length > 0) {\n params.set(key, value);\n }\n}\n\n/** The SDK's own embed parameter: it marks the bridge, never a place, so no anchor writes it. */\nconst SDK_BRIDGE_PARAM = 'athena_sdk_bridge';\n\n/**\n * The pairs of an anchor given as its raw query — the string an Athena link, citation or search\n * result carries (`anchor=slide&slide_index=3&slide_id=…`, `anchor=time&time=0:42`), alone or\n * after the `?` of an asset ref (`asset_x?anchor=…`) — read as URL parameters, or `null` when the\n * string holds none, or a parameter with no name. The SDK never interprets the anchor: the embed\n * page parses it with the references grammar, so every kind of place travels as written.\n */\nfunction rawAnchorQueryPairs(query: string): Array<[string, string]> | null {\n const queryIndex = query.indexOf('?');\n const search = queryIndex === -1 ? query : query.slice(queryIndex + 1);\n const pairs = [...new URLSearchParams(search)].filter(([key]) => key !== SDK_BRIDGE_PARAM);\n if (pairs.length === 0 || pairs.some(([key]) => key.length === 0)) return null;\n return pairs;\n}\n\nfunction appendRawAnchorQuery({ params, query }: { params: URLSearchParams; query: string }): void {\n const pairs = rawAnchorQueryPairs(query);\n if (!pairs) return;\n // Verbatim: a key the URL already has is replaced, and a repeated key keeps every value.\n for (const [key] of pairs) params.delete(key);\n for (const [key, value] of pairs) params.append(key, value);\n}\n\nfunction appendTypedAnchorParams({\n params,\n anchor,\n}: {\n params: URLSearchParams;\n anchor: object & { type: string };\n}): void {\n switch (anchor.type) {\n case 'page':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n break;\n case 'page_range':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'start_page',\n value: 'startPage' in anchor ? anchor.startPage : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'end_page',\n value: 'endPage' in anchor ? anchor.endPage : undefined,\n });\n break;\n case 'page_rect':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'x',\n value: 'x' in anchor ? anchor.x : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'y',\n value: 'y' in anchor ? anchor.y : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'width',\n value: 'width' in anchor ? anchor.width : undefined,\n });\n setFiniteNumberParam({\n params,\n key: 'height',\n value: 'height' in anchor ? anchor.height : undefined,\n });\n if (\n 'unit' in anchor &&\n (anchor.unit === 'percent' || anchor.unit === 'point')\n ) {\n params.set('unit', anchor.unit);\n }\n break;\n case 'page_text':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'page',\n value: 'page' in anchor ? anchor.page : undefined,\n });\n if ('text' in anchor && typeof anchor.text === 'string') {\n params.set('text', anchor.text);\n }\n // Optional context that disambiguates a repeated phrase, and the hash of\n // the cited unit's normalized text; the embed parses both with the shared\n // `@athenaintel/references` parser.\n setNonEmptyStringParam({ params, key: 'prefix', value: 'prefix' in anchor ? anchor.prefix : undefined });\n setNonEmptyStringParam({ params, key: 'suffix', value: 'suffix' in anchor ? anchor.suffix : undefined });\n setNonEmptyStringParam({ params, key: 'hash', value: 'hash' in anchor ? anchor.hash : undefined });\n break;\n // Sheet anchors ride the same query contract the embed page parses with\n // the shared `@athenaintel/references` parser.\n case 'sheet_cell':\n case 'sheet_range':\n case 'sheet_tab':\n case 'sheet_table':\n params.set('anchor', anchor.type);\n setFiniteNumberParam({\n params,\n key: 'sheet_id',\n value: 'sheetId' in anchor ? anchor.sheetId : undefined,\n });\n if ('cell' in anchor && typeof anchor.cell === 'string') {\n params.set('cell', anchor.cell);\n }\n if ('range' in anchor && typeof anchor.range === 'string') {\n params.set('range', anchor.range);\n }\n if ('tableName' in anchor && typeof anchor.tableName === 'string') {\n params.set('table_name', anchor.tableName);\n }\n if ('fallbackRange' in anchor && typeof anchor.fallbackRange === 'string') {\n params.set('fallback_range', anchor.fallbackRange);\n }\n break;\n }\n}\n\n/**\n * Writes a citation's place into an embed URL: its anchor and its excerpt (the passage the embed\n * finds by its words — alone, when the citation names no anchor). The anchor is either its raw\n * query string, forwarded as written whatever kind of place it names, or one of the typed page and\n * sheet anchors. Athena on the web's Spaces links read the same grammar, so a host that opens a\n * cited place there writes it the same way.\n */\nexport function appendCitationReferenceParams({\n params,\n reference,\n}: {\n params: URLSearchParams;\n reference?: AthenaAssetCitationReference;\n}): void {\n const anchor = reference?.anchor;\n if (typeof anchor === 'string') {\n appendRawAnchorQuery({ params, query: anchor });\n } else if (anchor && typeof anchor === 'object' && 'type' in anchor && typeof anchor.type === 'string') {\n appendTypedAnchorParams({ params, anchor: { ...anchor, type: anchor.type } });\n }\n\n const excerpt = reference?.meta?.excerpt;\n if (typeof excerpt === 'string' && excerpt) {\n params.set('excerpt', excerpt);\n }\n}\n\nexport function buildSdkAssetEmbedUrl(embedUrl: string, reference?: AthenaAssetCitationReference): string {\n try {\n const url = new URL(embedUrl);\n url.searchParams.set('athena_sdk_bridge', '1');\n appendCitationReferenceParams({ params: url.searchParams, reference });\n return url.toString();\n } catch {\n return embedUrl;\n }\n}\n\nconst getJwtPayload = (token: string): Record<string, unknown> | null => {\n try {\n const payloadSegment = token.split('.')[1];\n if (!payloadSegment || typeof atob !== 'function') return null;\n const normalized = payloadSegment.replace(/-/g, '+').replace(/_/g, '/');\n const payload: unknown = JSON.parse(\n atob(normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), '=')),\n );\n return payload && typeof payload === 'object' ? (payload as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n};\n\nexport function getAssetEmbedAuthContext({ token, apiKey }: { token?: string | null; apiKey?: string }): string {\n if (!token) return apiKey ? `api-key:${apiKey}` : 'anonymous';\n\n const payload = getJwtPayload(token);\n const userId = payload?.user_id ?? payload?.sub;\n if (typeof userId !== 'string' || !userId) return `opaque-token:${token}`;\n\n return `jwt:${JSON.stringify({\n issuer: payload?.iss ?? null,\n userId,\n activeOrgId: payload?.active_org_id ?? payload?.org_id ?? null,\n workspaceId: payload?.workspace_id ?? null,\n orgMemberships: payload?.org_id_to_org_member_info ?? null,\n })}`;\n}\n\n/**\n * Hook to generate an embed URL for rendering an asset in an iframe.\n * Calls the Athena embed token endpoint through TanStack Query (the host's\n * QueryClient when there is one, else the page's embed client), keyed on the\n * asset, the mode and the identity.\n *\n * Note: The auth token is read via a ref and is intentionally NOT part\n * of the query key. PropelAuth and similar providers refresh the token value\n * periodically (e.g. on window focus), but the underlying session is the\n * same — minting again on every token rotation would force the iframe to\n * reload unnecessarily. The link is minted again only when the identity\n * (user or tenant), the asset or the mode changes.\n */\nexport function useAssetEmbed(\n assetId: string | null,\n options: UseAssetEmbedOptions & {\n backendUrl: string;\n appUrl?: string;\n apiKey?: string;\n token?: string | null;\n /**\n * Live token resolver (the provider's `config.getToken`) that renews the\n * held `token`: read before the embed-token request, and asked for a\n * renewed token (`force: true`) when the backend refuses it. Ignored when\n * no token is held, so the request always authenticates as the identity\n * the embed cache is keyed on.\n */\n getToken?: AthenaStatewireTokenGetter;\n } = {\n backendUrl: '',\n },\n): UseAssetEmbedResult {\n const {\n readOnly = DEFAULT_ASSET_EMBED_READ_ONLY,\n expiresInSeconds = 60 * 60 * 24 * 30,\n displayMode = DEFAULT_ASSET_EMBED_DISPLAY_MODE,\n allowQuickEdit = DEFAULT_ASSET_EMBED_ALLOW_QUICK_EDIT,\n allowComments = DEFAULT_ASSET_EMBED_ALLOW_COMMENTS,\n backendUrl,\n appUrl,\n apiKey,\n token,\n getToken,\n } = options;\n // Keep the latest token in a ref so the query can read it without being\n // keyed on it: PropelAuth and similar providers rotate the token value.\n const tokenRef = useRef(token);\n tokenRef.current = token;\n const getTokenRef = useRef(getToken);\n getTokenRef.current = getToken;\n\n // Stable across normal JWT rotations, but changes when the user or tenant\n // changes so a signed URL can never be reused across auth boundaries.\n const authContext = getAssetEmbedAuthContext({ token, apiKey });\n const enabled = Boolean(assetId && backendUrl);\n\n const query = useQuery(\n {\n queryKey: [\n 'athena-sdk',\n 'asset-embed',\n assetId,\n readOnly,\n displayMode,\n allowQuickEdit,\n allowComments,\n // The identity, never the credential: an opaque token or a key is part of `authContext`.\n keyPartFingerprint(authContext),\n backendUrl,\n appUrl ?? '',\n ],\n queryFn: async ({ signal }): Promise<{ url: string; expiresAt: number }> => {\n // Derive the Athena API base from backendUrl before calling the embed endpoint.\n const apiBaseUrl = backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '');\n const heldToken = tokenRef.current;\n const res = await sendWithCredentialRenewal(\n { apiKey, token: heldToken, getToken: heldToken ? getTokenRef.current : undefined },\n (headers) =>\n fetch(`${apiBaseUrl}/api/embed/generate-token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(\n buildAssetEmbedRequestBody({\n assetId: assetId ?? '',\n readOnly,\n expiresInSeconds,\n displayMode,\n allowQuickEdit,\n allowComments,\n }),\n ),\n signal,\n }),\n );\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`Failed to generate embed token (${res.status}): ${text}`);\n }\n const data: { embed_url: string; expires_at: number } = await res.json();\n return {\n url: resolveAssetEmbedUrl({ embedUrl: data.embed_url, appUrl, backendUrl }),\n expiresAt: data.expires_at,\n };\n },\n enabled,\n // A minted link is good until it expires; a new one would reload the frame, so it is never\n // minted again while the embed shows (no refetch on focus or reconnect) and never retried.\n staleTime: (cached) =>\n cached.state.data ? Math.max(0, cached.state.data.expiresAt * 1000 - Date.now()) : 0,\n refetchOnWindowFocus: false,\n refetchOnReconnect: false,\n retry: false,\n },\n useContext(QueryClientContext) ?? embedQueryClient,\n );\n\n return {\n embedUrl: enabled ? (query.data?.url ?? null) : null,\n isLoading: enabled && query.isLoading,\n error: enabled && query.error ? query.error.message : null,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const reactQuery = require("@tanstack/react-query");
4
+ const useAthenaRestQuery = require("../lib/use-athena-rest-query.cjs");
5
+ const credentialRenewal = require("../runtime/credential-renewal.cjs");
6
+ const api = require("../threads/api.cjs");
7
+ const AthenaCredentialSourceContext = require("./AthenaCredentialSourceContext.cjs");
8
+ const credentialSource = require("./credential-source.cjs");
9
+ const tokenGetterIds = /* @__PURE__ */ new WeakMap();
10
+ let lastTokenGetterId = 0;
11
+ function tokenGetterId(getToken) {
12
+ if (getToken === void 0) return "";
13
+ let id = tokenGetterIds.get(getToken);
14
+ if (id === void 0) {
15
+ lastTokenGetterId += 1;
16
+ id = lastTokenGetterId;
17
+ tokenGetterIds.set(getToken, id);
18
+ }
19
+ return `getter:${id}`;
20
+ }
21
+ function heldTokenIdentity(token) {
22
+ if (!token) return "";
23
+ const subject = credentialSource.jwtSubject(token);
24
+ if (subject === null) return `token:${useAthenaRestQuery.keyPartFingerprint(token)}`;
25
+ const scope = credentialSource.jwtScope(token);
26
+ return scope === null ? `sub:${useAthenaRestQuery.keyPartFingerprint(subject)}` : `sub:${useAthenaRestQuery.keyPartFingerprint(subject)}|scope:${useAthenaRestQuery.keyPartFingerprint(scope)}`;
27
+ }
28
+ function accountIdentityQueryKey(credential, identityEpoch) {
29
+ return [
30
+ "athena-sdk",
31
+ "account",
32
+ credential.backendUrl,
33
+ credential.principal ?? "",
34
+ credential.apiKey ? useAthenaRestQuery.keyPartFingerprint(credential.apiKey) : "",
35
+ tokenGetterId(credential.tokenGetter),
36
+ heldTokenIdentity(credential.heldToken),
37
+ identityEpoch
38
+ ];
39
+ }
40
+ const NO_RETRY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 405, 409]);
41
+ function retryTransient(failureCount, error) {
42
+ const status = Reflect.get(error, "status");
43
+ if (typeof status === "number" && NO_RETRY_STATUSES.has(status)) return false;
44
+ return failureCount < 2;
45
+ }
46
+ function useAccountIdentityQuery({
47
+ credential,
48
+ resolveAuth,
49
+ enabled,
50
+ queryClient
51
+ }) {
52
+ const identityEpoch = AthenaCredentialSourceContext.useAthenaCredentialIdentityEpoch();
53
+ return reactQuery.useQuery(
54
+ {
55
+ queryKey: accountIdentityQueryKey(credential, identityEpoch),
56
+ queryFn: ({ signal }) => credentialRenewal.withCredentialRenewal(
57
+ resolveAuth,
58
+ (auth) => api.fetchAccountIdentity(credential.backendUrl, auth, { signal })
59
+ ),
60
+ enabled,
61
+ retry: retryTransient,
62
+ staleTime: 6e4
63
+ },
64
+ queryClient
65
+ );
66
+ }
67
+ exports.accountIdentityQueryKey = accountIdentityQueryKey;
68
+ exports.useAccountIdentityQuery = useAccountIdentityQuery;
69
+ //# sourceMappingURL=account-identity.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account-identity.cjs","sources":["../../src/auth/account-identity.ts"],"sourcesContent":["import { type QueryClient, type UseQueryResult, useQuery } from '@tanstack/react-query';\nimport { keyPartFingerprint } from '../lib/use-athena-rest-query';\nimport { type CredentialResolver, withCredentialRenewal } from '../runtime/credential-renewal';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport { type AccountIdentity, fetchAccountIdentity } from '../threads/api';\nimport { useAthenaCredentialIdentityEpoch } from './AthenaCredentialSourceContext';\nimport { jwtScope, jwtSubject } from './credential-source';\n\n/**\n * The credential an identity read is sent with, as its key names it: never\n * the secret itself.\n */\nexport interface AccountIdentityCredential {\n /** Agora's API base the read goes to. */\n backendUrl: string;\n /** The signed-in person (`AthenaAuthContext`'s user id), when one is. */\n principal: string | undefined;\n apiKey: string | undefined;\n /** The token the host holds now. A renewal for the same person and scope keeps the key. */\n heldToken: string | null | undefined;\n /** The provider's token getter (`config.getToken`), by identity. */\n tokenGetter: AthenaStatewireTokenGetter | undefined;\n}\n\n/** What one read presents, resolved when it is sent; `force` renews a refused token. */\nexport interface AccountIdentityAuth {\n apiKey?: string;\n token?: string | null;\n}\n\n/** One id per token getter, so the key can name a getter without holding it. */\nconst tokenGetterIds = new WeakMap<AthenaStatewireTokenGetter, number>();\nlet lastTokenGetterId = 0;\n\nfunction tokenGetterId(getToken: AthenaStatewireTokenGetter | undefined): string {\n if (getToken === undefined) return '';\n let id = tokenGetterIds.get(getToken);\n if (id === undefined) {\n lastTokenGetterId += 1;\n id = lastTokenGetterId;\n tokenGetterIds.set(getToken, id);\n }\n return `getter:${id}`;\n}\n\n/**\n * Who a held token acts as, and where, without the token: its JWT subject and\n * the claims scoping it to a workspace or org (`jwtScope`), or the token\n * itself when it is opaque, fingerprinted. A renewal for the same person and\n * scope keeps it, so a rotation is not a new identity; the same person's token\n * scoped to another workspace is.\n */\nfunction heldTokenIdentity(token: string | null | undefined): string {\n if (!token) return '';\n const subject = jwtSubject(token);\n if (subject === null) return `token:${keyPartFingerprint(token)}`;\n const scope = jwtScope(token);\n return scope === null\n ? `sub:${keyPartFingerprint(subject)}`\n : `sub:${keyPartFingerprint(subject)}|scope:${keyPartFingerprint(scope)}`;\n}\n\n/**\n * The one key a credential's identity is read under, whoever reads it: the\n * backend, the signed-in person, the API key's fingerprint, the provider's\n * token getter, who the held token acts as and which workspace it is scoped\n * to, and the provider's credential identity epoch\n * (`useAthenaCredentialIdentityEpoch`), which moves once the getter has handed\n * out a token that acts as someone else.\n */\nexport function accountIdentityQueryKey(\n credential: AccountIdentityCredential,\n identityEpoch: number,\n): readonly unknown[] {\n return [\n 'athena-sdk',\n 'account',\n credential.backendUrl,\n credential.principal ?? '',\n credential.apiKey ? keyPartFingerprint(credential.apiKey) : '',\n tokenGetterId(credential.tokenGetter),\n heldTokenIdentity(credential.heldToken),\n identityEpoch,\n ];\n}\n\n/** A refusal, or a route an older agora lacks, answers the same way again. */\nconst NO_RETRY_STATUSES: ReadonlySet<number> = new Set([401, 403, 404, 405, 409]);\n\n/** Retry a transient failure twice; never a refused credential. */\nfunction retryTransient(failureCount: number, error: Error): boolean {\n const status: unknown = Reflect.get(error, 'status');\n if (typeof status === 'number' && NO_RETRY_STATUSES.has(status)) return false;\n return failureCount < 2;\n}\n\n/**\n * The credential's agora identity (`GET /api/v0/me`): one read per credential,\n * through the query cache, shared by every reader of it. `AthenaChat` (its\n * mentions workspace and the viewer behind its answer labels) and the session\n * list (the conversation index it attaches to) read it here under one key\n * (`accountIdentityQueryKey`), so a page with both sends one request. Never a\n * process-wide map: the key names the credential and never the secret. A\n * renewal for the same person and scope keeps the answer; an API-key switch, a\n * held token scoped to another workspace, or a getter now signing in someone\n * else, reads `/me` again under a new key, never a cached answer for the\n * previous credential. The epoch follows the person, not the scope: a getter\n * that alone hands out the same person's token for another workspace is read\n * again once the answer goes stale.\n *\n * `resolveAuth` presents the credential the key names, so whichever reader\n * sends the request, the answer is the same. `enabled` is the reader's own.\n * `queryClient` defaults to the one in context.\n */\nexport function useAccountIdentityQuery({\n credential,\n resolveAuth,\n enabled,\n queryClient,\n}: {\n credential: AccountIdentityCredential;\n resolveAuth: CredentialResolver<AccountIdentityAuth>;\n enabled: boolean;\n queryClient?: QueryClient;\n}): UseQueryResult<AccountIdentity> {\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n return useQuery(\n {\n queryKey: accountIdentityQueryKey(credential, identityEpoch),\n queryFn: ({ signal }) =>\n withCredentialRenewal(resolveAuth, (auth) =>\n fetchAccountIdentity(credential.backendUrl, auth, { signal }),\n ),\n enabled,\n retry: retryTransient,\n staleTime: 60_000,\n },\n queryClient,\n );\n}\n"],"names":["jwtSubject","keyPartFingerprint","jwtScope","useAthenaCredentialIdentityEpoch","useQuery","withCredentialRenewal","fetchAccountIdentity"],"mappings":";;;;;;;;AA+BA,MAAM,qCAAqB,QAAA;AAC3B,IAAI,oBAAoB;AAExB,SAAS,cAAc,UAA0D;AAC/E,MAAI,aAAa,OAAW,QAAO;AACnC,MAAI,KAAK,eAAe,IAAI,QAAQ;AACpC,MAAI,OAAO,QAAW;AACpB,yBAAqB;AACrB,SAAK;AACL,mBAAe,IAAI,UAAU,EAAE;AAAA,EACjC;AACA,SAAO,UAAU,EAAE;AACrB;AASA,SAAS,kBAAkB,OAA0C;AACnE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,UAAUA,iBAAAA,WAAW,KAAK;AAChC,MAAI,YAAY,KAAM,QAAO,SAASC,mBAAAA,mBAAmB,KAAK,CAAC;AAC/D,QAAM,QAAQC,iBAAAA,SAAS,KAAK;AAC5B,SAAO,UAAU,OACb,OAAOD,mBAAAA,mBAAmB,OAAO,CAAC,KAClC,OAAOA,mBAAAA,mBAAmB,OAAO,CAAC,UAAUA,mBAAAA,mBAAmB,KAAK,CAAC;AAC3E;AAUO,SAAS,wBACd,YACA,eACoB;AACpB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,WAAW,aAAa;AAAA,IACxB,WAAW,SAASA,mBAAAA,mBAAmB,WAAW,MAAM,IAAI;AAAA,IAC5D,cAAc,WAAW,WAAW;AAAA,IACpC,kBAAkB,WAAW,SAAS;AAAA,IACtC;AAAA,EAAA;AAEJ;AAGA,MAAM,wCAA6C,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AAGhF,SAAS,eAAe,cAAsB,OAAuB;AACnE,QAAM,SAAkB,QAAQ,IAAI,OAAO,QAAQ;AACnD,MAAI,OAAO,WAAW,YAAY,kBAAkB,IAAI,MAAM,EAAG,QAAO;AACxE,SAAO,eAAe;AACxB;AAoBO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKoC;AAClC,QAAM,gBAAgBE,8BAAAA,iCAAA;AACtB,SAAOC,WAAAA;AAAAA,IACL;AAAA,MACE,UAAU,wBAAwB,YAAY,aAAa;AAAA,MAC3D,SAAS,CAAC,EAAE,OAAA,MACVC,kBAAAA;AAAAA,QAAsB;AAAA,QAAa,CAAC,SAClCC,IAAAA,qBAAqB,WAAW,YAAY,MAAM,EAAE,QAAQ;AAAA,MAAA;AAAA,MAEhE;AAAA,MACA,OAAO;AAAA,MACP,WAAW;AAAA,IAAA;AAAA,IAEb;AAAA,EAAA;AAEJ;;;"}