@athenaintel/react 0.24.1 → 0.25.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 (145) hide show
  1. package/README.md +18 -0
  2. package/dist/assets/AssetPanel.cjs +3 -2
  3. package/dist/assets/AssetPanel.cjs.map +1 -1
  4. package/dist/assets/AssetPanel.js +3 -2
  5. package/dist/assets/AssetPanel.js.map +1 -1
  6. package/dist/assets/AthenaAssetEmbed.cjs +1 -0
  7. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  8. package/dist/assets/AthenaAssetEmbed.js +1 -0
  9. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  10. package/dist/assets/use-asset-embed.cjs +22 -23
  11. package/dist/assets/use-asset-embed.cjs.map +1 -1
  12. package/dist/assets/use-asset-embed.d.ts +9 -0
  13. package/dist/assets/use-asset-embed.js +22 -23
  14. package/dist/assets/use-asset-embed.js.map +1 -1
  15. package/dist/auth/AthenaAuthProvider.cjs +32 -4
  16. package/dist/auth/AthenaAuthProvider.cjs.map +1 -1
  17. package/dist/auth/AthenaAuthProvider.d.ts +21 -0
  18. package/dist/auth/AthenaAuthProvider.js +33 -5
  19. package/dist/auth/AthenaAuthProvider.js.map +1 -1
  20. package/dist/auth/AthenaCredentialSourceContext.cjs +21 -0
  21. package/dist/auth/AthenaCredentialSourceContext.cjs.map +1 -0
  22. package/dist/auth/AthenaCredentialSourceContext.d.ts +14 -0
  23. package/dist/auth/AthenaCredentialSourceContext.js +21 -0
  24. package/dist/auth/AthenaCredentialSourceContext.js.map +1 -0
  25. package/dist/auth/AthenaSSOProvider.cjs +71 -47
  26. package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
  27. package/dist/auth/AthenaSSOProvider.js +71 -47
  28. package/dist/auth/AthenaSSOProvider.js.map +1 -1
  29. package/dist/auth/credential-source.cjs +120 -0
  30. package/dist/auth/credential-source.cjs.map +1 -0
  31. package/dist/auth/credential-source.d.ts +68 -0
  32. package/dist/auth/credential-source.js +120 -0
  33. package/dist/auth/credential-source.js.map +1 -0
  34. package/dist/auth/types.d.ts +9 -0
  35. package/dist/chat/AthenaChat.cjs +8 -0
  36. package/dist/chat/AthenaChat.cjs.map +1 -1
  37. package/dist/chat/AthenaChat.js +8 -0
  38. package/dist/chat/AthenaChat.js.map +1 -1
  39. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +20 -11
  40. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  41. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +11 -2
  42. package/dist/chat-ui/mentions/suggestions/data/api-client.js +20 -11
  43. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  44. package/dist/collab/mint.cjs +6 -15
  45. package/dist/collab/mint.cjs.map +1 -1
  46. package/dist/collab/mint.d.ts +8 -0
  47. package/dist/collab/mint.js +6 -15
  48. package/dist/collab/mint.js.map +1 -1
  49. package/dist/collab/presence-mint.cjs +20 -16
  50. package/dist/collab/presence-mint.cjs.map +1 -1
  51. package/dist/collab/presence-mint.js +21 -17
  52. package/dist/collab/presence-mint.js.map +1 -1
  53. package/dist/collab/presence-react.cjs +12 -5
  54. package/dist/collab/presence-react.cjs.map +1 -1
  55. package/dist/collab/presence-react.d.ts +3 -1
  56. package/dist/collab/presence-react.js +12 -5
  57. package/dist/collab/presence-react.js.map +1 -1
  58. package/dist/collab/react.cjs +5 -2
  59. package/dist/collab/react.cjs.map +1 -1
  60. package/dist/collab/react.js +5 -2
  61. package/dist/collab/react.js.map +1 -1
  62. package/dist/dictation/use-athena-dictation-engine.cjs +12 -11
  63. package/dist/dictation/use-athena-dictation-engine.cjs.map +1 -1
  64. package/dist/dictation/use-athena-dictation-engine.d.ts +4 -3
  65. package/dist/dictation/use-athena-dictation-engine.js +12 -11
  66. package/dist/dictation/use-athena-dictation-engine.js.map +1 -1
  67. package/dist/index.d.ts +1 -1
  68. package/dist/packages/react/package.json.cjs +1 -1
  69. package/dist/packages/react/package.json.js +1 -1
  70. package/dist/provider/AthenaContext.cjs.map +1 -1
  71. package/dist/provider/AthenaContext.d.ts +7 -1
  72. package/dist/provider/AthenaContext.js.map +1 -1
  73. package/dist/provider/AthenaProvider.cjs +23 -2
  74. package/dist/provider/AthenaProvider.cjs.map +1 -1
  75. package/dist/provider/AthenaProvider.js +23 -2
  76. package/dist/provider/AthenaProvider.js.map +1 -1
  77. package/dist/runtime/auth-refusal-restart.cjs +66 -0
  78. package/dist/runtime/auth-refusal-restart.cjs.map +1 -0
  79. package/dist/runtime/auth-refusal-restart.d.ts +35 -0
  80. package/dist/runtime/auth-refusal-restart.js +66 -0
  81. package/dist/runtime/auth-refusal-restart.js.map +1 -0
  82. package/dist/runtime/auth.cjs +58 -21
  83. package/dist/runtime/auth.cjs.map +1 -1
  84. package/dist/runtime/auth.d.ts +11 -1
  85. package/dist/runtime/auth.js +59 -22
  86. package/dist/runtime/auth.js.map +1 -1
  87. package/dist/runtime/credential-renewal.cjs +29 -0
  88. package/dist/runtime/credential-renewal.cjs.map +1 -0
  89. package/dist/runtime/credential-renewal.d.ts +35 -0
  90. package/dist/runtime/credential-renewal.js +29 -0
  91. package/dist/runtime/credential-renewal.js.map +1 -0
  92. package/dist/search/useAthenaRepoSearch.cjs +3 -5
  93. package/dist/search/useAthenaRepoSearch.cjs.map +1 -1
  94. package/dist/search/useAthenaRepoSearch.d.ts +7 -7
  95. package/dist/search/useAthenaRepoSearch.js +3 -5
  96. package/dist/search/useAthenaRepoSearch.js.map +1 -1
  97. package/dist/threads/api.cjs +5 -1
  98. package/dist/threads/api.cjs.map +1 -1
  99. package/dist/threads/api.js +5 -1
  100. package/dist/threads/api.js.map +1 -1
  101. package/dist/threads/conversation-list-fetcher.cjs +5 -5
  102. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  103. package/dist/threads/conversation-list-fetcher.d.ts +6 -5
  104. package/dist/threads/conversation-list-fetcher.js +5 -5
  105. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  106. package/dist/threads/statewire-thread-list.cjs +13 -14
  107. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  108. package/dist/threads/statewire-thread-list.js +13 -14
  109. package/dist/threads/statewire-thread-list.js.map +1 -1
  110. package/dist/threads/use-rest-conversation-history.cjs +6 -2
  111. package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
  112. package/dist/threads/use-rest-conversation-history.d.ts +3 -1
  113. package/dist/threads/use-rest-conversation-history.js +7 -3
  114. package/dist/threads/use-rest-conversation-history.js.map +1 -1
  115. package/dist/threads/use-statewire-conversation-history.cjs +9 -2
  116. package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
  117. package/dist/threads/use-statewire-conversation-history.d.ts +3 -1
  118. package/dist/threads/use-statewire-conversation-history.js +10 -3
  119. package/dist/threads/use-statewire-conversation-history.js.map +1 -1
  120. package/dist/threads/use-thread-archive.cjs +2 -1
  121. package/dist/threads/use-thread-archive.cjs.map +1 -1
  122. package/dist/threads/use-thread-archive.d.ts +3 -1
  123. package/dist/threads/use-thread-archive.js +2 -1
  124. package/dist/threads/use-thread-archive.js.map +1 -1
  125. package/dist/threads/use-thread-favorite.cjs.map +1 -1
  126. package/dist/threads/use-thread-favorite.d.ts +3 -1
  127. package/dist/threads/use-thread-favorite.js.map +1 -1
  128. package/dist/threads/use-thread-field-override.cjs +2 -1
  129. package/dist/threads/use-thread-field-override.cjs.map +1 -1
  130. package/dist/threads/use-thread-field-override.d.ts +3 -1
  131. package/dist/threads/use-thread-field-override.js +2 -1
  132. package/dist/threads/use-thread-field-override.js.map +1 -1
  133. package/dist/threads/use-thread-rename.cjs.map +1 -1
  134. package/dist/threads/use-thread-rename.d.ts +3 -1
  135. package/dist/threads/use-thread-rename.js.map +1 -1
  136. package/dist/tools/tool-uis.cjs +11 -15
  137. package/dist/tools/tool-uis.cjs.map +1 -1
  138. package/dist/tools/tool-uis.js +11 -15
  139. package/dist/tools/tool-uis.js.map +1 -1
  140. package/dist/voice/use-athena-voice-session.cjs +17 -5
  141. package/dist/voice/use-athena-voice-session.cjs.map +1 -1
  142. package/dist/voice/use-athena-voice-session.d.ts +2 -2
  143. package/dist/voice/use-athena-voice-session.js +17 -5
  144. package/dist/voice/use-athena-voice-session.js.map +1 -1
  145. package/package.json +1 -1
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react = require("react");
4
+ const AthenaCredentialSourceContext = require("../auth/AthenaCredentialSourceContext.cjs");
4
5
  const AthenaContext = require("../provider/AthenaContext.cjs");
5
6
  const client = require("./client.cjs");
6
7
  function useGenericDoc(assetId, options) {
7
8
  const config = AthenaContext.useAthenaConfig();
9
+ const identityEpoch = AthenaCredentialSourceContext.useAthenaCredentialIdentityEpoch();
8
10
  const access = (options == null ? void 0 : options.access) ?? "view";
9
11
  const [handle, setHandle] = react.useState(null);
10
12
  const handleRef = react.useRef(null);
@@ -15,7 +17,8 @@ function useGenericDoc(assetId, options) {
15
17
  auth: {
16
18
  backendUrl: config.backendUrl,
17
19
  apiKey: config.apiKey,
18
- token: config.token
20
+ token: config.token,
21
+ getToken: config.getToken
19
22
  }
20
23
  });
21
24
  handleRef.current = next;
@@ -24,7 +27,7 @@ function useGenericDoc(assetId, options) {
24
27
  handleRef.current = null;
25
28
  next.destroy();
26
29
  };
27
- }, [assetId, access, config.backendUrl, config.apiKey, config.token]);
30
+ }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);
28
31
  const subscribe = react.useMemo(() => {
29
32
  return (onStoreChange) => {
30
33
  if (!handle) return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"react.cjs","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or credentials change; the handle re-mints on\n // its own schedule otherwise.\n }, [assetId, access, config.backendUrl, config.apiKey, config.token]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":["useAthenaConfig","useState","useRef","useEffect","connectGenericDoc","useMemo","useSyncExternalStore"],"mappings":";;;;;AAmCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAASA,cAAAA,gBAAA;AACf,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA,SAAkC,IAAI;AAClE,QAAM,YAAYC,MAAAA,OAAgC,IAAI;AAEtDC,QAAAA,UAAU,MAAM;AACd,UAAM,OAAOC,OAAAA,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,MAAA;AAAA,IAChB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAGF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,KAAK,CAAC;AAEpE,QAAM,YAAYC,MAAAA,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEMC,QAAAA;AAAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;;"}
1
+ {"version":3,"file":"react.cjs","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaCredentialIdentityEpoch } from '../auth/AthenaCredentialSourceContext';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or the signed-in identity changes (the\n // credential source's identity epoch). A renewed token for the same\n // identity reaches the next re-mint through `getToken`, so a renewal\n // does not tear the live connection down.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":["useAthenaConfig","useAthenaCredentialIdentityEpoch","useState","useRef","useEffect","connectGenericDoc","useMemo","useSyncExternalStore"],"mappings":";;;;;;AAoCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAASA,cAAAA,gBAAA;AACf,QAAM,gBAAgBC,8BAAAA,iCAAA;AACtB,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAIC,MAAAA,SAAkC,IAAI;AAClE,QAAM,YAAYC,MAAAA,OAAgC,IAAI;AAEtDC,QAAAA,UAAU,MAAM;AACd,UAAM,OAAOC,OAAAA,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,QACd,UAAU,OAAO;AAAA,MAAA;AAAA,IACnB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAMF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,UAAU,aAAa,CAAC;AAEtF,QAAM,YAAYC,MAAAA,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEMC,QAAAA;AAAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;;"}
@@ -1,8 +1,10 @@
1
1
  import { useState, useRef, useEffect, useMemo, useSyncExternalStore } from "react";
2
+ import { useAthenaCredentialIdentityEpoch } from "../auth/AthenaCredentialSourceContext.js";
2
3
  import { useAthenaConfig } from "../provider/AthenaContext.js";
3
4
  import { connectGenericDoc } from "./client.js";
4
5
  function useGenericDoc(assetId, options) {
5
6
  const config = useAthenaConfig();
7
+ const identityEpoch = useAthenaCredentialIdentityEpoch();
6
8
  const access = (options == null ? void 0 : options.access) ?? "view";
7
9
  const [handle, setHandle] = useState(null);
8
10
  const handleRef = useRef(null);
@@ -13,7 +15,8 @@ function useGenericDoc(assetId, options) {
13
15
  auth: {
14
16
  backendUrl: config.backendUrl,
15
17
  apiKey: config.apiKey,
16
- token: config.token
18
+ token: config.token,
19
+ getToken: config.getToken
17
20
  }
18
21
  });
19
22
  handleRef.current = next;
@@ -22,7 +25,7 @@ function useGenericDoc(assetId, options) {
22
25
  handleRef.current = null;
23
26
  next.destroy();
24
27
  };
25
- }, [assetId, access, config.backendUrl, config.apiKey, config.token]);
28
+ }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);
26
29
  const subscribe = useMemo(() => {
27
30
  return (onStoreChange) => {
28
31
  if (!handle) return () => {
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or credentials change; the handle re-mints on\n // its own schedule otherwise.\n }, [assetId, access, config.backendUrl, config.apiKey, config.token]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":[],"mappings":";;;AAmCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAAS,gBAAA;AACf,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAkC,IAAI;AAClE,QAAM,YAAY,OAAgC,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,OAAO,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,MAAA;AAAA,IAChB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAGF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,KAAK,CAAC;AAEpE,QAAM,YAAY,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEM;AAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;"}
1
+ {"version":3,"file":"react.js","sources":["../../src/collab/react.ts"],"sourcesContent":["/**\n * React bindings for Generic Doc live collaboration.\n *\n * `useGenericDoc(assetId)` opens (and owns) a `connectGenericDoc` handle,\n * resolving auth from the surrounding `AthenaProvider` (per-viewer bearer when\n * present, API key otherwise) and re-rendering on status/presence changes via\n * `useSyncExternalStore`.\n */\n\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';\nimport { useAthenaCredentialIdentityEpoch } from '../auth/AthenaCredentialSourceContext';\nimport { useAthenaConfig } from '../provider/AthenaContext';\nimport {\n type GenericDocHandle,\n type GenericDocStatus,\n type PresenceEntry,\n connectGenericDoc,\n} from './client';\nimport type * as Y from '@y/y';\n\nexport interface UseGenericDocResult {\n /** Null until the handle is created (first render effect). */\n doc: Y.Doc | null;\n status: GenericDocStatus;\n /**\n * Whether the initial server sync completed for the current connection —\n * `status === 'connected'` fires before the server state arrives, so gate\n * \"is this doc really empty?\" UI on this.\n */\n synced: boolean;\n accessType: 'r' | 'rw' | null;\n error: Error | null;\n presence: PresenceEntry[];\n setPresence: (state: Record<string, unknown> | null) => void;\n}\n\nexport function useGenericDoc(\n assetId: string,\n options?: { access?: 'view' | 'edit' }\n): UseGenericDocResult {\n const config = useAthenaConfig();\n const identityEpoch = useAthenaCredentialIdentityEpoch();\n const access = options?.access ?? 'view';\n const [handle, setHandle] = useState<GenericDocHandle | null>(null);\n const handleRef = useRef<GenericDocHandle | null>(null);\n\n useEffect(() => {\n const next = connectGenericDoc({\n assetId,\n access,\n auth: {\n backendUrl: config.backendUrl,\n apiKey: config.apiKey,\n token: config.token,\n getToken: config.getToken,\n },\n });\n handleRef.current = next;\n setHandle(next);\n return () => {\n handleRef.current = null;\n next.destroy();\n };\n // Recreate when the target or the signed-in identity changes (the\n // credential source's identity epoch). A renewed token for the same\n // identity reaches the next re-mint through `getToken`, so a renewal\n // does not tear the live connection down.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [assetId, access, config.backendUrl, config.apiKey, config.getToken, identityEpoch]);\n\n const subscribe = useMemo(() => {\n return (onStoreChange: () => void) => {\n if (!handle) return () => {};\n return handle.onChange(onStoreChange);\n };\n }, [handle]);\n\n const snapshot = useSyncExternalStore(\n subscribe,\n () => {\n if (!handle) return EMPTY_SNAPSHOT;\n return `${handle.status}|${handle.synced}|${handle.accessType ?? ''}|${handle.version}`;\n },\n () => EMPTY_SNAPSHOT\n );\n void snapshot;\n\n return {\n doc: handle?.doc ?? null,\n status: handle?.status ?? 'connecting',\n synced: handle?.synced ?? false,\n accessType: handle?.accessType ?? null,\n error: handle?.error ?? null,\n presence: handle?.getPresence() ?? [],\n setPresence: (state) => handleRef.current?.setPresence(state),\n };\n}\n\nconst EMPTY_SNAPSHOT = 'init|false||0';\n"],"names":[],"mappings":";;;;AAoCO,SAAS,cACd,SACA,SACqB;AACrB,QAAM,SAAS,gBAAA;AACf,QAAM,gBAAgB,iCAAA;AACtB,QAAM,UAAS,mCAAS,WAAU;AAClC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAkC,IAAI;AAClE,QAAM,YAAY,OAAgC,IAAI;AAEtD,YAAU,MAAM;AACd,UAAM,OAAO,kBAAkB;AAAA,MAC7B;AAAA,MACA;AAAA,MACA,MAAM;AAAA,QACJ,YAAY,OAAO;AAAA,QACnB,QAAQ,OAAO;AAAA,QACf,OAAO,OAAO;AAAA,QACd,UAAU,OAAO;AAAA,MAAA;AAAA,IACnB,CACD;AACD,cAAU,UAAU;AACpB,cAAU,IAAI;AACd,WAAO,MAAM;AACX,gBAAU,UAAU;AACpB,WAAK,QAAA;AAAA,IACP;AAAA,EAMF,GAAG,CAAC,SAAS,QAAQ,OAAO,YAAY,OAAO,QAAQ,OAAO,UAAU,aAAa,CAAC;AAEtF,QAAM,YAAY,QAAQ,MAAM;AAC9B,WAAO,CAAC,kBAA8B;AACpC,UAAI,CAAC,OAAQ,QAAO,MAAM;AAAA,MAAC;AAC3B,aAAO,OAAO,SAAS,aAAa;AAAA,IACtC;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEM;AAAA,IACf;AAAA,IACA,MAAM;AACJ,UAAI,CAAC,OAAQ,QAAO;AACpB,aAAO,GAAG,OAAO,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,cAAc,EAAE,IAAI,OAAO,OAAO;AAAA,IACvF;AAAA,IACA,MAAM;AAAA,EAAA;AAIR,SAAO;AAAA,IACL,MAAK,iCAAQ,QAAO;AAAA,IACpB,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,SAAQ,iCAAQ,WAAU;AAAA,IAC1B,aAAY,iCAAQ,eAAc;AAAA,IAClC,QAAO,iCAAQ,UAAS;AAAA,IACxB,WAAU,iCAAQ,kBAAiB,CAAA;AAAA,IACnC,aAAa,CAAC,UAAA;;AAAU,6BAAU,YAAV,mBAAmB,YAAY;AAAA;AAAA,EAAK;AAEhE;AAEA,MAAM,iBAAiB;"}
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react = require("react");
4
4
  const AthenaContext = require("../provider/AthenaContext.cjs");
5
- const statewireAuthHeaders = require("../runtime/statewire-auth-headers.cjs");
5
+ const credentialRenewal = require("../runtime/credential-renewal.cjs");
6
6
  const dictationEngine = require("./dictation-engine.cjs");
7
7
  const recordedDictationEngine = require("./recorded-dictation-engine.cjs");
8
8
  function transcriptionUploadUrl(backendUrl) {
@@ -28,17 +28,18 @@ function transcriptionUploadErrorMessage(status) {
28
28
  function createAthenaUploadTranscriber(getConfig) {
29
29
  return async (clip, { lang, signal }) => {
30
30
  const config = getConfig();
31
- const headers = await statewireAuthHeaders.resolveStatewireAuthHeaders(config, { staleAuth: false });
32
- const form = new FormData();
33
31
  const extension = clip.type.includes("mp4") ? "mp4" : clip.type.includes("ogg") ? "ogg" : "webm";
34
- form.append("file", clip, `dictation.${extension}`);
35
- if (lang) form.append("language", lang);
36
- const response = await fetch(transcriptionUploadUrl(config.backendUrl), {
37
- method: "POST",
38
- headers,
39
- body: form,
40
- credentials: "include",
41
- signal
32
+ const response = await credentialRenewal.sendWithCredentialRenewal(config, (headers) => {
33
+ const form = new FormData();
34
+ form.append("file", clip, `dictation.${extension}`);
35
+ if (lang) form.append("language", lang);
36
+ return fetch(transcriptionUploadUrl(config.backendUrl), {
37
+ method: "POST",
38
+ headers,
39
+ body: form,
40
+ credentials: "include",
41
+ signal
42
+ });
42
43
  });
43
44
  if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));
44
45
  const payload = await response.json();
@@ -1 +1 @@
1
- {"version":3,"file":"use-athena-dictation-engine.cjs","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { resolveStatewireAuthHeaders } from '../runtime/statewire-auth-headers';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up. The session language (host `lang`\n * or the document's) is forwarded so non-English speech is recognised as\n * such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const headers = await resolveStatewireAuthHeaders(config, { staleAuth: false });\n const form = new FormData();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n const response = await fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":["resolveStatewireAuthHeaders","useAthenaConfig","useRef","useMemo","createRecordedDictationEngine","createWebSpeechDictationEngine"],"mappings":";;;;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AASO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,UAAU,MAAMA,qBAAAA,4BAA4B,QAAQ,EAAE,WAAW,OAAO;AAC9E,UAAM,OAAO,IAAI,SAAA;AACjB,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,SAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,QAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,UAAM,WAAW,MAAM,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,MACtE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM;AAAA,MACN,aAAa;AAAA,MACb;AAAA,IAAA,CACD;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAASC,cAAAA,gBAAA;AACf,QAAM,YAAYC,MAAAA,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAOC,MAAAA,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAWC,wBAAAA,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAWC,gBAAAA,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;;;;;"}
1
+ {"version":3,"file":"use-athena-dictation-engine.cjs","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up, and a `401` gets one forced renewal\n * (`getToken({ force: true })`) and a resend before it fails. The session\n * language (host `lang` or the document's) is forwarded so non-English speech\n * is recognised as such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n const response = await sendWithCredentialRenewal(config, (headers) => {\n // A request body is single-use: every attempt builds its own form.\n const form = new FormData();\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n return fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":["sendWithCredentialRenewal","useAthenaConfig","useRef","useMemo","createRecordedDictationEngine","createWebSpeechDictationEngine"],"mappings":";;;;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AAUO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,UAAM,WAAW,MAAMA,kBAAAA,0BAA0B,QAAQ,CAAC,YAAY;AAEpE,YAAM,OAAO,IAAI,SAAA;AACjB,WAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,UAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,aAAO,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,QACtD,QAAQ;AAAA,QACR;AAAA,QACA,MAAM;AAAA,QACN,aAAa;AAAA,QACb;AAAA,MAAA,CACD;AAAA,IACH,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAASC,cAAAA,gBAAA;AACf,QAAM,YAAYC,MAAAA,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAOC,MAAAA,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAWC,wBAAAA,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAWC,gBAAAA,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;;;;;"}
@@ -11,9 +11,10 @@ export declare function transcriptionUploadErrorMessage(status: number): string;
11
11
  /**
12
12
  * Post a finished clip to agora's `/transcription/upload` route, which runs
13
13
  * it through Deepgram and returns the text. Credentials are read at call
14
- * time so a rotated token is picked up. The session language (host `lang`
15
- * or the document's) is forwarded so non-English speech is recognised as
16
- * such; the request is dropped when the engine aborts.
14
+ * time so a rotated token is picked up, and a `401` gets one forced renewal
15
+ * (`getToken({ force: true })`) and a resend before it fails. The session
16
+ * language (host `lang` or the document's) is forwarded so non-English speech
17
+ * is recognised as such; the request is dropped when the engine aborts.
17
18
  */
18
19
  export declare function createAthenaUploadTranscriber(getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>): DictationTranscriber;
19
20
  /**
@@ -1,6 +1,6 @@
1
1
  import { useRef, useMemo } from "react";
2
2
  import { useAthenaConfig } from "../provider/AthenaContext.js";
3
- import { resolveStatewireAuthHeaders } from "../runtime/statewire-auth-headers.js";
3
+ import { sendWithCredentialRenewal } from "../runtime/credential-renewal.js";
4
4
  import { createWebSpeechDictationEngine } from "./dictation-engine.js";
5
5
  import { createRecordedDictationEngine } from "./recorded-dictation-engine.js";
6
6
  function transcriptionUploadUrl(backendUrl) {
@@ -26,17 +26,18 @@ function transcriptionUploadErrorMessage(status) {
26
26
  function createAthenaUploadTranscriber(getConfig) {
27
27
  return async (clip, { lang, signal }) => {
28
28
  const config = getConfig();
29
- const headers = await resolveStatewireAuthHeaders(config, { staleAuth: false });
30
- const form = new FormData();
31
29
  const extension = clip.type.includes("mp4") ? "mp4" : clip.type.includes("ogg") ? "ogg" : "webm";
32
- form.append("file", clip, `dictation.${extension}`);
33
- if (lang) form.append("language", lang);
34
- const response = await fetch(transcriptionUploadUrl(config.backendUrl), {
35
- method: "POST",
36
- headers,
37
- body: form,
38
- credentials: "include",
39
- signal
30
+ const response = await sendWithCredentialRenewal(config, (headers) => {
31
+ const form = new FormData();
32
+ form.append("file", clip, `dictation.${extension}`);
33
+ if (lang) form.append("language", lang);
34
+ return fetch(transcriptionUploadUrl(config.backendUrl), {
35
+ method: "POST",
36
+ headers,
37
+ body: form,
38
+ credentials: "include",
39
+ signal
40
+ });
40
41
  });
41
42
  if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));
42
43
  const payload = await response.json();
@@ -1 +1 @@
1
- {"version":3,"file":"use-athena-dictation-engine.js","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { resolveStatewireAuthHeaders } from '../runtime/statewire-auth-headers';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up. The session language (host `lang`\n * or the document's) is forwarded so non-English speech is recognised as\n * such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const headers = await resolveStatewireAuthHeaders(config, { staleAuth: false });\n const form = new FormData();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n const response = await fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":[],"mappings":";;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AASO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,UAAU,MAAM,4BAA4B,QAAQ,EAAE,WAAW,OAAO;AAC9E,UAAM,OAAO,IAAI,SAAA;AACjB,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,SAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,QAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,UAAM,WAAW,MAAM,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,MACtE,QAAQ;AAAA,MACR;AAAA,MACA,MAAM;AAAA,MACN,aAAa;AAAA,MACb;AAAA,IAAA,CACD;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAAS,gBAAA;AACf,QAAM,YAAY,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAO,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAW,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAW,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;"}
1
+ {"version":3,"file":"use-athena-dictation-engine.js","sources":["../../src/dictation/use-athena-dictation-engine.ts"],"sourcesContent":["import { useMemo, useRef } from 'react';\nimport { type AthenaConfig, useAthenaConfig } from '../provider/AthenaContext';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport { createWebSpeechDictationEngine, type DictationEngine } from './dictation-engine';\nimport { createRecordedDictationEngine, type DictationTranscriber } from './recorded-dictation-engine';\n\ninterface UploadTranscriptionResponse {\n transcript: string;\n}\n\n/** Agora's REST base from the configured backend URL (which may point at a nested endpoint). */\nexport function transcriptionUploadUrl(backendUrl: string): string {\n return `${backendUrl.replace(/\\/api\\/assistant-ui\\/?$/, '').replace(/\\/$/, '')}/api/transcription/upload`;\n}\n\n/**\n * One line for the composer per way `/transcription/upload` refuses a clip.\n * The copy lives here, apart from the fetch, so it can be pinned by a test.\n */\nexport function transcriptionUploadErrorMessage(status: number): string {\n switch (status) {\n case 401:\n case 403:\n return 'Dictation is not available for this sign-in. Reload and try again.';\n case 413:\n return 'That recording is too long to transcribe.';\n case 422:\n return 'The dictation language was rejected. Use a BCP-47 tag such as en-US.';\n case 429:\n return 'Too many dictations in a row. Wait a moment and try again.';\n case 503:\n return 'Dictation is not set up on this server.';\n default:\n return `Transcription failed (${status}).`;\n }\n}\n\n/**\n * Post a finished clip to agora's `/transcription/upload` route, which runs\n * it through Deepgram and returns the text. Credentials are read at call\n * time so a rotated token is picked up, and a `401` gets one forced renewal\n * (`getToken({ force: true })`) and a resend before it fails. The session\n * language (host `lang` or the document's) is forwarded so non-English speech\n * is recognised as such; the request is dropped when the engine aborts.\n */\nexport function createAthenaUploadTranscriber(\n getConfig: () => Pick<AthenaConfig, 'backendUrl' | 'apiKey' | 'token' | 'getToken'>,\n): DictationTranscriber {\n return async (clip, { lang, signal }) => {\n const config = getConfig();\n const extension = clip.type.includes('mp4') ? 'mp4' : clip.type.includes('ogg') ? 'ogg' : 'webm';\n const response = await sendWithCredentialRenewal(config, (headers) => {\n // A request body is single-use: every attempt builds its own form.\n const form = new FormData();\n form.append('file', clip, `dictation.${extension}`);\n if (lang) form.append('language', lang);\n return fetch(transcriptionUploadUrl(config.backendUrl), {\n method: 'POST',\n headers,\n body: form,\n credentials: 'include',\n signal,\n });\n });\n if (!response.ok) throw new Error(transcriptionUploadErrorMessage(response.status));\n const payload = (await response.json()) as UploadTranscriptionResponse;\n return typeof payload.transcript === 'string' ? payload.transcript : '';\n };\n}\n\n/**\n * The composer's default speech engine: record with `MediaRecorder`, then\n * transcribe the clip through agora (Deepgram). Falls back to the browser's\n * Web Speech API only where `MediaRecorder` is missing. A host-provided\n * `engine` always wins.\n */\nexport function useAthenaDictationEngine(override?: DictationEngine): DictationEngine {\n const config = useAthenaConfig();\n const configRef = useRef(config);\n configRef.current = config;\n return useMemo(() => {\n if (override) return override;\n const recorded = createRecordedDictationEngine({\n transcribe: createAthenaUploadTranscriber(() => configRef.current),\n });\n return recorded.isSupported() ? recorded : createWebSpeechDictationEngine();\n }, [override]);\n}\n"],"names":[],"mappings":";;;;;AAWO,SAAS,uBAAuB,YAA4B;AACjE,SAAO,GAAG,WAAW,QAAQ,2BAA2B,EAAE,EAAE,QAAQ,OAAO,EAAE,CAAC;AAChF;AAMO,SAAS,gCAAgC,QAAwB;AACtE,UAAQ,QAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,yBAAyB,MAAM;AAAA,EAAA;AAE5C;AAUO,SAAS,8BACd,WACsB;AACtB,SAAO,OAAO,MAAM,EAAE,MAAM,aAAa;AACvC,UAAM,SAAS,UAAA;AACf,UAAM,YAAY,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ,KAAK,KAAK,SAAS,KAAK,IAAI,QAAQ;AAC1F,UAAM,WAAW,MAAM,0BAA0B,QAAQ,CAAC,YAAY;AAEpE,YAAM,OAAO,IAAI,SAAA;AACjB,WAAK,OAAO,QAAQ,MAAM,aAAa,SAAS,EAAE;AAClD,UAAI,KAAM,MAAK,OAAO,YAAY,IAAI;AACtC,aAAO,MAAM,uBAAuB,OAAO,UAAU,GAAG;AAAA,QACtD,QAAQ;AAAA,QACR;AAAA,QACA,MAAM;AAAA,QACN,aAAa;AAAA,QACb;AAAA,MAAA,CACD;AAAA,IACH,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,gCAAgC,SAAS,MAAM,CAAC;AAClF,UAAM,UAAW,MAAM,SAAS,KAAA;AAChC,WAAO,OAAO,QAAQ,eAAe,WAAW,QAAQ,aAAa;AAAA,EACvE;AACF;AAQO,SAAS,yBAAyB,UAA6C;AACpF,QAAM,SAAS,gBAAA;AACf,QAAM,YAAY,OAAO,MAAM;AAC/B,YAAU,UAAU;AACpB,SAAO,QAAQ,MAAM;AACnB,QAAI,SAAU,QAAO;AACrB,UAAM,WAAW,8BAA8B;AAAA,MAC7C,YAAY,8BAA8B,MAAM,UAAU,OAAO;AAAA,IAAA,CAClE;AACD,WAAO,SAAS,gBAAgB,WAAW,+BAAA;AAAA,EAC7C,GAAG,CAAC,QAAQ,CAAC;AACf;"}
package/dist/index.d.ts CHANGED
@@ -148,7 +148,7 @@ export type { AthenaStatewireRuntimeConfig } from './runtime/useAthenaStatewireR
148
148
  export { buildStatewireRunConfig } from './runtime/statewire-run-config';
149
149
  export type { StatewireRunConfigOptions } from './runtime/statewire-run-config';
150
150
  export { useParentAuth, useParentBridge } from './runtime/auth';
151
- export type { ParentBridgeState } from './runtime/auth';
151
+ export type { ParentBridge, ParentBridgeState } from './runtime/auth';
152
152
  export { createAthenaSpacesUrl, DEFAULT_APP_URL, DEFAULT_STATEWIRE_SYNC_URL, deriveAthenaApiUrl, deriveAthenaAppUrl, deriveStatewireSyncUrl, resolveStatewireSyncUrl, } from './lib/athena-urls';
153
153
  export type { AthenaEnvironment } from './lib/athena-urls';
154
154
  export { Button, buttonVariants } from './ui/button';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const version = "0.24.1";
3
+ const version = "0.25.0";
4
4
  const packageJson = {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
- const version = "0.24.1";
1
+ const version = "0.25.0";
2
2
  const packageJson = {
3
3
  version
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AthenaContext.cjs","sources":["../../src/provider/AthenaContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaProviderConfig } from './config';\nimport type { AthenaCitationLinkOptions, AthenaLinkClickOptions } from './citation-links';\n\nexport interface AthenaConfig extends Pick<AthenaProviderConfig, 'apiKey' | 'token'> {\n backendUrl: string;\n /** The host's live token resolver, for SDK hooks that call agora's REST surface directly. */\n getToken?: AthenaStatewireTokenGetter;\n appUrl: string;\n enabledToolkits: string[];\n linkClicks?: AthenaLinkClickOptions;\n citationLinks?: AthenaCitationLinkOptions;\n}\n\nexport const AthenaContext = createContext<AthenaConfig | null>(null);\n\nexport function useAthenaConfig(): AthenaConfig {\n const ctx = useContext(AthenaContext);\n if (!ctx) {\n throw new Error('[AthenaSDK] useAthenaConfig must be used within <AthenaProvider>');\n }\n return ctx;\n}\n"],"names":["createContext","useContext"],"mappings":";;;AAeO,MAAM,gBAAgBA,MAAAA,cAAmC,IAAI;AAE7D,SAAS,kBAAgC;AAC9C,QAAM,MAAMC,MAAAA,WAAW,aAAa;AACpC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AACA,SAAO;AACT;;;"}
1
+ {"version":3,"file":"AthenaContext.cjs","sources":["../../src/provider/AthenaContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaProviderConfig } from './config';\nimport type { AthenaCitationLinkOptions, AthenaLinkClickOptions } from './citation-links';\n\nexport interface AthenaConfig extends Pick<AthenaProviderConfig, 'apiKey' | 'token'> {\n backendUrl: string;\n /**\n * The SDK's live token resolver, for SDK hooks that call agora's REST\n * surface directly. Every sign-in mode feeds it (the host's `getToken`, a\n * sign-in provider, the Marathon or parent-window bridge); pass\n * `{ force: true }` after a 401 to renew the token before one resend.\n * Undefined when there is no token to resolve (API key alone).\n */\n getToken?: AthenaStatewireTokenGetter;\n appUrl: string;\n enabledToolkits: string[];\n linkClicks?: AthenaLinkClickOptions;\n citationLinks?: AthenaCitationLinkOptions;\n}\n\nexport const AthenaContext = createContext<AthenaConfig | null>(null);\n\nexport function useAthenaConfig(): AthenaConfig {\n const ctx = useContext(AthenaContext);\n if (!ctx) {\n throw new Error('[AthenaSDK] useAthenaConfig must be used within <AthenaProvider>');\n }\n return ctx;\n}\n"],"names":["createContext","useContext"],"mappings":";;;AAqBO,MAAM,gBAAgBA,MAAAA,cAAmC,IAAI;AAE7D,SAAS,kBAAgC;AAC9C,QAAM,MAAMC,MAAAA,WAAW,aAAa;AACpC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AACA,SAAO;AACT;;;"}
@@ -3,7 +3,13 @@ import type { AthenaProviderConfig } from './config';
3
3
  import type { AthenaCitationLinkOptions, AthenaLinkClickOptions } from './citation-links';
4
4
  export interface AthenaConfig extends Pick<AthenaProviderConfig, 'apiKey' | 'token'> {
5
5
  backendUrl: string;
6
- /** The host's live token resolver, for SDK hooks that call agora's REST surface directly. */
6
+ /**
7
+ * The SDK's live token resolver, for SDK hooks that call agora's REST
8
+ * surface directly. Every sign-in mode feeds it (the host's `getToken`, a
9
+ * sign-in provider, the Marathon or parent-window bridge); pass
10
+ * `{ force: true }` after a 401 to renew the token before one resend.
11
+ * Undefined when there is no token to resolve (API key alone).
12
+ */
7
13
  getToken?: AthenaStatewireTokenGetter;
8
14
  appUrl: string;
9
15
  enabledToolkits: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"AthenaContext.js","sources":["../../src/provider/AthenaContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaProviderConfig } from './config';\nimport type { AthenaCitationLinkOptions, AthenaLinkClickOptions } from './citation-links';\n\nexport interface AthenaConfig extends Pick<AthenaProviderConfig, 'apiKey' | 'token'> {\n backendUrl: string;\n /** The host's live token resolver, for SDK hooks that call agora's REST surface directly. */\n getToken?: AthenaStatewireTokenGetter;\n appUrl: string;\n enabledToolkits: string[];\n linkClicks?: AthenaLinkClickOptions;\n citationLinks?: AthenaCitationLinkOptions;\n}\n\nexport const AthenaContext = createContext<AthenaConfig | null>(null);\n\nexport function useAthenaConfig(): AthenaConfig {\n const ctx = useContext(AthenaContext);\n if (!ctx) {\n throw new Error('[AthenaSDK] useAthenaConfig must be used within <AthenaProvider>');\n }\n return ctx;\n}\n"],"names":[],"mappings":";AAeO,MAAM,gBAAgB,cAAmC,IAAI;AAE7D,SAAS,kBAAgC;AAC9C,QAAM,MAAM,WAAW,aAAa;AACpC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"AthenaContext.js","sources":["../../src/provider/AthenaContext.ts"],"sourcesContent":["import { createContext, useContext } from 'react';\nimport type { AthenaStatewireTokenGetter } from '../runtime/statewire-auth-headers';\nimport type { AthenaProviderConfig } from './config';\nimport type { AthenaCitationLinkOptions, AthenaLinkClickOptions } from './citation-links';\n\nexport interface AthenaConfig extends Pick<AthenaProviderConfig, 'apiKey' | 'token'> {\n backendUrl: string;\n /**\n * The SDK's live token resolver, for SDK hooks that call agora's REST\n * surface directly. Every sign-in mode feeds it (the host's `getToken`, a\n * sign-in provider, the Marathon or parent-window bridge); pass\n * `{ force: true }` after a 401 to renew the token before one resend.\n * Undefined when there is no token to resolve (API key alone).\n */\n getToken?: AthenaStatewireTokenGetter;\n appUrl: string;\n enabledToolkits: string[];\n linkClicks?: AthenaLinkClickOptions;\n citationLinks?: AthenaCitationLinkOptions;\n}\n\nexport const AthenaContext = createContext<AthenaConfig | null>(null);\n\nexport function useAthenaConfig(): AthenaConfig {\n const ctx = useContext(AthenaContext);\n if (!ctx) {\n throw new Error('[AthenaSDK] useAthenaConfig must be used within <AthenaProvider>');\n }\n return ctx;\n}\n"],"names":[],"mappings":";AAqBO,MAAM,gBAAgB,cAAmC,IAAI;AAE7D,SAAS,kBAAgC;AAC9C,QAAM,MAAM,WAAW,aAAa;AACpC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,kEAAkE;AAAA,EACpF;AACA,SAAO;AACT;"}
@@ -7,6 +7,9 @@ const athenaUrls = require("../lib/athena-urls.cjs");
7
7
  const mint = require("../collab/mint.cjs");
8
8
  const auth = require("../runtime/auth.cjs");
9
9
  const AthenaAuthContext = require("../auth/AthenaAuthContext.cjs");
10
+ const AthenaCredentialSourceContext = require("../auth/AthenaCredentialSourceContext.cjs");
11
+ const credentialSource = require("../auth/credential-source.cjs");
12
+ const authRefusalRestart = require("../runtime/auth-refusal-restart.cjs");
10
13
  const useAthenaStatewireRuntime = require("../runtime/useAthenaStatewireRuntime.cjs");
11
14
  const statewireLifecycle = require("../runtime/statewire-lifecycle.cjs");
12
15
  const statewireRunConfig = require("../runtime/statewire-run-config.cjs");
@@ -150,6 +153,10 @@ function AthenaStatewireRuntimeMount({
150
153
  dispatchLifecycle({ type: "raw-connection", reconnect: connection.reconnect });
151
154
  }
152
155
  });
156
+ authRefusalRestart.useRestartAfterAuthRefusal({
157
+ connection: lifecycle.connection,
158
+ reconnect: lifecycle.reconnect
159
+ });
153
160
  const dismissError = react.useCallback(
154
161
  () => dispatchLifecycle({ type: "error", error: null }),
155
162
  []
@@ -414,6 +421,20 @@ function AthenaProvider({
414
421
  [composerHandoff$1]
415
422
  );
416
423
  const effectiveToken = configuredToken !== void 0 ? configuredToken : authProviderToken ?? bridge.token;
424
+ const authProviderGetToken = authContext == null ? void 0 : authContext.getToken;
425
+ const credentialInputsRef = react.useRef({ token: null });
426
+ credentialInputsRef.current = {
427
+ hostGetToken: configuredGetToken,
428
+ token: effectiveToken,
429
+ renew: configuredToken !== void 0 ? void 0 : authProviderToken !== null ? authProviderGetToken ? async ({ force }) => await authProviderGetToken({ force }) ?? null : void 0 : bridge.token !== null ? bridge.renewToken : void 0
430
+ };
431
+ const [credentials] = react.useState(
432
+ () => credentialSource.createAthenaCredentialSource(() => credentialInputsRef.current)
433
+ );
434
+ react.useEffect(() => {
435
+ credentials.notifyTokenChanged();
436
+ }, [credentials, effectiveToken]);
437
+ const credentialGetToken = configuredGetToken !== void 0 || effectiveToken ? credentials.getToken : void 0;
417
438
  const effectiveApiUrl = configuredApiUrl ?? environmentUrls.apiUrl;
418
439
  const effectiveBackendUrl = mint.getAthenaApiBaseUrl(
419
440
  configuredBackendUrl ?? bridge.backendBaseUrl ?? environmentUrls.backendUrl
@@ -439,7 +460,7 @@ function AthenaProvider({
439
460
  appUrl: effectiveAppUrl,
440
461
  apiKey: configuredApiKey,
441
462
  token: effectiveToken,
442
- getToken: configuredGetToken,
463
+ getToken: credentialGetToken,
443
464
  model,
444
465
  agent,
445
466
  channel,
@@ -470,7 +491,7 @@ function AthenaProvider({
470
491
  {
471
492
  className: themeStyleVars ? "athena-sdk athena-themed" : "athena-sdk",
472
493
  style: themeStyleVars,
473
- children: /* @__PURE__ */ jsxRuntime.jsx(PostHogProvider.PostHogProvider, { config: posthogConfig, children: /* @__PURE__ */ jsxRuntime.jsx(composerHandoff.ComposerHandoffContext.Provider, { value: composerHandoff$1, children: /* @__PURE__ */ jsxRuntime.jsx(themeStyleContext.AthenaThemeStyleContext.Provider, { value: themeStyleVars, children: inner }) }) })
494
+ children: /* @__PURE__ */ jsxRuntime.jsx(PostHogProvider.PostHogProvider, { config: posthogConfig, children: /* @__PURE__ */ jsxRuntime.jsx(composerHandoff.ComposerHandoffContext.Provider, { value: composerHandoff$1, children: /* @__PURE__ */ jsxRuntime.jsx(themeStyleContext.AthenaThemeStyleContext.Provider, { value: themeStyleVars, children: /* @__PURE__ */ jsxRuntime.jsx(AthenaCredentialSourceContext.AthenaCredentialSourceContext.Provider, { value: credentials, children: inner }) }) }) })
474
495
  }
475
496
  );
476
497
  }