reflet-sdk 0.6.5 → 0.6.6

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 (59) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +100 -2
  3. package/dist/{client-DAdWdIFY.js → client-oFFfRxAM.js} +135 -59
  4. package/dist/client-oFFfRxAM.js.map +1 -0
  5. package/dist/client-transport-B7BdVU7J.d.ts +150 -0
  6. package/dist/client-transport-B7BdVU7J.d.ts.map +1 -0
  7. package/dist/{devtools-layer-CEjzYGBB.js → devtools-layer-B91EVgqg.js} +417 -916
  8. package/dist/devtools-layer-B91EVgqg.js.map +1 -0
  9. package/dist/devtools-next.js +1 -1
  10. package/dist/devtools-vite.js +2 -2
  11. package/dist/feedback.d.ts +7 -1
  12. package/dist/feedback.d.ts.map +1 -1
  13. package/dist/feedback.js +105 -57
  14. package/dist/feedback.js.map +1 -1
  15. package/dist/{handler-BSNmdHXv.js → handler-yA_knIf3.js} +4 -4
  16. package/dist/{handler-BSNmdHXv.js.map → handler-yA_knIf3.js.map} +1 -1
  17. package/dist/index-Buum9tIY.d.ts +230 -0
  18. package/dist/index-Buum9tIY.d.ts.map +1 -0
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +1 -1
  21. package/dist/options-CIBazDPp.d.ts.map +1 -1
  22. package/dist/protocol-vJhQHTcB.js +390 -0
  23. package/dist/protocol-vJhQHTcB.js.map +1 -0
  24. package/dist/{react-context-Caq1fodf.js → react-context-Kazo_JCz.js} +3 -14
  25. package/dist/react-context-Kazo_JCz.js.map +1 -0
  26. package/dist/react.d.ts +43 -3
  27. package/dist/react.d.ts.map +1 -1
  28. package/dist/react.js +389 -3
  29. package/dist/react.js.map +1 -1
  30. package/dist/selection-outline-NzAAAZXR.js +51 -0
  31. package/dist/selection-outline-NzAAAZXR.js.map +1 -0
  32. package/dist/server.js +1 -1
  33. package/dist/server.js.map +1 -1
  34. package/dist/sessions-panel-DVyWzZ3C.js +6011 -0
  35. package/dist/sessions-panel-DVyWzZ3C.js.map +1 -0
  36. package/dist/{submit-MvVjNdYc.js → submit-DYGZQVdG.js} +44 -70
  37. package/dist/submit-DYGZQVdG.js.map +1 -0
  38. package/dist/surveys.d.ts +3 -0
  39. package/dist/surveys.js +2 -0
  40. package/dist/team-activation-DCqh71bl.js +24 -0
  41. package/dist/team-activation-DCqh71bl.js.map +1 -0
  42. package/dist/team-layer-F-9BE4hc.js +423 -0
  43. package/dist/team-layer-F-9BE4hc.js.map +1 -0
  44. package/dist/{types-DnaDEN9Y.d.ts → types-B3XiHPqy.d.ts} +4 -9
  45. package/dist/types-B3XiHPqy.d.ts.map +1 -0
  46. package/dist/use-survey-session-LK6sN1Fg.js +1247 -0
  47. package/dist/use-survey-session-LK6sN1Fg.js.map +1 -0
  48. package/dist/z-index-CwokqVq_.js +15 -0
  49. package/dist/z-index-CwokqVq_.js.map +1 -0
  50. package/package.json +6 -2
  51. package/dist/client-DAdWdIFY.js.map +0 -1
  52. package/dist/client-DF1fdsSC.d.ts +0 -131
  53. package/dist/client-DF1fdsSC.d.ts.map +0 -1
  54. package/dist/devtools-layer-CEjzYGBB.js.map +0 -1
  55. package/dist/protocol-ClqTFzwR.js +0 -35
  56. package/dist/protocol-ClqTFzwR.js.map +0 -1
  57. package/dist/react-context-Caq1fodf.js.map +0 -1
  58. package/dist/submit-MvVjNdYc.js.map +0 -1
  59. package/dist/types-DnaDEN9Y.d.ts.map +0 -1
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":[],"sources":["../src/react-changelog-widget.tsx","../src/react-feedback-dialog-types.ts","../src/react-feedback-styles-parts.ts","../src/react-feedback-styles.ts","../src/react-feedback-dialog-hook.ts","../src/react-feedback-dialog.tsx","../src/react-feedback-button.tsx","../src/react-mutation-hooks.ts","../src/react-query-hooks.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from \"react\";\nimport type { ChangelogEntry } from \"./types\";\n\ninterface ChangelogWidgetProps {\n autoOpenForNew?: boolean;\n locale?: string;\n maxEntries?: number;\n mode?: \"card\" | \"popup\" | \"trigger\";\n onClose?: () => void;\n onEntryClick?: (entry: ChangelogEntry) => void;\n onOpen?: () => void;\n position?: \"bottom-right\" | \"bottom-left\";\n primaryColor?: string;\n publicKey: string;\n theme?: \"light\" | \"dark\" | \"auto\";\n triggerSelector?: string;\n}\n\ninterface WidgetInstance {\n close: () => void;\n destroy: () => void;\n getUnreadCount: () => number;\n init: () => Promise<void>;\n markAsRead: () => void;\n open: () => void;\n}\n\ndeclare global {\n interface Window {\n RefletChangelogWidget?: new (\n config: ChangelogWidgetProps\n ) => WidgetInstance;\n }\n}\n\n/**\n * React component that wraps the Reflet Changelog Widget.\n *\n * Dynamically loads and initializes a RefletChangelogWidget instance.\n *\n * @example\n * ```tsx\n * import { ChangelogWidget } from 'reflet-sdk/react';\n *\n * export function MyApp() {\n * return <ChangelogWidget publicKey=\"fb_pub_xxx\" mode=\"card\" theme=\"auto\" />;\n * }\n * ```\n */\nexport function ChangelogWidget({\n publicKey,\n mode = \"card\",\n position = \"bottom-right\",\n theme = \"light\",\n primaryColor,\n maxEntries,\n triggerSelector,\n autoOpenForNew,\n locale,\n onOpen,\n onClose,\n onEntryClick,\n}: ChangelogWidgetProps) {\n const widgetRef = useRef<WidgetInstance | null>(null);\n\n const initWidget = useCallback(async () => {\n // Check if RefletChangelogWidget is available globally\n const WidgetClass = window.RefletChangelogWidget;\n\n if (WidgetClass) {\n const instance = new WidgetClass({\n autoOpenForNew,\n locale,\n maxEntries,\n mode,\n onClose,\n onEntryClick,\n onOpen,\n position,\n primaryColor,\n publicKey,\n theme,\n triggerSelector,\n });\n\n widgetRef.current = instance;\n await instance.init();\n return;\n }\n\n // Try loading the script dynamically\n const existingScript = document.querySelector(\n 'script[src*=\"reflet-changelog\"]'\n );\n if (!existingScript) {\n const script = document.createElement(\"script\");\n script.src = \"https://cdn.reflet.app/widget/reflet-changelog.v1.js\";\n script.async = true;\n script.onload = () => {\n const LoadedClass = window.RefletChangelogWidget;\n\n if (LoadedClass) {\n const instance = new LoadedClass({\n autoOpenForNew,\n locale,\n maxEntries,\n mode,\n onClose,\n onEntryClick,\n onOpen,\n position,\n primaryColor,\n publicKey,\n theme,\n triggerSelector,\n });\n\n widgetRef.current = instance;\n instance.init();\n }\n };\n document.head.appendChild(script);\n }\n }, [\n publicKey,\n mode,\n position,\n theme,\n primaryColor,\n maxEntries,\n triggerSelector,\n autoOpenForNew,\n locale,\n onOpen,\n onClose,\n onEntryClick,\n ]);\n\n useEffect(() => {\n initWidget();\n\n return () => {\n if (widgetRef.current) {\n widgetRef.current.destroy();\n widgetRef.current = null;\n }\n };\n }, [initWidget]);\n\n // This component doesn't render visible DOM - the widget manages its own Shadow DOM\n return null;\n}\n","import type { CreateFeedbackResponse, RefletUser } from \"./types\";\n\nexport interface FeedbackDialogLabels {\n cancel?: string;\n close?: string;\n descriptionPlaceholder?: string;\n emailLabel?: string;\n emailPlaceholder?: string;\n required?: string;\n submit?: string;\n successMessage?: string;\n successTitle?: string;\n title?: string;\n titlePlaceholder?: string;\n}\n\nexport interface FeedbackDialogProps {\n /** API base URL override */\n baseUrl?: string;\n /** Custom labels / i18n */\n labels?: FeedbackDialogLabels;\n /** Called when dialog closes */\n onClose?: () => void;\n /** Called when dialog opens */\n onOpen?: () => void;\n /** Callback when open state changes */\n onOpenChange: (open: boolean) => void;\n /** Called after successful submission */\n onSubmit?: (result: CreateFeedbackResponse) => void;\n /** Whether the dialog is open */\n open: boolean;\n /** Primary brand color (CSS value) */\n primaryColor?: string;\n /** Public API key (falls back to RefletProvider context) */\n publicKey?: string;\n /** Color theme */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** User identification (falls back to RefletProvider context) */\n user?: RefletUser;\n /** Pre-signed user token */\n userToken?: string;\n}\n\nexport const DEFAULT_LABELS: Required<FeedbackDialogLabels> = {\n cancel: \"Cancel\",\n close: \"Close\",\n descriptionPlaceholder: \"Tell us more... (optional)\",\n emailLabel: \"Email\",\n emailPlaceholder: \"your@email.com (optional)\",\n required: \"Required\",\n submit: \"Send Feedback\",\n successMessage: \"Your feedback has been received. We appreciate your input.\",\n successTitle: \"Thank you!\",\n title: \"Send Feedback\",\n titlePlaceholder: \"What's on your mind?\",\n};\n\nexport const AUTO_CLOSE_DELAY = 2500;\n","export function getFeedbackChromeCSS(): string {\n return `\n.reflet-footer {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n padding-top: 4px;\n}\n\n.reflet-btn {\n min-height: 40px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--reflet-radius-sm);\n font-family: var(--reflet-font);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 150ms, color 150ms, opacity 150ms, transform 150ms var(--reflet-ease);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n line-height: 1;\n}\n\n.reflet-btn-primary {\n background: var(--reflet-primary);\n color: var(--reflet-on-primary);\n}\n\n.reflet-btn-primary:hover:not(:disabled) {\n background: var(--reflet-primary-hover);\n}\n\n.reflet-btn-primary:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.reflet-btn-secondary {\n background: transparent;\n color: var(--reflet-text-secondary);\n}\n\n.reflet-btn-secondary:hover {\n background: var(--reflet-bg-secondary);\n color: var(--reflet-text);\n}\n\n.reflet-spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--reflet-on-primary-soft);\n border-top-color: var(--reflet-on-primary);\n border-radius: 50%;\n animation: reflet-spin 600ms linear infinite;\n}\n\n.reflet-success {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n text-align: center;\n gap: 16px;\n}\n\n.reflet-success-icon {\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--reflet-success) 12%, transparent);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.reflet-success-icon svg {\n width: 28px;\n height: 28px;\n color: var(--reflet-success);\n}\n\n.reflet-success-icon svg path {\n stroke-dasharray: 24;\n stroke-dashoffset: 24;\n animation: reflet-checkmark 400ms ease-out 100ms forwards;\n}\n\n.reflet-success-title {\n font-size: 18px;\n font-weight: 600;\n color: var(--reflet-text);\n margin: 0;\n}\n\n.reflet-success-text {\n font-size: 14px;\n color: var(--reflet-text-secondary);\n margin: 0;\n}\n\n.reflet-error-msg {\n font-size: 13px;\n color: var(--reflet-error);\n margin: 0;\n}\n\n.reflet-trigger {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-height: 40px;\n padding: 10px 18px;\n background: var(--reflet-primary);\n color: var(--reflet-on-primary);\n border: none;\n border-radius: var(--reflet-radius-sm);\n font-family: var(--reflet-font);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 150ms, transform 150ms var(--reflet-ease);\n line-height: 1;\n}\n\n.reflet-trigger:hover {\n background: var(--reflet-primary-hover);\n}\n\n.reflet-trigger svg {\n width: 16px;\n height: 16px;\n}\n\n.reflet-hp {\n position: absolute;\n top: -9999px;\n left: -9999px;\n opacity: 0;\n height: 0;\n width: 0;\n z-index: -1;\n}\n\n.reflet-powered {\n text-align: center;\n padding: 0 24px 16px;\n font-size: 11px;\n color: var(--reflet-text-tertiary);\n}\n\n.reflet-powered a {\n color: var(--reflet-text-secondary);\n text-decoration: none;\n font-weight: 500;\n}\n\n.reflet-powered a:hover {\n color: var(--reflet-primary);\n}\n\n@media (max-width: 640px) {\n .reflet-header::before {\n content: '';\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translateX(-50%);\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background: var(--reflet-border);\n }\n\n .reflet-header {\n position: relative;\n padding-top: 24px;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .reflet-success-icon svg path {\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n animation: none;\n }\n}\n`;\n}\n","import { getFeedbackChromeCSS } from \"./react-feedback-styles-parts\";\nimport { REFLET_Z_INDEX } from \"./z-index\";\n\nconst STYLE_ID = \"reflet-feedback-styles\";\n\nexport function injectFeedbackStyles(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n if (document.getElementById(STYLE_ID)) {\n return;\n }\n\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = getFeedbackCSS();\n document.head.appendChild(style);\n}\n\nfunction getFeedbackCSS(): string {\n return `\n:root {\n --reflet-scrim: rgb(0 0 0 / 45%);\n}\n\n[data-reflet-feedback] {\n --reflet-primary: #6366f1;\n --reflet-primary-hover: #4f46e5;\n --reflet-on-primary: #ffffff;\n --reflet-on-primary-soft: rgb(255 255 255 / 30%);\n --reflet-bg: #ffffff;\n --reflet-bg-secondary: #f9fafb;\n --reflet-text: #111827;\n --reflet-text-secondary: #6b7280;\n --reflet-text-tertiary: #9ca3af;\n --reflet-border: #e5e7eb;\n --reflet-radius: 12px;\n --reflet-radius-sm: 8px;\n --reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 25%), 0 0 0 1px rgb(0 0 0 / 5%);\n --reflet-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --reflet-success: #22c55e;\n --reflet-error: #ef4444;\n --reflet-ease: cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n@media (prefers-color-scheme: dark) {\n [data-reflet-feedback][data-theme=\"auto\"] {\n --reflet-bg: #1f2937;\n --reflet-bg-secondary: #111827;\n --reflet-text: #f9fafb;\n --reflet-text-secondary: #9ca3af;\n --reflet-text-tertiary: #6b7280;\n --reflet-border: #374151;\n --reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 50%), 0 0 0 1px rgb(255 255 255 / 5%);\n }\n}\n\n[data-reflet-feedback][data-theme=\"dark\"] {\n --reflet-bg: #1f2937;\n --reflet-bg-secondary: #111827;\n --reflet-text: #f9fafb;\n --reflet-text-secondary: #9ca3af;\n --reflet-text-tertiary: #6b7280;\n --reflet-border: #374151;\n --reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 50%), 0 0 0 1px rgb(255 255 255 / 5%);\n}\n\n@keyframes reflet-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes reflet-fade-out {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n@keyframes reflet-slide-up {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n}\n\n@keyframes reflet-slide-down {\n from { transform: translateY(0); }\n to { transform: translateY(100%); }\n}\n\n@keyframes reflet-scale-in {\n from { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }\n to { opacity: 1; transform: translate(-50%, -50%) scale(1); }\n}\n\n@keyframes reflet-scale-out {\n from { opacity: 1; transform: translate(-50%, -50%) scale(1); }\n to { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }\n}\n\n@keyframes reflet-checkmark {\n 0% { stroke-dashoffset: 24; }\n 100% { stroke-dashoffset: 0; }\n}\n\n@keyframes reflet-spin {\n to { transform: rotate(360deg); }\n}\n\n.reflet-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: ${REFLET_Z_INDEX.dialogContent};\n margin: 0;\n border: 0;\n padding: 0;\n background: var(--reflet-bg);\n border-radius: var(--reflet-radius);\n box-shadow: var(--reflet-shadow);\n width: 480px;\n max-width: calc(100vw - 32px);\n max-height: calc(100dvh - 64px);\n overflow: hidden;\n overscroll-behavior: contain;\n font-family: var(--reflet-font);\n color: var(--reflet-text);\n animation: reflet-scale-in 200ms var(--reflet-ease);\n}\n\n.reflet-dialog::backdrop {\n z-index: ${REFLET_Z_INDEX.dialogOverlay};\n background: var(--reflet-scrim);\n animation: reflet-fade-in 150ms ease-out;\n}\n\n.reflet-dialog[data-closing=\"true\"] {\n animation: reflet-scale-out 150ms ease-out forwards;\n}\n\n.reflet-dialog[data-closing=\"true\"]::backdrop {\n animation: reflet-fade-out 150ms ease-out forwards;\n}\n\n@media (max-width: 640px) {\n .reflet-dialog {\n top: auto;\n left: 0;\n right: 0;\n bottom: 0;\n transform: none;\n width: 100%;\n max-width: 100%;\n border-radius: var(--reflet-radius) var(--reflet-radius) 0 0;\n max-height: 90dvh;\n padding-bottom: env(safe-area-inset-bottom);\n animation: reflet-slide-up 300ms var(--reflet-ease);\n }\n\n .reflet-dialog[data-closing=\"true\"] {\n animation: reflet-slide-down 200ms ease-out forwards;\n }\n}\n\n.reflet-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 0;\n}\n\n.reflet-title {\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n color: var(--reflet-text);\n line-height: 1.3;\n}\n\n.reflet-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n background: transparent;\n border-radius: 8px;\n color: var(--reflet-text-secondary);\n cursor: pointer;\n padding: 0;\n flex-shrink: 0;\n transition: background 150ms, color 150ms, transform 150ms var(--reflet-ease);\n}\n\n.reflet-close:hover {\n background: var(--reflet-bg-secondary);\n color: var(--reflet-text);\n}\n\n.reflet-close svg {\n width: 18px;\n height: 18px;\n}\n\n.reflet-form {\n padding: 20px 24px 24px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.reflet-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.reflet-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--reflet-text-secondary);\n}\n\n.reflet-input,\n.reflet-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--reflet-border);\n border-radius: var(--reflet-radius-sm);\n background: var(--reflet-bg);\n color: var(--reflet-text);\n font-family: var(--reflet-font);\n font-size: 14px;\n line-height: 1.5;\n outline: none;\n transition: border-color 150ms, box-shadow 150ms;\n box-sizing: border-box;\n}\n\n.reflet-input::placeholder,\n.reflet-textarea::placeholder {\n color: var(--reflet-text-tertiary);\n}\n\n.reflet-input:focus,\n.reflet-textarea:focus {\n border-color: var(--reflet-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--reflet-primary) 15%, transparent);\n}\n\n.reflet-textarea {\n resize: vertical;\n min-height: 100px;\n}\n\n[data-reflet-feedback] :focus-visible {\n outline: 2px solid var(--reflet-primary);\n outline-offset: 2px;\n}\n\n[data-reflet-feedback] button:active:not(:disabled) {\n transform: scale(0.97);\n}\n\n@media (pointer: coarse), (max-width: 640px) {\n .reflet-input,\n .reflet-textarea {\n font-size: 16px;\n }\n}\n${getFeedbackChromeCSS()}\n@media (prefers-reduced-motion: reduce) {\n [data-reflet-feedback] *,\n [data-reflet-feedback] *::before,\n [data-reflet-feedback] *::after,\n .reflet-dialog,\n .reflet-dialog::backdrop {\n animation-duration: 1ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 1ms !important;\n }\n\n [data-reflet-feedback] button:active:not(:disabled) {\n transform: none;\n }\n}\n`;\n}\n","import {\n type FormEvent,\n type RefObject,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { Reflet } from \"./client\";\nimport { RefletContext } from \"./react-context\";\nimport {\n AUTO_CLOSE_DELAY,\n DEFAULT_LABELS,\n type FeedbackDialogProps,\n} from \"./react-feedback-dialog-types\";\nimport { injectFeedbackStyles } from \"./react-feedback-styles\";\n\nconst CLOSE_ANIMATION_MS = 200;\n\nfunction openDialog(dialog: HTMLDialogElement) {\n if (typeof dialog.showModal === \"function\") {\n dialog.showModal();\n return;\n }\n dialog.open = true;\n}\n\nfunction closeDialog(dialog: HTMLDialogElement) {\n if (typeof dialog.close === \"function\") {\n dialog.close();\n return;\n }\n dialog.open = false;\n}\n\nfunction useModalDialog(\n open: boolean,\n dialogRef: RefObject<HTMLDialogElement | null>,\n titleInputRef: RefObject<HTMLInputElement | null>\n) {\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!(open && dialog) || dialog.open) {\n return;\n }\n openDialog(dialog);\n titleInputRef.current?.focus();\n return () => closeDialog(dialog);\n }, [open, dialogRef, titleInputRef]);\n}\n\nfunction useBackdropDismiss(\n open: boolean,\n dialogRef: RefObject<HTMLDialogElement | null>,\n onDismiss: () => void\n) {\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!(open && dialog)) {\n return;\n }\n const dismiss = (event: MouseEvent) => {\n if (event.target === dialog) {\n onDismiss();\n }\n };\n dialog.addEventListener(\"click\", dismiss);\n return () => dialog.removeEventListener(\"click\", dismiss);\n }, [open, dialogRef, onDismiss]);\n}\n\nfunction useScrollLock(open: boolean) {\n useEffect(() => {\n if (!open) {\n return;\n }\n const root = document.documentElement;\n const previousOverflow = root.style.overflow;\n const previousGutter = root.style.getPropertyValue(\"scrollbar-gutter\");\n root.style.setProperty(\"scrollbar-gutter\", \"stable\");\n root.style.overflow = \"hidden\";\n return () => {\n root.style.overflow = previousOverflow;\n if (previousGutter) {\n root.style.setProperty(\"scrollbar-gutter\", previousGutter);\n } else {\n root.style.removeProperty(\"scrollbar-gutter\");\n }\n };\n }, [open]);\n}\n\nfunction usePrimaryColor(\n primaryColor: string | undefined,\n dialogRef: RefObject<HTMLDialogElement | null>\n) {\n useEffect(() => {\n injectFeedbackStyles();\n }, []);\n\n useEffect(() => {\n if (!(primaryColor && dialogRef.current)) {\n return;\n }\n const host = dialogRef.current.closest(\"[data-reflet-feedback]\");\n if (host instanceof HTMLElement) {\n host.style.setProperty(\"--reflet-primary\", primaryColor);\n }\n }, [primaryColor, dialogRef]);\n}\n\nexport function useFeedbackDialog({\n open,\n onOpenChange,\n publicKey: publicKeyProp,\n baseUrl,\n user: userProp,\n userToken: userTokenProp,\n primaryColor,\n labels: labelsProp,\n onSubmit,\n onOpen,\n onClose,\n}: Omit<FeedbackDialogProps, \"theme\">) {\n const context = useContext(RefletContext);\n const publicKey = publicKeyProp ?? context?.publicKey;\n const user = userProp ?? context?.user;\n const userToken = userTokenProp ?? context?.userToken;\n\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n\n const [title, setTitle] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [email, setEmail] = useState(\"\");\n const [honeypot, setHoneypot] = useState(\"\");\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [isSuccess, setIsSuccess] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [isClosing, setIsClosing] = useState(false);\n\n const titleInputRef = useRef<HTMLInputElement>(null);\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n const isAnonymous = !(user || userToken);\n\n usePrimaryColor(primaryColor, dialogRef);\n useModalDialog(open, dialogRef, titleInputRef);\n useScrollLock(open);\n\n useEffect(() => {\n if (open) {\n onOpen?.();\n }\n }, [open, onOpen]);\n\n const resetForm = () => {\n setTitle(\"\");\n setDescription(\"\");\n setEmail(\"\");\n setHoneypot(\"\");\n setError(null);\n setIsSuccess(false);\n setIsSubmitting(false);\n };\n\n const handleClose = () => {\n setIsClosing(true);\n setTimeout(() => {\n setIsClosing(false);\n onOpenChange(false);\n onClose?.();\n resetForm();\n }, CLOSE_ANIMATION_MS);\n };\n\n useBackdropDismiss(open, dialogRef, handleClose);\n\n const buildDescription = () => {\n const body = description.trim() || \"No additional details provided.\";\n const contact = isAnonymous && email ? `\\n\\n---\\nContact: ${email}` : \"\";\n return `${body}${contact}`;\n };\n\n const handleSubmit = async (event: FormEvent) => {\n event.preventDefault();\n\n if (honeypot) {\n setIsSuccess(true);\n return;\n }\n\n if (!publicKey) {\n setError(\n \"Missing publicKey. Provide it as a prop or via RefletProvider.\"\n );\n return;\n }\n\n if (!title.trim()) {\n setError(labels.required);\n return;\n }\n\n setIsSubmitting(true);\n setError(null);\n\n try {\n const client = new Reflet({ baseUrl, publicKey, user, userToken });\n const result = await client.create({\n description: buildDescription(),\n title: title.trim(),\n });\n\n setIsSuccess(true);\n onSubmit?.(result);\n setTimeout(handleClose, AUTO_CLOSE_DELAY);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : \"Something went wrong. Please try again.\"\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return {\n description,\n dialogRef,\n email,\n error,\n handleClose,\n handleSubmit,\n honeypot,\n isAnonymous,\n isClosing,\n isSubmitting,\n isSuccess,\n labels,\n setDescription,\n setEmail,\n setError,\n setHoneypot,\n setTitle,\n title,\n titleInputRef,\n };\n}\n","import { useFeedbackDialog } from \"./react-feedback-dialog-hook\";\nimport type { FeedbackDialogProps } from \"./react-feedback-dialog-types\";\n\nexport type {\n FeedbackDialogLabels,\n FeedbackDialogProps,\n} from \"./react-feedback-dialog-types\";\n\nexport function FeedbackDialog({\n open,\n onOpenChange,\n publicKey,\n baseUrl,\n user,\n userToken,\n theme = \"auto\",\n primaryColor,\n labels: labelsProp,\n onSubmit,\n onOpen,\n onClose,\n}: FeedbackDialogProps) {\n const {\n labels,\n title,\n setTitle,\n description,\n setDescription,\n email,\n setEmail,\n honeypot,\n setHoneypot,\n isSubmitting,\n isSuccess,\n error,\n setError,\n isClosing,\n isAnonymous,\n titleInputRef,\n dialogRef,\n handleClose,\n handleSubmit,\n } = useFeedbackDialog({\n baseUrl,\n labels: labelsProp,\n onClose,\n onOpen,\n onOpenChange,\n onSubmit,\n open,\n primaryColor,\n publicKey,\n user,\n userToken,\n });\n\n if (!open) {\n return null;\n }\n\n return (\n <div data-reflet-feedback=\"\" data-theme={theme}>\n <dialog\n aria-label={labels.title}\n className=\"reflet-dialog\"\n data-closing={isClosing ? \"true\" : undefined}\n onCancel={(event) => {\n event.preventDefault();\n handleClose();\n }}\n ref={dialogRef}\n >\n {isSuccess ? (\n <div className=\"reflet-success\">\n <div className=\"reflet-success-icon\">\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2.5\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n <h2 className=\"reflet-success-title\">{labels.successTitle}</h2>\n <p className=\"reflet-success-text\">{labels.successMessage}</p>\n </div>\n ) : (\n <>\n <div className=\"reflet-header\">\n <h2 className=\"reflet-title\">{labels.title}</h2>\n <button\n aria-label={labels.close}\n className=\"reflet-close\"\n onClick={handleClose}\n type=\"button\"\n >\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n >\n <line x1=\"18\" x2=\"6\" y1=\"6\" y2=\"18\" />\n <line x1=\"6\" x2=\"18\" y1=\"6\" y2=\"18\" />\n </svg>\n </button>\n </div>\n\n <form className=\"reflet-form\" onSubmit={handleSubmit}>\n <div className=\"reflet-field\">\n <input\n aria-label=\"Feedback title\"\n autoComplete=\"off\"\n className=\"reflet-input\"\n maxLength={100}\n onChange={(e) => {\n setTitle(e.target.value);\n if (error) {\n setError(null);\n }\n }}\n placeholder={labels.titlePlaceholder}\n ref={titleInputRef}\n required\n type=\"text\"\n value={title}\n />\n </div>\n\n <div className=\"reflet-field\">\n <textarea\n aria-label=\"Feedback description\"\n className=\"reflet-textarea\"\n maxLength={2000}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={labels.descriptionPlaceholder}\n rows={3}\n value={description}\n />\n </div>\n\n {isAnonymous && (\n <div className=\"reflet-field\">\n <label className=\"reflet-label\" htmlFor=\"reflet-email\">\n {labels.emailLabel}\n </label>\n <input\n autoComplete=\"email\"\n className=\"reflet-input\"\n id=\"reflet-email\"\n onChange={(e) => setEmail(e.target.value)}\n placeholder={labels.emailPlaceholder}\n type=\"email\"\n value={email}\n />\n </div>\n )}\n\n <div aria-hidden=\"true\" className=\"reflet-hp\">\n <input\n autoComplete=\"off\"\n name=\"website\"\n onChange={(e) => setHoneypot(e.target.value)}\n tabIndex={-1}\n type=\"text\"\n value={honeypot}\n />\n </div>\n\n {error && <p className=\"reflet-error-msg\">{error}</p>}\n\n <div className=\"reflet-footer\">\n <button\n className=\"reflet-btn reflet-btn-secondary\"\n onClick={handleClose}\n type=\"button\"\n >\n {labels.cancel}\n </button>\n <button\n className=\"reflet-btn reflet-btn-primary\"\n disabled={isSubmitting || !title.trim()}\n type=\"submit\"\n >\n {isSubmitting ? (\n <span className=\"reflet-spinner\" />\n ) : (\n labels.submit\n )}\n </button>\n </div>\n </form>\n\n <div className=\"reflet-powered\">\n Powered by{\" \"}\n <a\n href=\"https://reflet.app\"\n rel=\"noopener noreferrer\"\n target=\"_blank\"\n >\n Reflet\n </a>\n </div>\n </>\n )}\n </dialog>\n </div>\n );\n}\n","import {\n cloneElement,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useCallback,\n useState,\n} from \"react\";\nimport {\n FeedbackDialog,\n type FeedbackDialogLabels,\n} from \"./react-feedback-dialog\";\nimport { injectFeedbackStyles } from \"./react-feedback-styles\";\nimport type { CreateFeedbackResponse, RefletUser } from \"./types\";\n\n// ============================================\n// Types\n// ============================================\n\nexport interface FeedbackButtonProps {\n /**\n * Render as child element instead of the default button.\n * When true, the first child element receives the onClick handler.\n */\n asChild?: boolean;\n /** API base URL override */\n baseUrl?: string;\n /** Custom trigger content or element (used with asChild) */\n children?: ReactNode;\n /** Custom CSS class for the default trigger button */\n className?: string;\n /** Custom labels / i18n */\n labels?: FeedbackDialogLabels & { trigger?: string };\n /** Called when dialog closes */\n onClose?: () => void;\n /** Called when dialog opens */\n onOpen?: () => void;\n /** Called after successful submission */\n onSubmit?: (result: CreateFeedbackResponse) => void;\n /** Primary brand color (CSS value) */\n primaryColor?: string;\n /** Public API key (falls back to RefletProvider context) */\n publicKey?: string;\n /** Color theme */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** User identification (falls back to RefletProvider context) */\n user?: RefletUser;\n /** Pre-signed user token */\n userToken?: string;\n}\n\n// ============================================\n// Feedback Icon SVG\n// ============================================\n\nfunction FeedbackIcon() {\n return (\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n height=\"16\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n );\n}\n\n// ============================================\n// Component\n// ============================================\n\nexport function FeedbackButton({\n publicKey,\n baseUrl,\n user,\n userToken,\n theme = \"auto\",\n primaryColor,\n labels,\n onSubmit,\n onOpen,\n onClose,\n className,\n asChild = false,\n children,\n}: FeedbackButtonProps) {\n const [open, setOpen] = useState(false);\n\n // Inject styles so the trigger button is styled\n useState(() => {\n injectFeedbackStyles();\n });\n\n const handleClick = useCallback(() => {\n setOpen(true);\n }, []);\n\n // Render trigger\n let trigger: ReactNode;\n\n if (asChild && isValidElement(children)) {\n trigger = cloneElement(children as ReactElement<{ onClick?: () => void }>, {\n onClick: handleClick,\n });\n } else {\n const triggerLabel = labels?.trigger ?? \"Feedback\";\n trigger = (\n <div data-reflet-feedback=\"\" data-theme={theme}>\n <button\n aria-label={triggerLabel}\n className={className ?? \"reflet-trigger\"}\n onClick={handleClick}\n style={\n primaryColor\n ? ({\n \"--reflet-primary\": primaryColor,\n \"--reflet-primary-hover\": primaryColor,\n } as React.CSSProperties)\n : undefined\n }\n type=\"button\"\n >\n <FeedbackIcon />\n {triggerLabel}\n </button>\n </div>\n );\n }\n\n return (\n <>\n {trigger}\n <FeedbackDialog\n baseUrl={baseUrl}\n labels={labels}\n onClose={onClose}\n onOpen={onOpen}\n onOpenChange={setOpen}\n onSubmit={onSubmit}\n open={open}\n primaryColor={primaryColor}\n publicKey={publicKey}\n theme={theme}\n user={user}\n userToken={userToken}\n />\n </>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport { useRefletClient } from \"./react-context\";\nimport type { UseMutationResult } from \"./react-hooks-types\";\nimport type { AddCommentParams, CreateFeedbackParams } from \"./types\";\n\n// ============================================\n// Generic mutation hook — single source of truth\n// ============================================\n\nfunction useRefletMutation<TData, TVariables>(\n mutateFn: (variables: TVariables) => Promise<TData>\n): UseMutationResult<TData, TVariables> {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<TData | undefined>();\n\n const mutate = useCallback(\n async (variables: TVariables) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await mutateFn(variables);\n setData(result);\n return result;\n } catch (err) {\n const wrapped =\n err instanceof Error ? err : new Error(\"Request failed\");\n setError(wrapped);\n throw wrapped;\n } finally {\n setIsLoading(false);\n }\n },\n [mutateFn]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { data, error, isLoading, mutate, reset };\n}\n\n// ============================================\n// Create Feedback Mutation Hook\n// ============================================\n\n/**\n * Hook to create new feedback\n *\n * @example\n * ```tsx\n * const { mutate: createFeedback, isLoading } = useCreateFeedback();\n *\n * const handleSubmit = async (data) => {\n * const result = await createFeedback(data);\n * console.log('Created:', result.feedbackId);\n * };\n * ```\n */\nexport function useCreateFeedback(): UseMutationResult<\n { feedbackId: string; isApproved: boolean },\n CreateFeedbackParams\n> {\n const client = useRefletClient();\n const mutateFn = useCallback(\n (params: CreateFeedbackParams) => client.create(params),\n [client]\n );\n return useRefletMutation(mutateFn);\n}\n\n// ============================================\n// Vote Mutation Hook\n// ============================================\n\n/**\n * Hook to vote on feedback\n *\n * @example\n * ```tsx\n * const { mutate: vote } = useVote();\n *\n * <button onClick={() => vote({ feedbackId: 'xxx' })}>\n * Vote\n * </button>\n * ```\n */\nexport function useVote(): UseMutationResult<\n { voted: boolean; voteCount: number },\n { feedbackId: string; type?: \"upvote\" | \"downvote\" }\n> {\n const client = useRefletClient();\n const mutateFn = useCallback(\n ({\n feedbackId,\n type = \"upvote\",\n }: {\n feedbackId: string;\n type?: \"upvote\" | \"downvote\";\n }) => client.vote(feedbackId, type),\n [client]\n );\n return useRefletMutation(mutateFn);\n}\n\n// ============================================\n// Comment Mutation Hook\n// ============================================\n\n/**\n * Hook to add a comment\n */\nexport function useAddComment(): UseMutationResult<\n { commentId: string },\n AddCommentParams\n> {\n const client = useRefletClient();\n const mutateFn = useCallback(\n (params: AddCommentParams) => client.comment(params),\n [client]\n );\n return useRefletMutation(mutateFn);\n}\n\n// ============================================\n// Subscribe Mutation Hook\n// ============================================\n\n/**\n * Hook to subscribe/unsubscribe to feedback\n */\nexport function useSubscription(): {\n subscribe: (feedbackId: string) => Promise<{ subscribed: boolean }>;\n unsubscribe: (feedbackId: string) => Promise<{ unsubscribed: boolean }>;\n isLoading: boolean;\n error: Error | null;\n} {\n const client = useRefletClient();\n const subscribeFn = useCallback(\n (feedbackId: string) => client.subscribe(feedbackId),\n [client]\n );\n const unsubscribeFn = useCallback(\n (feedbackId: string) => client.unsubscribe(feedbackId),\n [client]\n );\n const subscription = useRefletMutation(subscribeFn);\n const unsubscription = useRefletMutation(unsubscribeFn);\n const { mutate: runSubscribe, reset: resetSubscribe } = subscription;\n const { mutate: runUnsubscribe, reset: resetUnsubscribe } = unsubscription;\n\n const subscribe = useCallback(\n (feedbackId: string) => {\n resetUnsubscribe();\n return runSubscribe(feedbackId);\n },\n [resetUnsubscribe, runSubscribe]\n );\n\n const unsubscribe = useCallback(\n (feedbackId: string) => {\n resetSubscribe();\n return runUnsubscribe(feedbackId);\n },\n [resetSubscribe, runUnsubscribe]\n );\n\n return {\n error: subscription.error ?? unsubscription.error,\n isLoading: subscription.isLoading || unsubscription.isLoading,\n subscribe,\n unsubscribe,\n };\n}\n","import {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { useRefletClient } from \"./react-context\";\nimport type { UseQueryResult } from \"./react-hooks-types\";\nimport type {\n ChangelogEntry,\n Comment,\n FeedbackDetail,\n FeedbackItem,\n FeedbackListParams,\n OrganizationConfig,\n Roadmap,\n} from \"./types\";\n\n// ============================================\n// Generic query hook — single source of truth\n// ============================================\n\nfunction useRefletQuery<T>(\n fetchFn: () => Promise<T>,\n enabled = true\n): UseQueryResult<T> {\n const [data, setData] = useState<T | undefined>();\n const [isLoading, setIsLoading] = useState(enabled);\n const [error, setError] = useState<Error | null>(null);\n\n const refetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await fetchFn();\n setData(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(\"Request failed\"));\n } finally {\n setIsLoading(false);\n }\n }, [fetchFn]);\n\n useEffect(() => {\n if (enabled) {\n refetch();\n }\n }, [refetch, enabled]);\n\n return { data, error, isLoading, refetch };\n}\n\n// ============================================\n// Organization Config Hook\n// ============================================\n\nexport function useOrganizationConfig(): UseQueryResult<OrganizationConfig> {\n const client = useRefletClient();\n const fetchFn = useCallback(() => client.getConfig(), [client]);\n return useRefletQuery(fetchFn);\n}\n\n// ============================================\n// Feedback List Hook\n// ============================================\n\nexport interface UseFeedbackListOptions extends FeedbackListParams {\n enabled?: boolean;\n}\n\nexport function useFeedbackList(\n options: UseFeedbackListOptions = {}\n): UseQueryResult<{ items: FeedbackItem[]; total: number; hasMore: boolean }> {\n const client = useRefletClient();\n const {\n enabled = true,\n statusId,\n status,\n search,\n sortBy,\n limit,\n offset,\n } = options;\n\n const params = useMemo(\n () => ({ limit, offset, search, sortBy, status, statusId }),\n [statusId, status, search, sortBy, limit, offset]\n );\n\n const fetchFn = useCallback(() => client.list(params), [client, params]);\n return useRefletQuery(fetchFn, enabled);\n}\n\n// ============================================\n// Single Feedback Hook\n// ============================================\n\nexport function useFeedback(\n feedbackId: string | undefined\n): UseQueryResult<FeedbackDetail> {\n const client = useRefletClient();\n\n const fetchFn = useCallback(() => {\n if (!feedbackId) {\n return Promise.resolve(undefined as unknown as FeedbackDetail);\n }\n return client.get(feedbackId);\n }, [client, feedbackId]);\n\n return useRefletQuery(fetchFn, !!feedbackId);\n}\n\n// ============================================\n// Comments Hook\n// ============================================\n\nexport function useComments(\n feedbackId: string | undefined,\n sortBy: \"newest\" | \"oldest\" = \"oldest\"\n): UseQueryResult<Comment[]> {\n const client = useRefletClient();\n\n const fetchFn = useCallback(() => {\n if (!feedbackId) {\n return Promise.resolve(undefined as unknown as Comment[]);\n }\n return client.getComments(feedbackId, sortBy);\n }, [client, feedbackId, sortBy]);\n\n return useRefletQuery(fetchFn, !!feedbackId);\n}\n\n// ============================================\n// Roadmap Hook\n// ============================================\n\nexport function useRoadmap(): UseQueryResult<Roadmap> {\n const client = useRefletClient();\n const fetchFn = useCallback(() => client.getRoadmap(), [client]);\n return useRefletQuery(fetchFn);\n}\n\n// ============================================\n// Changelog Hook\n// ============================================\n\nexport function useChangelog(limit?: number): UseQueryResult<ChangelogEntry[]> {\n const client = useRefletClient();\n const fetchFn = useCallback(\n () => client.getChangelog(limit),\n [client, limit]\n );\n return useRefletQuery(fetchFn);\n}\n\n// ============================================\n// Unread Changelog Count Hook\n// ============================================\n\nconst CHANGELOG_STORAGE_KEY_PREFIX = \"reflet_changelog_seen_\";\n\nfunction subscribeToStorage(onStoreChange: () => void): () => void {\n window.addEventListener(\"storage\", onStoreChange);\n return () => window.removeEventListener(\"storage\", onStoreChange);\n}\n\nfunction readLastSeen(storageKey: string): number {\n try {\n const stored = localStorage.getItem(storageKey);\n return stored ? Number(stored) : 0;\n } catch {\n return 0;\n }\n}\n\nexport function useUnreadChangelogCount(publicKey: string): {\n unreadCount: number;\n markAsRead: () => void;\n} {\n const { data: entries } = useChangelog();\n const [readMark, setReadMark] = useState<{\n storageKey: string;\n timestamp: number;\n } | null>(null);\n\n const storageKey = `${CHANGELOG_STORAGE_KEY_PREFIX}${publicKey}`;\n const storedLastSeen = useSyncExternalStore(\n subscribeToStorage,\n () => readLastSeen(storageKey),\n () => 0\n );\n const markedLastSeen =\n readMark?.storageKey === storageKey ? readMark.timestamp : 0;\n const lastSeen = Math.max(storedLastSeen, markedLastSeen);\n\n let unreadCount = 0;\n for (const entry of entries ?? []) {\n if (entry.publishedAt && entry.publishedAt > lastSeen) {\n unreadCount++;\n }\n }\n\n const markAsRead = useCallback(() => {\n if (!entries || entries.length === 0) {\n return;\n }\n\n let latestTimestamp = 0;\n for (const entry of entries) {\n if (entry.publishedAt && entry.publishedAt > latestTimestamp) {\n latestTimestamp = entry.publishedAt;\n }\n }\n\n if (latestTimestamp > 0) {\n try {\n localStorage.setItem(storageKey, String(latestTimestamp));\n } catch {\n // localStorage unavailable\n }\n setReadMark({ storageKey, timestamp: latestTimestamp });\n }\n }, [entries, storageKey]);\n\n return { markAsRead, unreadCount };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"react.js","names":[],"sources":["../../survey-core/src/targeting.ts","../../survey-core/src/triggers.ts","../src/react-changelog-widget.tsx","../src/react-feedback-dialog-types.ts","../src/react-feedback-styles-parts.ts","../src/react-feedback-styles.ts","../src/react-feedback-dialog-hook.ts","../src/react-feedback-dialog.tsx","../src/react-feedback-button.tsx","../src/react-mutation-hooks.ts","../src/react-query-hooks.ts","../src/surveys/controller.ts","../src/surveys/survey-portal.tsx","../src/surveys/reflet-surveys.tsx","../src/surveys/use-reflet-surveys.ts"],"sourcesContent":["import { DEFAULT_RECONTACT_DAYS } from \"./defaults\";\nimport type { SurveyDisplay } from \"./types\";\n\nconst MS_PER_DAY = 24 * 60 * 60 * 1000;\nconst PERCENT = 100;\nconst FNV_OFFSET_BASIS = 0x81_1c_9d_c5;\nconst FNV_PRIME = 0x01_00_01_93;\nconst PATTERN_SEPARATOR = /[,\\n]/;\nconst REGEX_SPECIAL_CHARS = /[.+?^${}()|[\\]\\\\]/g;\nconst WILDCARD = /\\*/g;\nconst TRAILING_SLASHES = /\\/+$/;\n\nconst stripTrailingSlash = (path: string): string =>\n path.replace(TRAILING_SLASHES, \"\") || \"/\";\n\nconst patternToRegex = (pattern: string): RegExp => {\n const escaped = stripTrailingSlash(pattern)\n .replace(REGEX_SPECIAL_CHARS, \"\\\\$&\")\n .replace(WILDCARD, \".*\");\n return new RegExp(`^${escaped}/?$`, \"i\");\n};\n\nconst HASH_ROUTE_PREFIX = \"#/\";\nconst HASH_ROUTE_QUERY = /\\?.*$/;\n\nconst hashRoutePathOf = (url: URL): string[] =>\n url.hash.startsWith(HASH_ROUTE_PREFIX)\n ? [stripTrailingSlash(url.hash.slice(1).replace(HASH_ROUTE_QUERY, \"\"))]\n : [];\n\n/**\n * Comma- or newline-separated patterns with `*` wildcards. A pattern starting\n * with `/` matches the path (or a `#/` hash route); one with a scheme matches\n * the full URL; anything else matches host and path. Empty matches every page.\n */\nexport const matchesPageUrl = (\n patterns: string | undefined,\n href: string\n): boolean => {\n const list = (patterns ?? \"\")\n .split(PATTERN_SEPARATOR)\n .map((pattern) => pattern.trim())\n .filter(Boolean);\n if (list.length === 0) {\n return true;\n }\n let url: URL;\n try {\n url = new URL(href);\n } catch {\n return false;\n }\n const path = stripTrailingSlash(url.pathname);\n const paths = [path, ...hashRoutePathOf(url)];\n const candidatesFor = (pattern: string): string[] => {\n if (pattern.startsWith(\"/\")) {\n return paths;\n }\n return pattern.includes(\"://\")\n ? [`${url.origin}${path}`]\n : [`${url.host}${path}`];\n };\n return list.some((pattern) => {\n const regex = patternToRegex(pattern);\n return candidatesFor(pattern).some((candidate) => regex.test(candidate));\n });\n};\n\n/** Stable 0–99 bucket so one respondent is always in or out of a sample. */\nexport const sampleBucket = (key: string): number => {\n let hash = FNV_OFFSET_BASIS;\n for (let index = 0; index < key.length; index++) {\n // biome-ignore lint/suspicious/noBitwiseOperators: FNV-1a hashing is bitwise by definition\n hash ^= key.charCodeAt(index);\n // biome-ignore lint/suspicious/noBitwiseOperators: keeps the hash an unsigned 32-bit integer\n hash = Math.imul(hash, FNV_PRIME) >>> 0;\n }\n return hash % PERCENT;\n};\n\n/** Without a respondent key sampling can't be stable, so the respondent is let in. */\nexport const isSampledIn = (\n sampleRate: number | undefined,\n surveyId: string,\n respondentKey: string | undefined\n): boolean => {\n if (sampleRate === undefined || sampleRate >= PERCENT) {\n return true;\n }\n if (sampleRate <= 0) {\n return false;\n }\n if (!respondentKey) {\n return true;\n }\n return sampleBucket(`${surveyId}:${respondentKey}`) < sampleRate;\n};\n\nexport const isWithinSchedule = (\n schedule: { endsAt?: number; startsAt?: number },\n now: number\n): boolean =>\n !(\n (schedule.startsAt !== undefined && now < schedule.startsAt) ||\n (schedule.endsAt !== undefined && now > schedule.endsAt)\n );\n\nexport interface RespondentHistory {\n hasCompleted: boolean;\n lastShownAt?: number;\n}\n\n/** Whether the display frequency allows showing the survey to someone with this history. */\nexport const frequencyAllows = (\n display: SurveyDisplay,\n history: RespondentHistory,\n now: number\n): boolean => {\n if (history.lastShownAt === undefined) {\n return true;\n }\n const recontactWaitMs =\n (display.recontactDays ?? DEFAULT_RECONTACT_DAYS) * MS_PER_DAY;\n const waitedLongEnough = now - history.lastShownAt >= recontactWaitMs;\n switch (display.frequency) {\n case \"once\":\n return false;\n case \"until_completed\":\n return !history.hasCompleted && waitedLongEnough;\n case \"recurring\":\n return waitedLongEnough;\n default:\n return false;\n }\n};\n\nexport const NPS_PROMOTER_MIN = 9;\nexport const NPS_DETRACTOR_MAX = 6;\n\nexport interface NpsBreakdown {\n detractors: number;\n passives: number;\n promoters: number;\n /** −100 to 100, or null without any scores. */\n score: number | null;\n total: number;\n}\n\nexport const npsBreakdown = (scores: readonly number[]): NpsBreakdown => {\n const promoters = scores.filter((s) => s >= NPS_PROMOTER_MIN).length;\n const detractors = scores.filter((s) => s <= NPS_DETRACTOR_MAX).length;\n const total = scores.length;\n return {\n detractors,\n passives: total - promoters - detractors,\n promoters,\n score:\n total === 0\n ? null\n : Math.round(((promoters - detractors) / total) * PERCENT),\n total,\n };\n};\n","import { DEFAULT_TIME_DELAY_MS } from \"./defaults\";\nimport { matchesPageUrl } from \"./targeting\";\nimport type { PublicSurvey } from \"./types\";\n\nconst URL_POLL_INTERVAL_MS = 1000;\n\nexport interface SurveyTriggers {\n destroy: () => void;\n notifyFeedbackSubmitted: () => void;\n setSurveys: (surveys: readonly PublicSurvey[]) => void;\n track: (eventName: string) => void;\n}\n\nexport interface SurveyTriggerOptions {\n /** Return false when the survey can't be shown now; it stays armed. */\n onTrigger: (survey: PublicSurvey) => boolean;\n surveys: readonly PublicSurvey[];\n window?: Window;\n}\n\n/**\n * Arms every survey's trigger on the page: page visits (including client-side\n * navigation), time on page, exit intent, custom events and submitted feedback.\n * Each survey fires at most once per page session.\n */\nexport const createSurveyTriggers = (\n options: SurveyTriggerOptions\n): SurveyTriggers => {\n const win = options.window ?? window;\n const fired = new Set<string>();\n const timers = new Map<string, number>();\n let surveys = options.surveys;\n let lastHref = win.location.href;\n\n const onCurrentPage = (survey: PublicSurvey) =>\n matchesPageUrl(survey.triggerConfig?.pageUrl, win.location.href);\n\n const fire = (survey: PublicSurvey) => {\n if (fired.has(survey._id) || !onCurrentPage(survey)) {\n return;\n }\n if (options.onTrigger(survey)) {\n fired.add(survey._id);\n }\n };\n\n const clearTimers = () => {\n for (const timer of timers.values()) {\n win.clearTimeout(timer);\n }\n timers.clear();\n };\n\n const armPage = () => {\n clearTimers();\n for (const survey of surveys) {\n if (fired.has(survey._id) || !onCurrentPage(survey)) {\n continue;\n }\n if (survey.triggerType === \"page_visit\") {\n fire(survey);\n }\n if (survey.triggerType === \"time_delay\") {\n const delayMs = survey.triggerConfig?.delayMs ?? DEFAULT_TIME_DELAY_MS;\n timers.set(\n survey._id,\n win.setTimeout(() => fire(survey), delayMs)\n );\n }\n }\n };\n\n const fireAll = (matches: (survey: PublicSurvey) => boolean) => {\n for (const survey of surveys) {\n if (matches(survey)) {\n fire(survey);\n }\n }\n };\n\n const onMouseOut = (event: MouseEvent) => {\n const leftThroughTop = event.relatedTarget === null && event.clientY <= 0;\n if (leftThroughTop) {\n fireAll((survey) => survey.triggerType === \"exit_intent\");\n }\n };\n\n const onNavigation = () => {\n if (win.location.href === lastHref) {\n return;\n }\n lastHref = win.location.href;\n armPage();\n };\n\n win.document.addEventListener(\"mouseout\", onMouseOut);\n win.addEventListener(\"popstate\", onNavigation);\n win.addEventListener(\"hashchange\", onNavigation);\n const urlPoll = win.setInterval(onNavigation, URL_POLL_INTERVAL_MS);\n armPage();\n\n return {\n destroy: () => {\n clearTimers();\n win.clearInterval(urlPoll);\n win.document.removeEventListener(\"mouseout\", onMouseOut);\n win.removeEventListener(\"popstate\", onNavigation);\n win.removeEventListener(\"hashchange\", onNavigation);\n },\n notifyFeedbackSubmitted: () =>\n fireAll((survey) => survey.triggerType === \"feedback_submitted\"),\n setSurveys: (next) => {\n surveys = next;\n armPage();\n },\n track: (eventName) =>\n fireAll(\n (survey) =>\n survey.triggerType === \"event\" &&\n survey.triggerConfig?.eventName === eventName\n ),\n };\n};\n","import { useCallback, useEffect, useRef } from \"react\";\nimport type { ChangelogEntry } from \"./types\";\n\ninterface ChangelogWidgetProps {\n autoOpenForNew?: boolean;\n locale?: string;\n maxEntries?: number;\n mode?: \"card\" | \"popup\" | \"trigger\";\n onClose?: () => void;\n onEntryClick?: (entry: ChangelogEntry) => void;\n onOpen?: () => void;\n position?: \"bottom-right\" | \"bottom-left\";\n primaryColor?: string;\n publicKey: string;\n theme?: \"light\" | \"dark\" | \"auto\";\n triggerSelector?: string;\n}\n\ninterface WidgetInstance {\n close: () => void;\n destroy: () => void;\n getUnreadCount: () => number;\n init: () => Promise<void>;\n markAsRead: () => void;\n open: () => void;\n}\n\ndeclare global {\n interface Window {\n RefletChangelogWidget?: new (\n config: ChangelogWidgetProps\n ) => WidgetInstance;\n }\n}\n\n/**\n * React component that wraps the Reflet Changelog Widget.\n *\n * Dynamically loads and initializes a RefletChangelogWidget instance.\n *\n * @example\n * ```tsx\n * import { ChangelogWidget } from 'reflet-sdk/react';\n *\n * export function MyApp() {\n * return <ChangelogWidget publicKey=\"fb_pub_xxx\" mode=\"card\" theme=\"auto\" />;\n * }\n * ```\n */\nexport function ChangelogWidget({\n publicKey,\n mode = \"card\",\n position = \"bottom-right\",\n theme = \"light\",\n primaryColor,\n maxEntries,\n triggerSelector,\n autoOpenForNew,\n locale,\n onOpen,\n onClose,\n onEntryClick,\n}: ChangelogWidgetProps) {\n const widgetRef = useRef<WidgetInstance | null>(null);\n\n const initWidget = useCallback(async () => {\n // Check if RefletChangelogWidget is available globally\n const WidgetClass = window.RefletChangelogWidget;\n\n if (WidgetClass) {\n const instance = new WidgetClass({\n autoOpenForNew,\n locale,\n maxEntries,\n mode,\n onClose,\n onEntryClick,\n onOpen,\n position,\n primaryColor,\n publicKey,\n theme,\n triggerSelector,\n });\n\n widgetRef.current = instance;\n await instance.init();\n return;\n }\n\n // Try loading the script dynamically\n const existingScript = document.querySelector(\n 'script[src*=\"reflet-changelog\"]'\n );\n if (!existingScript) {\n const script = document.createElement(\"script\");\n script.src = \"https://cdn.reflet.app/widget/reflet-changelog.v1.js\";\n script.async = true;\n script.onload = () => {\n const LoadedClass = window.RefletChangelogWidget;\n\n if (LoadedClass) {\n const instance = new LoadedClass({\n autoOpenForNew,\n locale,\n maxEntries,\n mode,\n onClose,\n onEntryClick,\n onOpen,\n position,\n primaryColor,\n publicKey,\n theme,\n triggerSelector,\n });\n\n widgetRef.current = instance;\n instance.init();\n }\n };\n document.head.appendChild(script);\n }\n }, [\n publicKey,\n mode,\n position,\n theme,\n primaryColor,\n maxEntries,\n triggerSelector,\n autoOpenForNew,\n locale,\n onOpen,\n onClose,\n onEntryClick,\n ]);\n\n useEffect(() => {\n initWidget();\n\n return () => {\n if (widgetRef.current) {\n widgetRef.current.destroy();\n widgetRef.current = null;\n }\n };\n }, [initWidget]);\n\n // This component doesn't render visible DOM - the widget manages its own Shadow DOM\n return null;\n}\n","import type { CreateFeedbackResponse, RefletUser } from \"./types\";\n\nexport interface FeedbackDialogLabels {\n cancel?: string;\n close?: string;\n descriptionPlaceholder?: string;\n emailLabel?: string;\n emailPlaceholder?: string;\n required?: string;\n submit?: string;\n successMessage?: string;\n successTitle?: string;\n title?: string;\n titlePlaceholder?: string;\n}\n\nexport interface FeedbackDialogProps {\n /** API base URL override */\n baseUrl?: string;\n /** Custom labels / i18n */\n labels?: FeedbackDialogLabels;\n /** Called when dialog closes */\n onClose?: () => void;\n /** Called when dialog opens */\n onOpen?: () => void;\n /** Callback when open state changes */\n onOpenChange: (open: boolean) => void;\n /** Called after successful submission */\n onSubmit?: (result: CreateFeedbackResponse) => void;\n /** Whether the dialog is open */\n open: boolean;\n /** Primary brand color (CSS value) */\n primaryColor?: string;\n /** Public API key (falls back to RefletProvider context) */\n publicKey?: string;\n /** Color theme */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** User identification (falls back to RefletProvider context) */\n user?: RefletUser;\n /** Pre-signed user token */\n userToken?: string;\n}\n\nexport const DEFAULT_LABELS: Required<FeedbackDialogLabels> = {\n cancel: \"Cancel\",\n close: \"Close\",\n descriptionPlaceholder: \"Tell us more... (optional)\",\n emailLabel: \"Email\",\n emailPlaceholder: \"your@email.com (optional)\",\n required: \"Required\",\n submit: \"Send Feedback\",\n successMessage: \"Your feedback has been received. We appreciate your input.\",\n successTitle: \"Thank you!\",\n title: \"Send Feedback\",\n titlePlaceholder: \"What's on your mind?\",\n};\n\nexport const AUTO_CLOSE_DELAY = 2500;\n","export function getFeedbackChromeCSS(): string {\n return `\n.reflet-footer {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n padding-top: 4px;\n}\n\n.reflet-btn {\n min-height: 40px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--reflet-radius-sm);\n font-family: var(--reflet-font);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 150ms, color 150ms, opacity 150ms, transform 150ms var(--reflet-ease);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n line-height: 1;\n}\n\n.reflet-btn-primary {\n background: var(--reflet-primary);\n color: var(--reflet-on-primary);\n}\n\n.reflet-btn-primary:hover:not(:disabled) {\n background: var(--reflet-primary-hover);\n}\n\n.reflet-btn-primary:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.reflet-btn-secondary {\n background: transparent;\n color: var(--reflet-text-secondary);\n}\n\n.reflet-btn-secondary:hover {\n background: var(--reflet-bg-secondary);\n color: var(--reflet-text);\n}\n\n.reflet-spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--reflet-on-primary-soft);\n border-top-color: var(--reflet-on-primary);\n border-radius: 50%;\n animation: reflet-spin 600ms linear infinite;\n}\n\n.reflet-success {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n text-align: center;\n gap: 16px;\n}\n\n.reflet-success-icon {\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--reflet-success) 12%, transparent);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.reflet-success-icon svg {\n width: 28px;\n height: 28px;\n color: var(--reflet-success);\n}\n\n.reflet-success-icon svg path {\n stroke-dasharray: 24;\n stroke-dashoffset: 24;\n animation: reflet-checkmark 400ms ease-out 100ms forwards;\n}\n\n.reflet-success-title {\n font-size: 18px;\n font-weight: 600;\n color: var(--reflet-text);\n margin: 0;\n}\n\n.reflet-success-text {\n font-size: 14px;\n color: var(--reflet-text-secondary);\n margin: 0;\n}\n\n.reflet-error-msg {\n font-size: 13px;\n color: var(--reflet-error);\n margin: 0;\n}\n\n.reflet-trigger {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-height: 40px;\n padding: 10px 18px;\n background: var(--reflet-primary);\n color: var(--reflet-on-primary);\n border: none;\n border-radius: var(--reflet-radius-sm);\n font-family: var(--reflet-font);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 150ms, transform 150ms var(--reflet-ease);\n line-height: 1;\n}\n\n.reflet-trigger:hover {\n background: var(--reflet-primary-hover);\n}\n\n.reflet-trigger svg {\n width: 16px;\n height: 16px;\n}\n\n.reflet-hp {\n position: absolute;\n top: -9999px;\n left: -9999px;\n opacity: 0;\n height: 0;\n width: 0;\n z-index: -1;\n}\n\n.reflet-powered {\n text-align: center;\n padding: 0 24px 16px;\n font-size: 11px;\n color: var(--reflet-text-tertiary);\n}\n\n.reflet-powered a {\n color: var(--reflet-text-secondary);\n text-decoration: none;\n font-weight: 500;\n}\n\n.reflet-powered a:hover {\n color: var(--reflet-primary);\n}\n\n@media (max-width: 640px) {\n .reflet-header::before {\n content: '';\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translateX(-50%);\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background: var(--reflet-border);\n }\n\n .reflet-header {\n position: relative;\n padding-top: 24px;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .reflet-success-icon svg path {\n stroke-dasharray: none;\n stroke-dashoffset: 0;\n animation: none;\n }\n}\n`;\n}\n","import { getFeedbackChromeCSS } from \"./react-feedback-styles-parts\";\nimport { REFLET_Z_INDEX } from \"./z-index\";\n\nconst STYLE_ID = \"reflet-feedback-styles\";\n\nexport function injectFeedbackStyles(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n if (document.getElementById(STYLE_ID)) {\n return;\n }\n\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = getFeedbackCSS();\n document.head.appendChild(style);\n}\n\nfunction getFeedbackCSS(): string {\n return `\n:root {\n --reflet-scrim: rgb(0 0 0 / 45%);\n}\n\n[data-reflet-feedback] {\n --reflet-primary: #6366f1;\n --reflet-primary-hover: #4f46e5;\n --reflet-on-primary: #ffffff;\n --reflet-on-primary-soft: rgb(255 255 255 / 30%);\n --reflet-bg: #ffffff;\n --reflet-bg-secondary: #f9fafb;\n --reflet-text: #111827;\n --reflet-text-secondary: #6b7280;\n --reflet-text-tertiary: #9ca3af;\n --reflet-border: #e5e7eb;\n --reflet-radius: 12px;\n --reflet-radius-sm: 8px;\n --reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 25%), 0 0 0 1px rgb(0 0 0 / 5%);\n --reflet-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --reflet-success: #22c55e;\n --reflet-error: #ef4444;\n --reflet-ease: cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n@media (prefers-color-scheme: dark) {\n [data-reflet-feedback][data-theme=\"auto\"] {\n --reflet-bg: #1f2937;\n --reflet-bg-secondary: #111827;\n --reflet-text: #f9fafb;\n --reflet-text-secondary: #9ca3af;\n --reflet-text-tertiary: #6b7280;\n --reflet-border: #374151;\n --reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 50%), 0 0 0 1px rgb(255 255 255 / 5%);\n }\n}\n\n[data-reflet-feedback][data-theme=\"dark\"] {\n --reflet-bg: #1f2937;\n --reflet-bg-secondary: #111827;\n --reflet-text: #f9fafb;\n --reflet-text-secondary: #9ca3af;\n --reflet-text-tertiary: #6b7280;\n --reflet-border: #374151;\n --reflet-shadow: 0 20px 60px -12px rgb(0 0 0 / 50%), 0 0 0 1px rgb(255 255 255 / 5%);\n}\n\n@keyframes reflet-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes reflet-fade-out {\n from { opacity: 1; }\n to { opacity: 0; }\n}\n\n@keyframes reflet-slide-up {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n}\n\n@keyframes reflet-slide-down {\n from { transform: translateY(0); }\n to { transform: translateY(100%); }\n}\n\n@keyframes reflet-scale-in {\n from { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }\n to { opacity: 1; transform: translate(-50%, -50%) scale(1); }\n}\n\n@keyframes reflet-scale-out {\n from { opacity: 1; transform: translate(-50%, -50%) scale(1); }\n to { opacity: 0; transform: translate(-50%, -50%) scale(0.95); }\n}\n\n@keyframes reflet-checkmark {\n 0% { stroke-dashoffset: 24; }\n 100% { stroke-dashoffset: 0; }\n}\n\n@keyframes reflet-spin {\n to { transform: rotate(360deg); }\n}\n\n.reflet-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: ${REFLET_Z_INDEX.dialogContent};\n margin: 0;\n border: 0;\n padding: 0;\n background: var(--reflet-bg);\n border-radius: var(--reflet-radius);\n box-shadow: var(--reflet-shadow);\n width: 480px;\n max-width: calc(100vw - 32px);\n max-height: calc(100dvh - 64px);\n overflow: hidden;\n overscroll-behavior: contain;\n font-family: var(--reflet-font);\n color: var(--reflet-text);\n animation: reflet-scale-in 200ms var(--reflet-ease);\n}\n\n.reflet-dialog::backdrop {\n z-index: ${REFLET_Z_INDEX.dialogOverlay};\n background: var(--reflet-scrim);\n animation: reflet-fade-in 150ms ease-out;\n}\n\n.reflet-dialog[data-closing=\"true\"] {\n animation: reflet-scale-out 150ms ease-out forwards;\n}\n\n.reflet-dialog[data-closing=\"true\"]::backdrop {\n animation: reflet-fade-out 150ms ease-out forwards;\n}\n\n@media (max-width: 640px) {\n .reflet-dialog {\n top: auto;\n left: 0;\n right: 0;\n bottom: 0;\n transform: none;\n width: 100%;\n max-width: 100%;\n border-radius: var(--reflet-radius) var(--reflet-radius) 0 0;\n max-height: 90dvh;\n padding-bottom: env(safe-area-inset-bottom);\n animation: reflet-slide-up 300ms var(--reflet-ease);\n }\n\n .reflet-dialog[data-closing=\"true\"] {\n animation: reflet-slide-down 200ms ease-out forwards;\n }\n}\n\n.reflet-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px 0;\n}\n\n.reflet-title {\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n color: var(--reflet-text);\n line-height: 1.3;\n}\n\n.reflet-close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n background: transparent;\n border-radius: 8px;\n color: var(--reflet-text-secondary);\n cursor: pointer;\n padding: 0;\n flex-shrink: 0;\n transition: background 150ms, color 150ms, transform 150ms var(--reflet-ease);\n}\n\n.reflet-close:hover {\n background: var(--reflet-bg-secondary);\n color: var(--reflet-text);\n}\n\n.reflet-close svg {\n width: 18px;\n height: 18px;\n}\n\n.reflet-form {\n padding: 20px 24px 24px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.reflet-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.reflet-label {\n font-size: 13px;\n font-weight: 500;\n color: var(--reflet-text-secondary);\n}\n\n.reflet-input,\n.reflet-textarea {\n width: 100%;\n padding: 10px 12px;\n border: 1px solid var(--reflet-border);\n border-radius: var(--reflet-radius-sm);\n background: var(--reflet-bg);\n color: var(--reflet-text);\n font-family: var(--reflet-font);\n font-size: 14px;\n line-height: 1.5;\n outline: none;\n transition: border-color 150ms, box-shadow 150ms;\n box-sizing: border-box;\n}\n\n.reflet-input::placeholder,\n.reflet-textarea::placeholder {\n color: var(--reflet-text-tertiary);\n}\n\n.reflet-input:focus,\n.reflet-textarea:focus {\n border-color: var(--reflet-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--reflet-primary) 15%, transparent);\n}\n\n.reflet-textarea {\n resize: vertical;\n min-height: 100px;\n}\n\n[data-reflet-feedback] :focus-visible {\n outline: 2px solid var(--reflet-primary);\n outline-offset: 2px;\n}\n\n[data-reflet-feedback] button:active:not(:disabled) {\n transform: scale(0.97);\n}\n\n@media (pointer: coarse), (max-width: 640px) {\n .reflet-input,\n .reflet-textarea {\n font-size: 16px;\n }\n}\n${getFeedbackChromeCSS()}\n@media (prefers-reduced-motion: reduce) {\n [data-reflet-feedback] *,\n [data-reflet-feedback] *::before,\n [data-reflet-feedback] *::after,\n .reflet-dialog,\n .reflet-dialog::backdrop {\n animation-duration: 1ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 1ms !important;\n }\n\n [data-reflet-feedback] button:active:not(:disabled) {\n transform: none;\n }\n}\n`;\n}\n","import {\n type FormEvent,\n type RefObject,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { Reflet } from \"./client\";\nimport { RefletContext } from \"./react-context\";\nimport {\n AUTO_CLOSE_DELAY,\n DEFAULT_LABELS,\n type FeedbackDialogProps,\n} from \"./react-feedback-dialog-types\";\nimport { injectFeedbackStyles } from \"./react-feedback-styles\";\n\nconst CLOSE_ANIMATION_MS = 200;\n\nfunction openDialog(dialog: HTMLDialogElement) {\n if (typeof dialog.showModal === \"function\") {\n dialog.showModal();\n return;\n }\n dialog.open = true;\n}\n\nfunction closeDialog(dialog: HTMLDialogElement) {\n if (typeof dialog.close === \"function\") {\n dialog.close();\n return;\n }\n dialog.open = false;\n}\n\nfunction useModalDialog(\n open: boolean,\n dialogRef: RefObject<HTMLDialogElement | null>,\n titleInputRef: RefObject<HTMLInputElement | null>\n) {\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!(open && dialog) || dialog.open) {\n return;\n }\n openDialog(dialog);\n titleInputRef.current?.focus();\n return () => closeDialog(dialog);\n }, [open, dialogRef, titleInputRef]);\n}\n\nfunction useBackdropDismiss(\n open: boolean,\n dialogRef: RefObject<HTMLDialogElement | null>,\n onDismiss: () => void\n) {\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!(open && dialog)) {\n return;\n }\n const dismiss = (event: MouseEvent) => {\n if (event.target === dialog) {\n onDismiss();\n }\n };\n dialog.addEventListener(\"click\", dismiss);\n return () => dialog.removeEventListener(\"click\", dismiss);\n }, [open, dialogRef, onDismiss]);\n}\n\nfunction useScrollLock(open: boolean) {\n useEffect(() => {\n if (!open) {\n return;\n }\n const root = document.documentElement;\n const previousOverflow = root.style.overflow;\n const previousGutter = root.style.getPropertyValue(\"scrollbar-gutter\");\n root.style.setProperty(\"scrollbar-gutter\", \"stable\");\n root.style.overflow = \"hidden\";\n return () => {\n root.style.overflow = previousOverflow;\n if (previousGutter) {\n root.style.setProperty(\"scrollbar-gutter\", previousGutter);\n } else {\n root.style.removeProperty(\"scrollbar-gutter\");\n }\n };\n }, [open]);\n}\n\nfunction usePrimaryColor(\n primaryColor: string | undefined,\n dialogRef: RefObject<HTMLDialogElement | null>\n) {\n useEffect(() => {\n injectFeedbackStyles();\n }, []);\n\n useEffect(() => {\n if (!(primaryColor && dialogRef.current)) {\n return;\n }\n const host = dialogRef.current.closest(\"[data-reflet-feedback]\");\n if (host instanceof HTMLElement) {\n host.style.setProperty(\"--reflet-primary\", primaryColor);\n }\n }, [primaryColor, dialogRef]);\n}\n\nexport function useFeedbackDialog({\n open,\n onOpenChange,\n publicKey: publicKeyProp,\n baseUrl,\n user: userProp,\n userToken: userTokenProp,\n primaryColor,\n labels: labelsProp,\n onSubmit,\n onOpen,\n onClose,\n}: Omit<FeedbackDialogProps, \"theme\">) {\n const context = useContext(RefletContext);\n const publicKey = publicKeyProp ?? context?.publicKey;\n const user = userProp ?? context?.user;\n const userToken = userTokenProp ?? context?.userToken;\n\n const labels = { ...DEFAULT_LABELS, ...labelsProp };\n\n const [title, setTitle] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [email, setEmail] = useState(\"\");\n const [honeypot, setHoneypot] = useState(\"\");\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [isSuccess, setIsSuccess] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [isClosing, setIsClosing] = useState(false);\n\n const titleInputRef = useRef<HTMLInputElement>(null);\n const dialogRef = useRef<HTMLDialogElement>(null);\n\n const isAnonymous = !(user || userToken);\n\n usePrimaryColor(primaryColor, dialogRef);\n useModalDialog(open, dialogRef, titleInputRef);\n useScrollLock(open);\n\n useEffect(() => {\n if (open) {\n onOpen?.();\n }\n }, [open, onOpen]);\n\n const resetForm = () => {\n setTitle(\"\");\n setDescription(\"\");\n setEmail(\"\");\n setHoneypot(\"\");\n setError(null);\n setIsSuccess(false);\n setIsSubmitting(false);\n };\n\n const handleClose = () => {\n setIsClosing(true);\n setTimeout(() => {\n setIsClosing(false);\n onOpenChange(false);\n onClose?.();\n resetForm();\n }, CLOSE_ANIMATION_MS);\n };\n\n useBackdropDismiss(open, dialogRef, handleClose);\n\n const buildDescription = () => {\n const body = description.trim() || \"No additional details provided.\";\n const contact = isAnonymous && email ? `\\n\\n---\\nContact: ${email}` : \"\";\n return `${body}${contact}`;\n };\n\n const handleSubmit = async (event: FormEvent) => {\n event.preventDefault();\n\n if (honeypot) {\n setIsSuccess(true);\n return;\n }\n\n if (!publicKey) {\n setError(\n \"Missing publicKey. Provide it as a prop or via RefletProvider.\"\n );\n return;\n }\n\n if (!title.trim()) {\n setError(labels.required);\n return;\n }\n\n setIsSubmitting(true);\n setError(null);\n\n try {\n const client = new Reflet({ baseUrl, publicKey, user, userToken });\n const result = await client.create({\n description: buildDescription(),\n title: title.trim(),\n });\n\n setIsSuccess(true);\n onSubmit?.(result);\n setTimeout(handleClose, AUTO_CLOSE_DELAY);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : \"Something went wrong. Please try again.\"\n );\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return {\n description,\n dialogRef,\n email,\n error,\n handleClose,\n handleSubmit,\n honeypot,\n isAnonymous,\n isClosing,\n isSubmitting,\n isSuccess,\n labels,\n setDescription,\n setEmail,\n setError,\n setHoneypot,\n setTitle,\n title,\n titleInputRef,\n };\n}\n","import { useFeedbackDialog } from \"./react-feedback-dialog-hook\";\nimport type { FeedbackDialogProps } from \"./react-feedback-dialog-types\";\n\nexport type {\n FeedbackDialogLabels,\n FeedbackDialogProps,\n} from \"./react-feedback-dialog-types\";\n\nexport function FeedbackDialog({\n open,\n onOpenChange,\n publicKey,\n baseUrl,\n user,\n userToken,\n theme = \"auto\",\n primaryColor,\n labels: labelsProp,\n onSubmit,\n onOpen,\n onClose,\n}: FeedbackDialogProps) {\n const {\n labels,\n title,\n setTitle,\n description,\n setDescription,\n email,\n setEmail,\n honeypot,\n setHoneypot,\n isSubmitting,\n isSuccess,\n error,\n setError,\n isClosing,\n isAnonymous,\n titleInputRef,\n dialogRef,\n handleClose,\n handleSubmit,\n } = useFeedbackDialog({\n baseUrl,\n labels: labelsProp,\n onClose,\n onOpen,\n onOpenChange,\n onSubmit,\n open,\n primaryColor,\n publicKey,\n user,\n userToken,\n });\n\n if (!open) {\n return null;\n }\n\n return (\n <div data-reflet-feedback=\"\" data-theme={theme}>\n <dialog\n aria-label={labels.title}\n className=\"reflet-dialog\"\n data-closing={isClosing ? \"true\" : undefined}\n onCancel={(event) => {\n event.preventDefault();\n handleClose();\n }}\n ref={dialogRef}\n >\n {isSuccess ? (\n <div className=\"reflet-success\">\n <div className=\"reflet-success-icon\">\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2.5\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n <h2 className=\"reflet-success-title\">{labels.successTitle}</h2>\n <p className=\"reflet-success-text\">{labels.successMessage}</p>\n </div>\n ) : (\n <>\n <div className=\"reflet-header\">\n <h2 className=\"reflet-title\">{labels.title}</h2>\n <button\n aria-label={labels.close}\n className=\"reflet-close\"\n onClick={handleClose}\n type=\"button\"\n >\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n >\n <line x1=\"18\" x2=\"6\" y1=\"6\" y2=\"18\" />\n <line x1=\"6\" x2=\"18\" y1=\"6\" y2=\"18\" />\n </svg>\n </button>\n </div>\n\n <form className=\"reflet-form\" onSubmit={handleSubmit}>\n <div className=\"reflet-field\">\n <input\n aria-label=\"Feedback title\"\n autoComplete=\"off\"\n className=\"reflet-input\"\n maxLength={100}\n onChange={(e) => {\n setTitle(e.target.value);\n if (error) {\n setError(null);\n }\n }}\n placeholder={labels.titlePlaceholder}\n ref={titleInputRef}\n required\n type=\"text\"\n value={title}\n />\n </div>\n\n <div className=\"reflet-field\">\n <textarea\n aria-label=\"Feedback description\"\n className=\"reflet-textarea\"\n maxLength={2000}\n onChange={(e) => setDescription(e.target.value)}\n placeholder={labels.descriptionPlaceholder}\n rows={3}\n value={description}\n />\n </div>\n\n {isAnonymous && (\n <div className=\"reflet-field\">\n <label className=\"reflet-label\" htmlFor=\"reflet-email\">\n {labels.emailLabel}\n </label>\n <input\n autoComplete=\"email\"\n className=\"reflet-input\"\n id=\"reflet-email\"\n onChange={(e) => setEmail(e.target.value)}\n placeholder={labels.emailPlaceholder}\n type=\"email\"\n value={email}\n />\n </div>\n )}\n\n <div aria-hidden=\"true\" className=\"reflet-hp\">\n <input\n autoComplete=\"off\"\n name=\"website\"\n onChange={(e) => setHoneypot(e.target.value)}\n tabIndex={-1}\n type=\"text\"\n value={honeypot}\n />\n </div>\n\n {error && <p className=\"reflet-error-msg\">{error}</p>}\n\n <div className=\"reflet-footer\">\n <button\n className=\"reflet-btn reflet-btn-secondary\"\n onClick={handleClose}\n type=\"button\"\n >\n {labels.cancel}\n </button>\n <button\n className=\"reflet-btn reflet-btn-primary\"\n disabled={isSubmitting || !title.trim()}\n type=\"submit\"\n >\n {isSubmitting ? (\n <span className=\"reflet-spinner\" />\n ) : (\n labels.submit\n )}\n </button>\n </div>\n </form>\n\n <div className=\"reflet-powered\">\n Powered by{\" \"}\n <a\n href=\"https://reflet.app\"\n rel=\"noopener noreferrer\"\n target=\"_blank\"\n >\n Reflet\n </a>\n </div>\n </>\n )}\n </dialog>\n </div>\n );\n}\n","import {\n cloneElement,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useCallback,\n useState,\n} from \"react\";\nimport {\n FeedbackDialog,\n type FeedbackDialogLabels,\n} from \"./react-feedback-dialog\";\nimport { injectFeedbackStyles } from \"./react-feedback-styles\";\nimport type { CreateFeedbackResponse, RefletUser } from \"./types\";\n\n// ============================================\n// Types\n// ============================================\n\nexport interface FeedbackButtonProps {\n /**\n * Render as child element instead of the default button.\n * When true, the first child element receives the onClick handler.\n */\n asChild?: boolean;\n /** API base URL override */\n baseUrl?: string;\n /** Custom trigger content or element (used with asChild) */\n children?: ReactNode;\n /** Custom CSS class for the default trigger button */\n className?: string;\n /** Custom labels / i18n */\n labels?: FeedbackDialogLabels & { trigger?: string };\n /** Called when dialog closes */\n onClose?: () => void;\n /** Called when dialog opens */\n onOpen?: () => void;\n /** Called after successful submission */\n onSubmit?: (result: CreateFeedbackResponse) => void;\n /** Primary brand color (CSS value) */\n primaryColor?: string;\n /** Public API key (falls back to RefletProvider context) */\n publicKey?: string;\n /** Color theme */\n theme?: \"light\" | \"dark\" | \"auto\";\n /** User identification (falls back to RefletProvider context) */\n user?: RefletUser;\n /** Pre-signed user token */\n userToken?: string;\n}\n\n// ============================================\n// Feedback Icon SVG\n// ============================================\n\nfunction FeedbackIcon() {\n return (\n <svg\n aria-hidden=\"true\"\n fill=\"none\"\n height=\"16\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n width=\"16\"\n >\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\n </svg>\n );\n}\n\n// ============================================\n// Component\n// ============================================\n\nexport function FeedbackButton({\n publicKey,\n baseUrl,\n user,\n userToken,\n theme = \"auto\",\n primaryColor,\n labels,\n onSubmit,\n onOpen,\n onClose,\n className,\n asChild = false,\n children,\n}: FeedbackButtonProps) {\n const [open, setOpen] = useState(false);\n\n // Inject styles so the trigger button is styled\n useState(() => {\n injectFeedbackStyles();\n });\n\n const handleClick = useCallback(() => {\n setOpen(true);\n }, []);\n\n // Render trigger\n let trigger: ReactNode;\n\n if (asChild && isValidElement(children)) {\n trigger = cloneElement(children as ReactElement<{ onClick?: () => void }>, {\n onClick: handleClick,\n });\n } else {\n const triggerLabel = labels?.trigger ?? \"Feedback\";\n trigger = (\n <div data-reflet-feedback=\"\" data-theme={theme}>\n <button\n aria-label={triggerLabel}\n className={className ?? \"reflet-trigger\"}\n onClick={handleClick}\n style={\n primaryColor\n ? ({\n \"--reflet-primary\": primaryColor,\n \"--reflet-primary-hover\": primaryColor,\n } as React.CSSProperties)\n : undefined\n }\n type=\"button\"\n >\n <FeedbackIcon />\n {triggerLabel}\n </button>\n </div>\n );\n }\n\n return (\n <>\n {trigger}\n <FeedbackDialog\n baseUrl={baseUrl}\n labels={labels}\n onClose={onClose}\n onOpen={onOpen}\n onOpenChange={setOpen}\n onSubmit={onSubmit}\n open={open}\n primaryColor={primaryColor}\n publicKey={publicKey}\n theme={theme}\n user={user}\n userToken={userToken}\n />\n </>\n );\n}\n","import { useCallback, useState } from \"react\";\nimport { useRefletClient } from \"./react-context\";\nimport type { UseMutationResult } from \"./react-hooks-types\";\nimport type { AddCommentParams, CreateFeedbackParams } from \"./types\";\n\n// ============================================\n// Generic mutation hook — single source of truth\n// ============================================\n\nfunction useRefletMutation<TData, TVariables>(\n mutateFn: (variables: TVariables) => Promise<TData>\n): UseMutationResult<TData, TVariables> {\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<TData | undefined>();\n\n const mutate = useCallback(\n async (variables: TVariables) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await mutateFn(variables);\n setData(result);\n return result;\n } catch (err) {\n const wrapped =\n err instanceof Error ? err : new Error(\"Request failed\");\n setError(wrapped);\n throw wrapped;\n } finally {\n setIsLoading(false);\n }\n },\n [mutateFn]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { data, error, isLoading, mutate, reset };\n}\n\n// ============================================\n// Create Feedback Mutation Hook\n// ============================================\n\n/**\n * Hook to create new feedback\n *\n * @example\n * ```tsx\n * const { mutate: createFeedback, isLoading } = useCreateFeedback();\n *\n * const handleSubmit = async (data) => {\n * const result = await createFeedback(data);\n * console.log('Created:', result.feedbackId);\n * };\n * ```\n */\nexport function useCreateFeedback(): UseMutationResult<\n { feedbackId: string; isApproved: boolean },\n CreateFeedbackParams\n> {\n const client = useRefletClient();\n const mutateFn = useCallback(\n (params: CreateFeedbackParams) => client.create(params),\n [client]\n );\n return useRefletMutation(mutateFn);\n}\n\n// ============================================\n// Vote Mutation Hook\n// ============================================\n\n/**\n * Hook to vote on feedback\n *\n * @example\n * ```tsx\n * const { mutate: vote } = useVote();\n *\n * <button onClick={() => vote({ feedbackId: 'xxx' })}>\n * Vote\n * </button>\n * ```\n */\nexport function useVote(): UseMutationResult<\n { voted: boolean; voteCount: number },\n { feedbackId: string; type?: \"upvote\" | \"downvote\" }\n> {\n const client = useRefletClient();\n const mutateFn = useCallback(\n ({\n feedbackId,\n type = \"upvote\",\n }: {\n feedbackId: string;\n type?: \"upvote\" | \"downvote\";\n }) => client.vote(feedbackId, type),\n [client]\n );\n return useRefletMutation(mutateFn);\n}\n\n// ============================================\n// Comment Mutation Hook\n// ============================================\n\n/**\n * Hook to add a comment\n */\nexport function useAddComment(): UseMutationResult<\n { commentId: string },\n AddCommentParams\n> {\n const client = useRefletClient();\n const mutateFn = useCallback(\n (params: AddCommentParams) => client.comment(params),\n [client]\n );\n return useRefletMutation(mutateFn);\n}\n\n// ============================================\n// Subscribe Mutation Hook\n// ============================================\n\n/**\n * Hook to subscribe/unsubscribe to feedback\n */\nexport function useSubscription(): {\n subscribe: (feedbackId: string) => Promise<{ subscribed: boolean }>;\n unsubscribe: (feedbackId: string) => Promise<{ unsubscribed: boolean }>;\n isLoading: boolean;\n error: Error | null;\n} {\n const client = useRefletClient();\n const subscribeFn = useCallback(\n (feedbackId: string) => client.subscribe(feedbackId),\n [client]\n );\n const unsubscribeFn = useCallback(\n (feedbackId: string) => client.unsubscribe(feedbackId),\n [client]\n );\n const subscription = useRefletMutation(subscribeFn);\n const unsubscription = useRefletMutation(unsubscribeFn);\n const { mutate: runSubscribe, reset: resetSubscribe } = subscription;\n const { mutate: runUnsubscribe, reset: resetUnsubscribe } = unsubscription;\n\n const subscribe = useCallback(\n (feedbackId: string) => {\n resetUnsubscribe();\n return runSubscribe(feedbackId);\n },\n [resetUnsubscribe, runSubscribe]\n );\n\n const unsubscribe = useCallback(\n (feedbackId: string) => {\n resetSubscribe();\n return runUnsubscribe(feedbackId);\n },\n [resetSubscribe, runUnsubscribe]\n );\n\n return {\n error: subscription.error ?? unsubscription.error,\n isLoading: subscription.isLoading || unsubscription.isLoading,\n subscribe,\n unsubscribe,\n };\n}\n","import {\n useCallback,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { useRefletClient } from \"./react-context\";\nimport type { UseQueryResult } from \"./react-hooks-types\";\nimport type {\n ChangelogEntry,\n Comment,\n FeedbackDetail,\n FeedbackItem,\n FeedbackListParams,\n OrganizationConfig,\n Roadmap,\n} from \"./types\";\n\n// ============================================\n// Generic query hook — single source of truth\n// ============================================\n\nfunction useRefletQuery<T>(\n fetchFn: () => Promise<T>,\n enabled = true\n): UseQueryResult<T> {\n const [data, setData] = useState<T | undefined>();\n const [isLoading, setIsLoading] = useState(enabled);\n const [error, setError] = useState<Error | null>(null);\n\n const refetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await fetchFn();\n setData(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(\"Request failed\"));\n } finally {\n setIsLoading(false);\n }\n }, [fetchFn]);\n\n useEffect(() => {\n if (enabled) {\n refetch();\n }\n }, [refetch, enabled]);\n\n return { data, error, isLoading, refetch };\n}\n\n// ============================================\n// Organization Config Hook\n// ============================================\n\nexport function useOrganizationConfig(): UseQueryResult<OrganizationConfig> {\n const client = useRefletClient();\n const fetchFn = useCallback(() => client.getConfig(), [client]);\n return useRefletQuery(fetchFn);\n}\n\n// ============================================\n// Feedback List Hook\n// ============================================\n\nexport interface UseFeedbackListOptions extends FeedbackListParams {\n enabled?: boolean;\n}\n\nexport function useFeedbackList(\n options: UseFeedbackListOptions = {}\n): UseQueryResult<{ items: FeedbackItem[]; total: number; hasMore: boolean }> {\n const client = useRefletClient();\n const {\n enabled = true,\n statusId,\n status,\n search,\n sortBy,\n limit,\n offset,\n } = options;\n\n const params = useMemo(\n () => ({ limit, offset, search, sortBy, status, statusId }),\n [statusId, status, search, sortBy, limit, offset]\n );\n\n const fetchFn = useCallback(() => client.list(params), [client, params]);\n return useRefletQuery(fetchFn, enabled);\n}\n\n// ============================================\n// Single Feedback Hook\n// ============================================\n\nexport function useFeedback(\n feedbackId: string | undefined\n): UseQueryResult<FeedbackDetail> {\n const client = useRefletClient();\n\n const fetchFn = useCallback(() => {\n if (!feedbackId) {\n return Promise.resolve(undefined as unknown as FeedbackDetail);\n }\n return client.get(feedbackId);\n }, [client, feedbackId]);\n\n return useRefletQuery(fetchFn, !!feedbackId);\n}\n\n// ============================================\n// Comments Hook\n// ============================================\n\nexport function useComments(\n feedbackId: string | undefined,\n sortBy: \"newest\" | \"oldest\" = \"oldest\"\n): UseQueryResult<Comment[]> {\n const client = useRefletClient();\n\n const fetchFn = useCallback(() => {\n if (!feedbackId) {\n return Promise.resolve(undefined as unknown as Comment[]);\n }\n return client.getComments(feedbackId, sortBy);\n }, [client, feedbackId, sortBy]);\n\n return useRefletQuery(fetchFn, !!feedbackId);\n}\n\n// ============================================\n// Roadmap Hook\n// ============================================\n\nexport function useRoadmap(): UseQueryResult<Roadmap> {\n const client = useRefletClient();\n const fetchFn = useCallback(() => client.getRoadmap(), [client]);\n return useRefletQuery(fetchFn);\n}\n\n// ============================================\n// Changelog Hook\n// ============================================\n\nexport function useChangelog(limit?: number): UseQueryResult<ChangelogEntry[]> {\n const client = useRefletClient();\n const fetchFn = useCallback(\n () => client.getChangelog(limit),\n [client, limit]\n );\n return useRefletQuery(fetchFn);\n}\n\n// ============================================\n// Unread Changelog Count Hook\n// ============================================\n\nconst CHANGELOG_STORAGE_KEY_PREFIX = \"reflet_changelog_seen_\";\n\nfunction subscribeToStorage(onStoreChange: () => void): () => void {\n window.addEventListener(\"storage\", onStoreChange);\n return () => window.removeEventListener(\"storage\", onStoreChange);\n}\n\nfunction readLastSeen(storageKey: string): number {\n try {\n const stored = localStorage.getItem(storageKey);\n return stored ? Number(stored) : 0;\n } catch {\n return 0;\n }\n}\n\nexport function useUnreadChangelogCount(publicKey: string): {\n unreadCount: number;\n markAsRead: () => void;\n} {\n const { data: entries } = useChangelog();\n const [readMark, setReadMark] = useState<{\n storageKey: string;\n timestamp: number;\n } | null>(null);\n\n const storageKey = `${CHANGELOG_STORAGE_KEY_PREFIX}${publicKey}`;\n const storedLastSeen = useSyncExternalStore(\n subscribeToStorage,\n () => readLastSeen(storageKey),\n () => 0\n );\n const markedLastSeen =\n readMark?.storageKey === storageKey ? readMark.timestamp : 0;\n const lastSeen = Math.max(storedLastSeen, markedLastSeen);\n\n let unreadCount = 0;\n for (const entry of entries ?? []) {\n if (entry.publishedAt && entry.publishedAt > lastSeen) {\n unreadCount++;\n }\n }\n\n const markAsRead = useCallback(() => {\n if (!entries || entries.length === 0) {\n return;\n }\n\n let latestTimestamp = 0;\n for (const entry of entries) {\n if (entry.publishedAt && entry.publishedAt > latestTimestamp) {\n latestTimestamp = entry.publishedAt;\n }\n }\n\n if (latestTimestamp > 0) {\n try {\n localStorage.setItem(storageKey, String(latestTimestamp));\n } catch {\n // localStorage unavailable\n }\n setReadMark({ storageKey, timestamp: latestTimestamp });\n }\n }, [entries, storageKey]);\n\n return { markAsRead, unreadCount };\n}\n","import type { PublicSurvey } from \"@reflet/survey-core\";\nimport {\n createSurveyTriggers,\n SurveySession,\n type SurveySessionCallbacks,\n type SurveyTriggers,\n} from \"@reflet/survey-core/client\";\nimport { DEFAULT_API_URL, type Reflet } from \"../client\";\nimport { createRefletSurveyTransport } from \"./client-transport\";\nimport { surveyControllers, surveyRegistryKey } from \"./registry\";\n\ntype PendingCall =\n | { eventName: string; kind: \"track\" }\n | { kind: \"feedback_submitted\" }\n | { kind: \"show\"; surveyId: string };\n\nexport interface RefletSurveysCallbacks {\n onSurveyAnswer?: SurveySessionCallbacks[\"onAnswer\"];\n onSurveyComplete?: SurveySessionCallbacks[\"onComplete\"];\n onSurveyDismiss?: SurveySessionCallbacks[\"onDismiss\"];\n onSurveyStart?: SurveySessionCallbacks[\"onStart\"];\n}\n\nconst MAX_PENDING_CALLS = 50;\n\n/**\n * Owns in-app delivery for one Reflet project: eligible surveys, armed\n * triggers and the survey on screen. Lives outside React so signals sent\n * before `<RefletSurveys />` has loaded surveys are queued, not lost.\n */\nexport class SurveyController {\n callbacks: RefletSurveysCallbacks = {};\n private active: SurveySession | null = null;\n private client: Reflet | null = null;\n private generation = 0;\n private readonly listeners = new Set<() => void>();\n private pending: PendingCall[] = [];\n private surveys: PublicSurvey[] | null = null;\n private triggers: SurveyTriggers | null = null;\n\n readonly subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n };\n\n readonly getActiveSession = (): SurveySession | null => this.active;\n\n attach(client: Reflet): () => void {\n this.generation += 1;\n const generation = this.generation;\n this.client = client;\n this.triggers = createSurveyTriggers({\n onTrigger: (survey) => this.activate(survey),\n surveys: [],\n });\n this.refresh();\n return () => {\n if (generation !== this.generation) {\n return;\n }\n this.generation += 1;\n this.triggers?.destroy();\n this.triggers = null;\n this.client = null;\n this.surveys = null;\n this.active?.dismiss();\n this.setActive(null);\n };\n }\n\n track(eventName: string): void {\n if (this.triggers && this.surveys) {\n this.triggers.track(eventName);\n return;\n }\n this.enqueue({ eventName, kind: \"track\" });\n }\n\n notifyFeedbackSubmitted(): void {\n if (this.triggers && this.surveys) {\n this.triggers.notifyFeedbackSubmitted();\n return;\n }\n this.enqueue({ kind: \"feedback_submitted\" });\n }\n\n showSurvey(surveyId: string): void {\n if (!this.surveys) {\n this.enqueue({ kind: \"show\", surveyId });\n return;\n }\n const survey = this.surveys.find((candidate) => candidate._id === surveyId);\n if (survey) {\n this.activate(survey);\n }\n }\n\n dismissSurvey(): void {\n this.active?.dismiss();\n }\n\n private async refresh(): Promise<void> {\n const client = this.client;\n const generation = this.generation;\n if (!client) {\n return;\n }\n let surveys: PublicSurvey[];\n try {\n surveys = await client.getEligibleSurveys();\n } catch {\n surveys = [];\n }\n if (generation !== this.generation) {\n return;\n }\n this.surveys = surveys;\n this.triggers?.setSurveys(surveys);\n const pending = this.pending;\n this.pending = [];\n for (const call of pending) {\n if (call.kind === \"track\") {\n this.track(call.eventName);\n } else if (call.kind === \"feedback_submitted\") {\n this.notifyFeedbackSubmitted();\n } else {\n this.showSurvey(call.surveyId);\n }\n }\n }\n\n private enqueue(call: PendingCall): void {\n this.pending = [...this.pending, call].slice(-MAX_PENDING_CALLS);\n }\n\n private activate(survey: PublicSurvey): boolean {\n const client = this.client;\n if (this.active || !client) {\n return false;\n }\n const session = new SurveySession({\n callbacks: {\n onAnswer: (event) => this.callbacks.onSurveyAnswer?.(event),\n onComplete: (event) => this.callbacks.onSurveyComplete?.(event),\n onDismiss: (event) => this.callbacks.onSurveyDismiss?.(event),\n onStart: (event) => this.callbacks.onSurveyStart?.(event),\n },\n survey,\n transport: createRefletSurveyTransport(client),\n });\n const unsubscribe = session.subscribe(() => {\n if (session.getSnapshot().phase !== \"dismissed\") {\n return;\n }\n unsubscribe();\n if (this.active === session) {\n this.setActive(null);\n this.refresh();\n }\n });\n this.setActive(session);\n session.start().then(() => {\n const failed = session.getSnapshot().phase === \"start_failed\";\n if (failed && this.active === session) {\n unsubscribe();\n this.setActive(null);\n }\n });\n return true;\n }\n\n private setActive(session: SurveySession | null): void {\n this.active = session;\n for (const listener of this.listeners) {\n listener();\n }\n }\n}\n\n/** Shared by `<RefletSurveys />`, `useRefletSurveys()` and the client's feedback signal. */\nexport const surveyControllerFor = (\n publicKey: string,\n baseUrl: string | undefined\n): SurveyController => {\n const key = surveyRegistryKey(publicKey, baseUrl ?? DEFAULT_API_URL);\n let controller = surveyControllers.get(key);\n if (!controller) {\n controller = new SurveyController();\n surveyControllers.set(key, controller);\n }\n return controller;\n};\n","import type { SurveyPosition } from \"@reflet/survey-core\";\nimport { type ReactNode, useEffect, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { REFLET_Z_INDEX } from \"../z-index\";\n\nexport const SURVEY_HOST_ATTRIBUTE = \"data-reflet-survey\";\n\nconst LAYER_STYLES = `\n:host { all: initial; }\n.rfs-layer {\n position: fixed;\n z-index: ${REFLET_Z_INDEX.surveyLayer};\n bottom: 20px;\n width: min(360px, calc(100vw - 32px));\n}\n.rfs-layer[data-position=\"bottom_right\"] { right: 20px; }\n.rfs-layer[data-position=\"bottom_left\"] { left: 20px; }\n.rfs-layer[data-position=\"center\"] {\n inset: 0;\n display: grid;\n place-items: center;\n width: auto;\n padding: 16px;\n background: rgb(15 15 15 / 28%);\n animation: rfs-layer-fade 240ms ease-out;\n}\n.rfs-layer[data-position=\"center\"] > .rf-survey { max-width: 420px; }\n@media (max-width: 480px) {\n .rfs-layer[data-position^=\"bottom\"] { right: 16px; bottom: 16px; left: 16px; width: auto; }\n}\n@keyframes rfs-layer-fade { from { opacity: 0; } }\n@media (prefers-reduced-motion: reduce) { .rfs-layer { animation: none; } }\n`;\n\n/**\n * Hosts an in-app survey in its own shadow root so page CSS can't restyle it.\n * Direct DOM work is unavoidable: React can't create a shadow root declaratively.\n */\nexport function SurveyPortal({\n children,\n position,\n}: {\n children: ReactNode;\n position: SurveyPosition;\n}) {\n const [shadowRoot, setShadowRoot] = useState<ShadowRoot | null>(null);\n\n useEffect(() => {\n const host = document.createElement(\"div\");\n host.setAttribute(SURVEY_HOST_ATTRIBUTE, \"\");\n const root = host.attachShadow({ mode: \"open\" });\n const style = document.createElement(\"style\");\n style.textContent = LAYER_STYLES;\n root.append(style);\n document.body.append(host);\n setShadowRoot(root);\n return () => host.remove();\n }, []);\n\n if (!shadowRoot) {\n return null;\n }\n return createPortal(\n <div className=\"rfs-layer\" data-position={position}>\n {children}\n </div>,\n shadowRoot\n );\n}\n","import { displayOf } from \"@reflet/survey-core\";\nimport type { SurveySession } from \"@reflet/survey-core/client\";\nimport { useEffect, useState, useSyncExternalStore } from \"react\";\nimport type { Reflet } from \"../client\";\nimport { useRefletContext } from \"../react-context\";\nimport { SurveyCard, type SurveyTheme } from \"./card/survey-card\";\nimport { type RefletSurveysCallbacks, surveyControllerFor } from \"./controller\";\nimport { SurveyPortal } from \"./survey-portal\";\nimport { useSessionHandle } from \"./use-survey-session\";\n\nconst ENDING_AUTO_CLOSE_MS = 6000;\n\nexport interface RefletSurveysProps extends RefletSurveysCallbacks {\n /** Turn delivery off without unmounting, e.g. on pages where surveys would interrupt. */\n enabled?: boolean;\n /** Defaults to your organization's brand color. */\n primaryColor?: string;\n theme?: SurveyTheme;\n}\n\nfunction useOrganizationColor(\n client: Reflet,\n override: string | undefined\n): string | undefined {\n const [organizationColor, setOrganizationColor] = useState<string>();\n\n useEffect(() => {\n if (override) {\n return;\n }\n let cancelled = false;\n client\n .getConfig()\n .then((config) => {\n if (!cancelled) {\n setOrganizationColor(config.primaryColor);\n }\n })\n .catch(() => undefined);\n return () => {\n cancelled = true;\n };\n }, [client, override]);\n\n return override ?? organizationColor;\n}\n\nfunction ActiveSurvey({\n primaryColor,\n session,\n theme,\n}: {\n primaryColor?: string;\n session: SurveySession;\n theme: SurveyTheme;\n}) {\n const handle = useSessionHandle(session);\n const { ending, phase } = handle.snapshot;\n const endsQuietly =\n ending !== null && !(ending.buttonUrl || ending.buttonLabel);\n\n useEffect(() => {\n if (!endsQuietly) {\n return;\n }\n const timer = window.setTimeout(\n () => session.dismiss(),\n ENDING_AUTO_CLOSE_MS\n );\n return () => window.clearTimeout(timer);\n }, [endsQuietly, session]);\n\n if (phase !== \"question\" && phase !== \"ending\") {\n return null;\n }\n return (\n <SurveyPortal\n position={displayOf(session.survey.display).position ?? \"bottom_right\"}\n >\n <SurveyCard\n primaryColor={primaryColor}\n session={handle}\n theme={theme}\n variant=\"floating\"\n />\n </SurveyPortal>\n );\n}\n\n/**\n * In-app survey delivery. Mount once inside `RefletProvider`: it loads the\n * surveys this visitor is eligible for, arms their triggers and shows one at a time.\n */\nexport function RefletSurveys({\n enabled = true,\n onSurveyAnswer,\n onSurveyComplete,\n onSurveyDismiss,\n onSurveyStart,\n primaryColor,\n theme = \"auto\",\n}: RefletSurveysProps) {\n const { baseUrl, client, publicKey } = useRefletContext();\n const controller = surveyControllerFor(publicKey, baseUrl);\n const color = useOrganizationColor(client, primaryColor);\n const session = useSyncExternalStore(\n controller.subscribe,\n controller.getActiveSession,\n () => null\n );\n\n useEffect(() => {\n controller.callbacks = {\n onSurveyAnswer,\n onSurveyComplete,\n onSurveyDismiss,\n onSurveyStart,\n };\n });\n\n useEffect(() => {\n if (!enabled) {\n return;\n }\n return controller.attach(client);\n }, [client, controller, enabled]);\n\n if (!session) {\n return null;\n }\n return (\n <ActiveSurvey\n key={session.survey._id}\n primaryColor={color}\n session={session}\n theme={theme}\n />\n );\n}\n","import { useMemo, useSyncExternalStore } from \"react\";\nimport { useRefletContext } from \"../react-context\";\nimport { surveyControllerFor } from \"./controller\";\n\nexport interface RefletSurveysApi {\n /** Survey on screen, or null. */\n activeSurveyId: string | null;\n /** Closes the survey on screen; an unfinished response is marked abandoned. */\n dismissSurvey: () => void;\n /** Shows an eligible survey now, whatever its trigger (use for `manual` surveys). */\n showSurvey: (surveyId: string) => void;\n /** Fires surveys triggered by this custom event. */\n track: (eventName: string) => void;\n}\n\n/**\n * Drives `<RefletSurveys />` from anywhere inside `RefletProvider`. Calls made\n * before surveys load are queued and replayed once they do.\n */\nexport function useRefletSurveys(): RefletSurveysApi {\n const { baseUrl, publicKey } = useRefletContext();\n const controller = surveyControllerFor(publicKey, baseUrl);\n const activeSurveyId = useSyncExternalStore(\n controller.subscribe,\n () => controller.getActiveSession()?.survey._id ?? null,\n () => null\n );\n\n return useMemo(\n () => ({\n activeSurveyId,\n dismissSurvey: () => controller.dismissSurvey(),\n showSurvey: (surveyId: string) => controller.showSurvey(surveyId),\n track: (eventName: string) => controller.track(eventName),\n }),\n [activeSurveyId, controller]\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/feedback/ui/selection-outline.tsx
4
+ /**
5
+ * Keeps the picked element ringed while the reporter writes about it, so the
6
+ * composer parked next to it is visibly about that element.
7
+ */
8
+ function SelectionOutline({ node }) {
9
+ const boxRef = useRef(null);
10
+ useEffect(() => {
11
+ const box = boxRef.current;
12
+ if (!(box && node)) return;
13
+ let frame = 0;
14
+ const draw = () => {
15
+ frame = 0;
16
+ const rect = node.getBoundingClientRect();
17
+ const hidden = rect.width === 0 || rect.height === 0 || rect.bottom < 0 || rect.top > window.innerHeight;
18
+ box.style.opacity = hidden ? "0" : "1";
19
+ box.style.width = `${rect.width}px`;
20
+ box.style.height = `${rect.height}px`;
21
+ box.style.transform = `translate(${rect.left}px, ${rect.top}px)`;
22
+ };
23
+ const schedule = () => {
24
+ frame ||= requestAnimationFrame(draw);
25
+ };
26
+ draw();
27
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(draw);
28
+ observer?.observe(node);
29
+ window.addEventListener("scroll", schedule, {
30
+ capture: true,
31
+ passive: true
32
+ });
33
+ window.addEventListener("resize", schedule);
34
+ return () => {
35
+ cancelAnimationFrame(frame);
36
+ observer?.disconnect();
37
+ window.removeEventListener("scroll", schedule, true);
38
+ window.removeEventListener("resize", schedule);
39
+ };
40
+ }, [node]);
41
+ if (!node) return null;
42
+ return /* @__PURE__ */ jsx("div", {
43
+ "aria-hidden": "true",
44
+ className: "selection-outline",
45
+ ref: boxRef
46
+ });
47
+ }
48
+ //#endregion
49
+ export { SelectionOutline as t };
50
+
51
+ //# sourceMappingURL=selection-outline-NzAAAZXR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-outline-NzAAAZXR.js","names":[],"sources":["../src/feedback/ui/selection-outline.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Keeps the picked element ringed while the reporter writes about it, so the\n * composer parked next to it is visibly about that element.\n */\nexport function SelectionOutline({ node }: { node: Element | null }) {\n const boxRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const box = boxRef.current;\n if (!(box && node)) {\n return;\n }\n\n let frame = 0;\n const draw = () => {\n frame = 0;\n const rect = node.getBoundingClientRect();\n const hidden =\n rect.width === 0 ||\n rect.height === 0 ||\n rect.bottom < 0 ||\n rect.top > window.innerHeight;\n box.style.opacity = hidden ? \"0\" : \"1\";\n box.style.width = `${rect.width}px`;\n box.style.height = `${rect.height}px`;\n box.style.transform = `translate(${rect.left}px, ${rect.top}px)`;\n };\n const schedule = () => {\n frame ||= requestAnimationFrame(draw);\n };\n\n draw();\n const observer =\n typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(draw);\n observer?.observe(node);\n window.addEventListener(\"scroll\", schedule, {\n capture: true,\n passive: true,\n });\n window.addEventListener(\"resize\", schedule);\n\n return () => {\n cancelAnimationFrame(frame);\n observer?.disconnect();\n window.removeEventListener(\"scroll\", schedule, true);\n window.removeEventListener(\"resize\", schedule);\n };\n }, [node]);\n\n if (!node) {\n return null;\n }\n\n return <div aria-hidden=\"true\" className=\"selection-outline\" ref={boxRef} />;\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,iBAAiB,EAAE,QAAkC;CACnE,MAAM,SAAS,OAAuB,IAAI;CAE1C,gBAAgB;EACd,MAAM,MAAM,OAAO;EACnB,IAAI,EAAE,OAAO,OACX;EAGF,IAAI,QAAQ;EACZ,MAAM,aAAa;GACjB,QAAQ;GACR,MAAM,OAAO,KAAK,sBAAsB;GACxC,MAAM,SACJ,KAAK,UAAU,KACf,KAAK,WAAW,KAChB,KAAK,SAAS,KACd,KAAK,MAAM,OAAO;GACpB,IAAI,MAAM,UAAU,SAAS,MAAM;GACnC,IAAI,MAAM,QAAQ,GAAG,KAAK,MAAM;GAChC,IAAI,MAAM,SAAS,GAAG,KAAK,OAAO;GAClC,IAAI,MAAM,YAAY,aAAa,KAAK,KAAK,MAAM,KAAK,IAAI;EAC9D;EACA,MAAM,iBAAiB;GACrB,UAAU,sBAAsB,IAAI;EACtC;EAEA,KAAK;EACL,MAAM,WACJ,OAAO,mBAAmB,cAAc,OAAO,IAAI,eAAe,IAAI;EACxE,UAAU,QAAQ,IAAI;EACtB,OAAO,iBAAiB,UAAU,UAAU;GAC1C,SAAS;GACT,SAAS;EACX,CAAC;EACD,OAAO,iBAAiB,UAAU,QAAQ;EAE1C,aAAa;GACX,qBAAqB,KAAK;GAC1B,UAAU,WAAW;GACrB,OAAO,oBAAoB,UAAU,UAAU,IAAI;GACnD,OAAO,oBAAoB,UAAU,QAAQ;EAC/C;CACF,GAAG,CAAC,IAAI,CAAC;CAET,IAAI,CAAC,MACH,OAAO;CAGT,OAAO,oBAAC,OAAD;EAAK,eAAY;EAAO,WAAU;EAAoB,KAAK;CAAS,CAAA;AAC7E"}
package/dist/server.js CHANGED
@@ -63,7 +63,7 @@ async function verifyUser(token, secretKey) {
63
63
  if (!constantTimeEqual(signature, await createHmacSha256Signature(`${headerB64}.${payloadB64}`, await deriveSigningKey(secretKey)))) return null;
64
64
  try {
65
65
  const payload = JSON.parse(base64UrlDecode(payloadB64));
66
- if (payload.exp && Date.now() > payload.exp * 1e3) return null;
66
+ if (typeof payload.exp !== "number" || Date.now() > payload.exp * 1e3) return null;
67
67
  const userId = payload.id ?? payload.sub;
68
68
  if (!userId) return null;
69
69
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server-side utilities for Reflet SDK\n * Works in Node.js, Edge runtimes, Cloudflare Workers, and Convex\n *\n * @example\n * ```ts\n * import { signUser } from 'reflet-sdk/server';\n *\n * const { token } = await signUser(\n * { id: user.id, email: user.email, name: user.name },\n * process.env.REFLET_SECRET_KEY!\n * );\n * ```\n */\n\n// Top-level regex patterns for base64url encoding\nconst PLUS_REGEX = /\\+/g;\nconst SLASH_REGEX = /\\//g;\nconst PADDING_REGEX = /[=]+$/;\nconst DASH_REGEX = /-/g;\nconst UNDERSCORE_REGEX = /_/g;\n\nexport interface SignUserOptions {\n /** URL to user's avatar image */\n avatar?: string;\n /** User's email address */\n email?: string;\n /** User's unique ID in your system */\n id: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n /** User's display name */\n name?: string;\n}\n\nexport interface SignedUserToken {\n /** Token expiration timestamp (ms) */\n expiresAt: number;\n /** The signed JWT token */\n token: string;\n}\n\n/**\n * Sign a user token for secure client-side identification\n * Uses HMAC-SHA256 via Web Crypto API (works in all runtimes)\n */\nexport async function signUser(\n user: SignUserOptions,\n secretKey: string,\n expiresInSeconds = 86_400 // 24 hours default\n): Promise<SignedUserToken> {\n if (!secretKey) {\n throw new Error(\"signUser: secretKey is required\");\n }\n\n if (!user.id) {\n throw new Error(\"signUser: user.id is required\");\n }\n\n const now = Math.floor(Date.now() / 1000);\n const exp = now + expiresInSeconds;\n\n const header = { alg: \"HS256\", typ: \"JWT\" };\n const payload = {\n avatar: user.avatar,\n email: user.email,\n exp,\n iat: now,\n id: user.id,\n metadata: user.metadata,\n name: user.name,\n sub: user.id,\n };\n\n const headerB64 = base64UrlEncode(JSON.stringify(header));\n const payloadB64 = base64UrlEncode(JSON.stringify(payload));\n\n // Create signature using HMAC-SHA256 via Web Crypto API\n const signature = await createHmacSha256Signature(\n `${headerB64}.${payloadB64}`,\n await deriveSigningKey(secretKey)\n );\n\n return {\n expiresAt: exp * 1000,\n token: `${headerB64}.${payloadB64}.${signature}`,\n };\n}\n\n/**\n * Verify a user token\n * Returns the user data if valid, null otherwise\n */\nexport async function verifyUser(\n token: string,\n secretKey: string\n): Promise<SignUserOptions | null> {\n if (!(token && secretKey)) {\n return null;\n }\n\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return null;\n }\n\n // TypeScript doesn't narrow array destructuring, so we use direct indexing\n const headerB64 = parts[0] as string;\n const payloadB64 = parts[1] as string;\n const signature = parts[2] as string;\n\n // Verify signature using constant-time comparison\n const expectedSignature = await createHmacSha256Signature(\n `${headerB64}.${payloadB64}`,\n await deriveSigningKey(secretKey)\n );\n\n if (!constantTimeEqual(signature, expectedSignature)) {\n return null;\n }\n\n try {\n const payload = JSON.parse(base64UrlDecode(payloadB64)) as {\n id?: string;\n sub?: string;\n email?: string;\n name?: string;\n avatar?: string;\n metadata?: Record<string, unknown>;\n exp?: number;\n };\n\n // Check expiration\n if (payload.exp && Date.now() > payload.exp * 1000) {\n return null;\n }\n\n const userId = payload.id ?? payload.sub;\n if (!userId) {\n return null;\n }\n\n return {\n avatar: payload.avatar,\n email: payload.email,\n id: userId,\n metadata: payload.metadata,\n name: payload.name,\n };\n } catch {\n return null;\n }\n}\n\n// ============================================\n// Utility Functions (Web Crypto API - Universal)\n// ============================================\n\nfunction base64UrlEncode(str: string): string {\n // Use TextEncoder for universal compatibility\n const bytes = new TextEncoder().encode(str);\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n const base64 = btoa(binary);\n\n return base64\n .replace(PLUS_REGEX, \"-\")\n .replace(SLASH_REGEX, \"_\")\n .replace(PADDING_REGEX, \"\");\n}\n\nfunction base64UrlDecode(str: string): string {\n // Restore base64 padding\n const padding = 4 - (str.length % 4);\n const paddedStr = padding < 4 ? str + \"=\".repeat(padding) : str;\n\n // Replace URL-safe characters\n const base64 = paddedStr\n .replace(DASH_REGEX, \"+\")\n .replace(UNDERSCORE_REGEX, \"/\");\n\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Signing key is the SHA-256 hex digest of the secret key: Reflet stores exactly\n * that digest, so it can verify tokens without ever holding the raw secret.\n */\nasync function deriveSigningKey(secretKey: string): Promise<string> {\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(secretKey)\n );\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * Create HMAC-SHA256 signature using Web Crypto API\n * Works in Node.js 18+, Edge runtimes, Cloudflare Workers, Convex, etc.\n */\nasync function createHmacSha256Signature(\n data: string,\n secret: string\n): Promise<string> {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(secret);\n const messageData = encoder.encode(data);\n\n // Import the secret key\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyData,\n { hash: \"SHA-256\", name: \"HMAC\" },\n false,\n [\"sign\"]\n );\n\n // Sign the data\n const signatureBuffer = await crypto.subtle.sign(\n \"HMAC\",\n cryptoKey,\n messageData\n );\n\n // Convert to base64url\n const signatureArray = new Uint8Array(signatureBuffer);\n let binary = \"\";\n for (const byte of signatureArray) {\n binary += String.fromCharCode(byte);\n }\n const base64 = btoa(binary);\n\n return base64\n .replace(PLUS_REGEX, \"-\")\n .replace(SLASH_REGEX, \"_\")\n .replace(PADDING_REGEX, \"\");\n}\n\n/**\n * Constant-time string comparison to prevent timing attacks\n */\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n // biome-ignore lint/suspicious/noBitwiseOperators: intentional for constant-time comparison\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n\n return result === 0;\n}\n\n// Export types\nexport type { SignUserOptions as RefletServerUser };\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["/**\n * Server-side utilities for Reflet SDK\n * Works in Node.js, Edge runtimes, Cloudflare Workers, and Convex\n *\n * @example\n * ```ts\n * import { signUser } from 'reflet-sdk/server';\n *\n * const { token } = await signUser(\n * { id: user.id, email: user.email, name: user.name },\n * process.env.REFLET_SECRET_KEY!\n * );\n * ```\n */\n\n// Top-level regex patterns for base64url encoding\nconst PLUS_REGEX = /\\+/g;\nconst SLASH_REGEX = /\\//g;\nconst PADDING_REGEX = /[=]+$/;\nconst DASH_REGEX = /-/g;\nconst UNDERSCORE_REGEX = /_/g;\n\nexport interface SignUserOptions {\n /** URL to user's avatar image */\n avatar?: string;\n /** User's email address */\n email?: string;\n /** User's unique ID in your system */\n id: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n /** User's display name */\n name?: string;\n}\n\nexport interface SignedUserToken {\n /** Token expiration timestamp (ms) */\n expiresAt: number;\n /** The signed JWT token */\n token: string;\n}\n\n/**\n * Sign a user token for secure client-side identification\n * Uses HMAC-SHA256 via Web Crypto API (works in all runtimes)\n */\nexport async function signUser(\n user: SignUserOptions,\n secretKey: string,\n expiresInSeconds = 86_400 // 24 hours default\n): Promise<SignedUserToken> {\n if (!secretKey) {\n throw new Error(\"signUser: secretKey is required\");\n }\n\n if (!user.id) {\n throw new Error(\"signUser: user.id is required\");\n }\n\n const now = Math.floor(Date.now() / 1000);\n const exp = now + expiresInSeconds;\n\n const header = { alg: \"HS256\", typ: \"JWT\" };\n const payload = {\n avatar: user.avatar,\n email: user.email,\n exp,\n iat: now,\n id: user.id,\n metadata: user.metadata,\n name: user.name,\n sub: user.id,\n };\n\n const headerB64 = base64UrlEncode(JSON.stringify(header));\n const payloadB64 = base64UrlEncode(JSON.stringify(payload));\n\n // Create signature using HMAC-SHA256 via Web Crypto API\n const signature = await createHmacSha256Signature(\n `${headerB64}.${payloadB64}`,\n await deriveSigningKey(secretKey)\n );\n\n return {\n expiresAt: exp * 1000,\n token: `${headerB64}.${payloadB64}.${signature}`,\n };\n}\n\n/**\n * Verify a user token\n * Returns the user data if valid, null otherwise\n */\nexport async function verifyUser(\n token: string,\n secretKey: string\n): Promise<SignUserOptions | null> {\n if (!(token && secretKey)) {\n return null;\n }\n\n const parts = token.split(\".\");\n if (parts.length !== 3) {\n return null;\n }\n\n // TypeScript doesn't narrow array destructuring, so we use direct indexing\n const headerB64 = parts[0] as string;\n const payloadB64 = parts[1] as string;\n const signature = parts[2] as string;\n\n // Verify signature using constant-time comparison\n const expectedSignature = await createHmacSha256Signature(\n `${headerB64}.${payloadB64}`,\n await deriveSigningKey(secretKey)\n );\n\n if (!constantTimeEqual(signature, expectedSignature)) {\n return null;\n }\n\n try {\n const payload = JSON.parse(base64UrlDecode(payloadB64)) as {\n id?: string;\n sub?: string;\n email?: string;\n name?: string;\n avatar?: string;\n metadata?: Record<string, unknown>;\n exp?: number;\n };\n\n if (typeof payload.exp !== \"number\" || Date.now() > payload.exp * 1000) {\n return null;\n }\n\n const userId = payload.id ?? payload.sub;\n if (!userId) {\n return null;\n }\n\n return {\n avatar: payload.avatar,\n email: payload.email,\n id: userId,\n metadata: payload.metadata,\n name: payload.name,\n };\n } catch {\n return null;\n }\n}\n\n// ============================================\n// Utility Functions (Web Crypto API - Universal)\n// ============================================\n\nfunction base64UrlEncode(str: string): string {\n // Use TextEncoder for universal compatibility\n const bytes = new TextEncoder().encode(str);\n let binary = \"\";\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n const base64 = btoa(binary);\n\n return base64\n .replace(PLUS_REGEX, \"-\")\n .replace(SLASH_REGEX, \"_\")\n .replace(PADDING_REGEX, \"\");\n}\n\nfunction base64UrlDecode(str: string): string {\n // Restore base64 padding\n const padding = 4 - (str.length % 4);\n const paddedStr = padding < 4 ? str + \"=\".repeat(padding) : str;\n\n // Replace URL-safe characters\n const base64 = paddedStr\n .replace(DASH_REGEX, \"+\")\n .replace(UNDERSCORE_REGEX, \"/\");\n\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Signing key is the SHA-256 hex digest of the secret key: Reflet stores exactly\n * that digest, so it can verify tokens without ever holding the raw secret.\n */\nasync function deriveSigningKey(secretKey: string): Promise<string> {\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(secretKey)\n );\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/**\n * Create HMAC-SHA256 signature using Web Crypto API\n * Works in Node.js 18+, Edge runtimes, Cloudflare Workers, Convex, etc.\n */\nasync function createHmacSha256Signature(\n data: string,\n secret: string\n): Promise<string> {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(secret);\n const messageData = encoder.encode(data);\n\n // Import the secret key\n const cryptoKey = await crypto.subtle.importKey(\n \"raw\",\n keyData,\n { hash: \"SHA-256\", name: \"HMAC\" },\n false,\n [\"sign\"]\n );\n\n // Sign the data\n const signatureBuffer = await crypto.subtle.sign(\n \"HMAC\",\n cryptoKey,\n messageData\n );\n\n // Convert to base64url\n const signatureArray = new Uint8Array(signatureBuffer);\n let binary = \"\";\n for (const byte of signatureArray) {\n binary += String.fromCharCode(byte);\n }\n const base64 = btoa(binary);\n\n return base64\n .replace(PLUS_REGEX, \"-\")\n .replace(SLASH_REGEX, \"_\")\n .replace(PADDING_REGEX, \"\");\n}\n\n/**\n * Constant-time string comparison to prevent timing attacks\n */\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) {\n return false;\n }\n\n let result = 0;\n for (let i = 0; i < a.length; i++) {\n // biome-ignore lint/suspicious/noBitwiseOperators: intentional for constant-time comparison\n result |= a.charCodeAt(i) ^ b.charCodeAt(i);\n }\n\n return result === 0;\n}\n\n// Export types\nexport type { SignUserOptions as RefletServerUser };\n"],"mappings":"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"}