reflet-sdk 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -7
- package/dist/client-BYuSKJw5.js +270 -0
- package/dist/client-BYuSKJw5.js.map +1 -0
- package/dist/client-Bi-GlSii.d.ts +129 -0
- package/dist/client-Bi-GlSii.d.ts.map +1 -0
- package/dist/feedback.d.ts +130 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +2276 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.d.ts +3 -297
- package/dist/index.js +2 -3
- package/dist/react-context-DZw_HHjj.js +85 -0
- package/dist/react-context-DZw_HHjj.js.map +1 -0
- package/dist/react.d.ts +208 -89
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +1246 -338
- package/dist/react.js.map +1 -1
- package/dist/server.d.ts +18 -16
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +118 -117
- package/dist/server.js.map +1 -1
- package/dist/types-xyOadyfX.d.ts +269 -0
- package/dist/types-xyOadyfX.d.ts.map +1 -0
- package/package.json +29 -9
- package/dist/chunk-GD7FQQBT.js +0 -282
- package/dist/chunk-GD7FQQBT.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react-context.tsx","../src/react-hooks.ts"],"names":["useState","useEffect","error"],"mappings":"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","file":"react.js","sourcesContent":["import {\n createContext,\n type ReactNode,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport { Reflet } from \"./client\";\nimport type { RefletConfig, RefletUser } from \"./types\";\n\n// ============================================\n// Context Types\n// ============================================\n\ninterface RefletContextValue {\n client: Reflet;\n isReady: boolean;\n user: RefletUser | undefined;\n setUser: (user: RefletUser | undefined) => void;\n}\n\n// ============================================\n// Context\n// ============================================\n\nconst RefletContext = createContext<RefletContextValue | null>(null);\n\n// ============================================\n// Provider\n// ============================================\n\nexport interface RefletProviderProps {\n /** Your organization's public API key */\n publicKey: string;\n /** API base URL (optional, defaults to Reflet production API) */\n baseUrl?: string;\n /** Initial user identification */\n user?: RefletUser;\n /** Pre-signed user token (alternative to user) */\n userToken?: string;\n /** Children */\n children: ReactNode;\n}\n\n/**\n * Provider component for Reflet SDK\n *\n * @example\n * ```tsx\n * import { RefletProvider } from 'reflet-sdk/react';\n *\n * function App() {\n * return (\n * <RefletProvider\n * publicKey=\"fb_pub_xxx\"\n * user={{ id: 'user_123', email: 'user@example.com', name: 'John' }}\n * >\n * <YourApp />\n * </RefletProvider>\n * );\n * }\n * ```\n */\nexport function RefletProvider({\n publicKey,\n baseUrl,\n user: initialUser,\n userToken,\n children,\n}: RefletProviderProps) {\n const [user, setUser] = useState<RefletUser | undefined>(initialUser);\n const [isReady, setIsReady] = useState(false);\n\n const client = useMemo(() => {\n const config: RefletConfig = {\n publicKey,\n baseUrl,\n user,\n userToken,\n };\n return new Reflet(config);\n }, [publicKey, baseUrl, user, userToken]);\n\n // Update client when user changes\n useEffect(() => {\n if (userToken) {\n client.setUserToken(userToken);\n } else {\n client.setUser(user);\n }\n }, [client, user, userToken]);\n\n // Mark as ready after mount\n useEffect(() => {\n setIsReady(true);\n }, []);\n\n const value = useMemo(\n () => ({\n client,\n isReady,\n user,\n setUser,\n }),\n [client, isReady, user]\n );\n\n return (\n <RefletContext.Provider value={value}>{children}</RefletContext.Provider>\n );\n}\n\n// ============================================\n// Hook to access context\n// ============================================\n\nexport function useRefletContext(): RefletContextValue {\n const context = useContext(RefletContext);\n if (!context) {\n throw new Error(\"useRefletContext must be used within a RefletProvider\");\n }\n return context;\n}\n\nexport function useRefletClient(): Reflet {\n const { client } = useRefletContext();\n return client;\n}\n","import { useCallback, useEffect, useState } from \"react\";\nimport { useRefletClient } from \"./react-context\";\nimport type {\n AddCommentParams,\n ChangelogEntry,\n Comment,\n CreateFeedbackParams,\n FeedbackDetail,\n FeedbackItem,\n FeedbackListParams,\n OrganizationConfig,\n Roadmap,\n} from \"./types\";\n\n// ============================================\n// Types\n// ============================================\n\ninterface UseQueryResult<T> {\n data: T | undefined;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n}\n\ninterface UseMutationResult<TData, TVariables> {\n mutate: (variables: TVariables) => Promise<TData>;\n isLoading: boolean;\n error: Error | null;\n data: TData | undefined;\n reset: () => void;\n}\n\n// ============================================\n// Organization Config Hook\n// ============================================\n\n/**\n * Hook to fetch organization configuration\n */\nexport function useOrganizationConfig(): UseQueryResult<OrganizationConfig> {\n const client = useRefletClient();\n const [data, setData] = useState<OrganizationConfig | undefined>();\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getConfig();\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error\n ? err\n : new Error(\"Failed to fetch organization config\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Feedback List Hook\n// ============================================\n\nexport interface UseFeedbackListOptions extends FeedbackListParams {\n /** Auto-fetch on mount */\n enabled?: boolean;\n}\n\n/**\n * Hook to fetch feedback list with filtering\n *\n * @example\n * ```tsx\n * const { data, isLoading } = useFeedbackList({ status: 'open', sortBy: 'votes' });\n * ```\n */\nexport function useFeedbackList(\n options: UseFeedbackListOptions = {}\n): UseQueryResult<{ items: FeedbackItem[]; total: number; hasMore: boolean }> {\n const client = useRefletClient();\n const { enabled = true, ...params } = options;\n\n // Create stable reference using individual params\n const { statusId, status, search, sortBy, limit, offset } = params;\n\n const [data, setData] = useState<{\n items: FeedbackItem[];\n total: number;\n hasMore: boolean;\n }>();\n const [isLoading, setIsLoading] = useState(enabled);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.list({\n statusId,\n status,\n search,\n sortBy,\n limit,\n offset,\n });\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch feedback\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, statusId, status, search, sortBy, limit, offset]);\n\n useEffect(() => {\n if (enabled) {\n fetch();\n }\n }, [fetch, enabled]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Single Feedback Hook\n// ============================================\n\n/**\n * Hook to fetch a single feedback item\n *\n * @example\n * ```tsx\n * const { data: feedback, isLoading } = useFeedback('feedback_id');\n * ```\n */\nexport function useFeedback(\n feedbackId: string | undefined\n): UseQueryResult<FeedbackDetail> {\n const client = useRefletClient();\n const [data, setData] = useState<FeedbackDetail | undefined>();\n const [isLoading, setIsLoading] = useState(!!feedbackId);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n if (!feedbackId) {\n setData(undefined);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.get(feedbackId);\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch feedback\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, feedbackId]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Comments Hook\n// ============================================\n\n/**\n * Hook to fetch comments for a feedback item\n */\nexport function useComments(\n feedbackId: string | undefined,\n sortBy: \"newest\" | \"oldest\" = \"oldest\"\n): UseQueryResult<Comment[]> {\n const client = useRefletClient();\n const [data, setData] = useState<Comment[] | undefined>();\n const [isLoading, setIsLoading] = useState(!!feedbackId);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n if (!feedbackId) {\n setData(undefined);\n setIsLoading(false);\n return;\n }\n\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getComments(feedbackId, sortBy);\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch comments\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, feedbackId, sortBy]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Roadmap Hook\n// ============================================\n\n/**\n * Hook to fetch roadmap data\n */\nexport function useRoadmap(): UseQueryResult<Roadmap> {\n const client = useRefletClient();\n const [data, setData] = useState<Roadmap | undefined>();\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getRoadmap();\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch roadmap\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\n}\n\n// ============================================\n// Changelog Hook\n// ============================================\n\n/**\n * Hook to fetch changelog entries\n */\nexport function useChangelog(limit?: number): UseQueryResult<ChangelogEntry[]> {\n const client = useRefletClient();\n const [data, setData] = useState<ChangelogEntry[] | undefined>();\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetch = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.getChangelog(limit);\n setData(result);\n } catch (err) {\n setError(\n err instanceof Error ? err : new Error(\"Failed to fetch changelog\")\n );\n } finally {\n setIsLoading(false);\n }\n }, [client, limit]);\n\n useEffect(() => {\n fetch();\n }, [fetch]);\n\n return { data, isLoading, error, refetch: fetch };\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 [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<{\n feedbackId: string;\n isApproved: boolean;\n }>();\n\n const mutate = useCallback(\n async (params: CreateFeedbackParams) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.create(params);\n setData(result);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to create feedback\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { mutate, isLoading, error, data, reset };\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 [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<{ voted: boolean; voteCount: number }>();\n\n const mutate = useCallback(\n async ({\n feedbackId,\n type = \"upvote\",\n }: {\n feedbackId: string;\n type?: \"upvote\" | \"downvote\";\n }) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.vote(feedbackId, type);\n setData(result);\n return result;\n } catch (err) {\n const error = err instanceof Error ? err : new Error(\"Failed to vote\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { mutate, isLoading, error, data, reset };\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 [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<{ commentId: string }>();\n\n const mutate = useCallback(\n async (params: AddCommentParams) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.comment(params);\n setData(result);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to add comment\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const reset = useCallback(() => {\n setData(undefined);\n setError(null);\n }, []);\n\n return { mutate, isLoading, error, data, reset };\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 [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const subscribe = useCallback(\n async (feedbackId: string) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.subscribe(feedbackId);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to subscribe\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const unsubscribe = useCallback(\n async (feedbackId: string) => {\n setIsLoading(true);\n setError(null);\n try {\n const result = await client.unsubscribe(feedbackId);\n return result;\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(\"Failed to unsubscribe\");\n setError(error);\n throw error;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n return { subscribe, unsubscribe, isLoading, error };\n}\n"]}
|
|
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 type FeedbackCategory = \"feature\" | \"bug\" | \"question\";\n\nexport interface FeedbackDialogLabels {\n cancel?: string;\n categoryBug?: string;\n categoryFeature?: string;\n categoryQuestion?: 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 /** Categories to show (default: all three) */\n categories?: FeedbackCategory[];\n /** Default selected category */\n defaultCategory?: FeedbackCategory;\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 categoryBug: \"Bug\",\n categoryFeature: \"Feature\",\n categoryQuestion: \"Question\",\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 CATEGORY_ICONS: Record<FeedbackCategory, string> = {\n bug: \"🐛\",\n feature: \"✨\",\n question: \"💬\",\n};\n\nexport const AUTO_CLOSE_DELAY = 2500;\n","export function getFeedbackChromeCSS(): string {\n return `\n/* ===== Footer ===== */\n.reflet-footer {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n padding-top: 4px;\n}\n\n.reflet-btn {\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: all 150ms;\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: #ffffff;\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/* ===== Spinner ===== */\n.reflet-spinner {\n width: 16px;\n height: 16px;\n border: 2px solid rgba(255,255,255,0.3);\n border-top-color: #ffffff;\n border-radius: 50%;\n animation: reflet-spin 600ms linear infinite;\n}\n\n/* ===== Success State ===== */\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/* ===== Error State ===== */\n.reflet-error-msg {\n font-size: 13px;\n color: var(--reflet-error);\n margin: 0;\n}\n\n/* ===== Trigger Button ===== */\n.reflet-trigger {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n background: var(--reflet-primary);\n color: #ffffff;\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;\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/* ===== Honeypot ===== */\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/* ===== Powered by ===== */\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/* ===== Mobile handle ===== */\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}\n","/**\n * Self-contained styles for the FeedbackButton and FeedbackDialog components.\n * Uses CSS custom properties for theming. Injected via <style> tag with a unique ID\n * to avoid duplication. All selectors are scoped under `[data-reflet-feedback]`.\n */\n\nimport { getFeedbackChromeCSS } from \"./react-feedback-styles-parts\";\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[data-reflet-feedback] {\n --reflet-primary: #6366f1;\n --reflet-primary-hover: #4f46e5;\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 rgba(0,0,0,0.25), 0 0 0 1px rgba(0,0,0,0.05);\n --reflet-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --reflet-success: #22c55e;\n --reflet-error: #ef4444;\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 rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05);\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 rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.05);\n}\n\n/* ===== Overlay ===== */\n.reflet-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,0.4);\n z-index: 99998;\n animation: reflet-fade-in 150ms ease-out;\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-overlay[data-closing=\"true\"] {\n animation: reflet-fade-out 150ms ease-in forwards;\n}\n\n/* ===== Dialog (Desktop) ===== */\n.reflet-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 99999;\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(100vh - 64px);\n overflow: hidden;\n font-family: var(--reflet-font);\n color: var(--reflet-text);\n animation: reflet-scale-in 200ms cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.reflet-dialog[data-closing=\"true\"] {\n animation: reflet-scale-out 150ms ease-in forwards;\n}\n\n/* ===== Sheet (Mobile) ===== */\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: 90vh;\n animation: reflet-slide-up 300ms cubic-bezier(0.16, 1, 0.3, 1);\n }\n\n .reflet-dialog[data-closing=\"true\"] {\n animation: reflet-slide-down 200ms ease-in forwards;\n }\n}\n\n/* ===== Header ===== */\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: 32px;\n height: 32px;\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;\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/* ===== Form ===== */\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/* ===== Category Selector ===== */\n.reflet-categories {\n display: flex;\n gap: 8px;\n}\n\n.reflet-category {\n flex: 1;\n padding: 8px 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-secondary);\n font-family: var(--reflet-font);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n text-align: center;\n transition: all 150ms;\n}\n\n.reflet-category:hover {\n border-color: var(--reflet-primary);\n color: var(--reflet-text);\n}\n\n.reflet-category[data-selected=\"true\"] {\n border-color: var(--reflet-primary);\n background: color-mix(in srgb, var(--reflet-primary) 8%, transparent);\n color: var(--reflet-primary);\n}\n${getFeedbackChromeCSS()}\n`;\n}\n","import {\n type FormEvent,\n type KeyboardEvent,\n useCallback,\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 FeedbackCategory,\n type FeedbackDialogProps,\n} from \"./react-feedback-dialog-types\";\nimport { injectFeedbackStyles } from \"./react-feedback-styles\";\n\nexport function useFeedbackDialog({\n open,\n onOpenChange,\n publicKey: publicKeyProp,\n baseUrl,\n user: userProp,\n userToken: userTokenProp,\n primaryColor,\n defaultCategory = \"feature\",\n labels: labelsProp,\n onSubmit,\n onOpen,\n onClose,\n}: Omit<FeedbackDialogProps, \"theme\" | \"categories\">) {\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 [category, setCategory] = useState<FeedbackCategory>(defaultCategory);\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<HTMLDivElement>(null);\n const previousActiveElement = useRef<Element | null>(null);\n\n const isAnonymous = !(user || userToken);\n\n // Inject styles on mount\n useEffect(() => {\n injectFeedbackStyles();\n }, []);\n\n // Apply primary color\n useEffect(() => {\n if (!(primaryColor && dialogRef.current)) {\n return;\n }\n const el = dialogRef.current.closest(\"[data-reflet-feedback]\");\n if (el instanceof HTMLElement) {\n el.style.setProperty(\"--reflet-primary\", primaryColor);\n }\n }, [primaryColor]);\n\n // Focus management\n useEffect(() => {\n if (open) {\n previousActiveElement.current = document.activeElement;\n onOpen?.();\n requestAnimationFrame(() => {\n titleInputRef.current?.focus();\n });\n }\n }, [open, onOpen]);\n\n // Escape key\n useEffect(() => {\n if (!open) {\n return;\n }\n\n const handleKeyDown = (e: globalThis.KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n handleClose();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n });\n\n // Lock body scroll\n useEffect(() => {\n if (!open) {\n return;\n }\n\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.body.style.overflow = originalOverflow;\n };\n }, [open]);\n\n const resetForm = useCallback(() => {\n setTitle(\"\");\n setDescription(\"\");\n setEmail(\"\");\n setCategory(defaultCategory);\n setHoneypot(\"\");\n setError(null);\n setIsSuccess(false);\n setIsSubmitting(false);\n }, [defaultCategory]);\n\n const handleClose = useCallback(() => {\n setIsClosing(true);\n setTimeout(() => {\n setIsClosing(false);\n onOpenChange(false);\n onClose?.();\n resetForm();\n // Restore focus\n if (previousActiveElement.current instanceof HTMLElement) {\n previousActiveElement.current.focus();\n }\n }, 200);\n }, [onOpenChange, onClose, resetForm]);\n\n const handleSubmit = useCallback(\n async (e: FormEvent) => {\n e.preventDefault();\n\n // Honeypot check (spam prevention)\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({\n baseUrl,\n publicKey,\n user,\n userToken,\n });\n\n const categoryPrefix = `[${category.charAt(0).toUpperCase() + category.slice(1)}] `;\n const fullDescription = description.trim()\n ? description.trim()\n : \"No additional details provided.\";\n\n const result = await client.create({\n description: `${categoryPrefix}${fullDescription}${isAnonymous && email ? `\\n\\n---\\nContact: ${email}` : \"\"}`,\n title: title.trim(),\n });\n\n setIsSuccess(true);\n onSubmit?.(result);\n\n setTimeout(() => {\n handleClose();\n }, AUTO_CLOSE_DELAY);\n } catch (err) {\n const message =\n err instanceof Error\n ? err.message\n : \"Something went wrong. Please try again.\";\n setError(message);\n } finally {\n setIsSubmitting(false);\n }\n },\n [\n honeypot,\n publicKey,\n title,\n description,\n email,\n category,\n baseUrl,\n user,\n userToken,\n isAnonymous,\n labels.required,\n onSubmit,\n handleClose,\n ]\n );\n\n const handleTrapFocus = useCallback((e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"Tab\" || !dialogRef.current) {\n return;\n }\n\n const focusable = dialogRef.current.querySelectorAll<HTMLElement>(\n 'button, input, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n const first = focusable[0];\n // biome-ignore lint/style/useAtIndex: NodeListOf doesn't support .at()\n const last = focusable[focusable.length - 1];\n\n if (!(first && last)) {\n return;\n }\n\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }, []);\n\n return {\n category,\n description,\n dialogRef,\n email,\n error,\n handleClose,\n handleSubmit,\n handleTrapFocus,\n honeypot,\n isAnonymous,\n isClosing,\n isSubmitting,\n isSuccess,\n labels,\n setCategory,\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 {\n CATEGORY_ICONS,\n type FeedbackDialogLabels,\n type FeedbackDialogProps,\n} from \"./react-feedback-dialog-types\";\n\nexport type {\n FeedbackCategory,\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 defaultCategory = \"feature\",\n categories = [\"feature\", \"bug\", \"question\"],\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 category,\n setCategory,\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 handleTrapFocus,\n } = useFeedbackDialog({\n baseUrl,\n defaultCategory,\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 {/* Overlay */}\n <button\n aria-label=\"Close feedback dialog\"\n className=\"reflet-overlay\"\n data-closing={isClosing ? \"true\" : undefined}\n onClick={handleClose}\n tabIndex={0}\n type=\"button\"\n />\n\n {/* Dialog */}\n {/* biome-ignore lint/a11y/noNoninteractiveElementInteractions: dialog role requires keyboard trap */}\n <div\n aria-label={labels.title}\n aria-modal=\"true\"\n className=\"reflet-dialog\"\n data-closing={isClosing ? \"true\" : undefined}\n onKeyDown={handleTrapFocus}\n ref={dialogRef}\n role=\"dialog\"\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 {/* Header */}\n <div className=\"reflet-header\">\n <h2 className=\"reflet-title\">{labels.title}</h2>\n <button\n aria-label=\"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 */}\n <form className=\"reflet-form\" onSubmit={handleSubmit}>\n {/* Category selector */}\n {categories.length > 1 && (\n <div className=\"reflet-categories\">\n {categories.map((cat) => (\n <button\n className=\"reflet-category\"\n data-selected={cat === category ? \"true\" : undefined}\n key={cat}\n onClick={() => setCategory(cat)}\n type=\"button\"\n >\n {CATEGORY_ICONS[cat]}{\" \"}\n {\n labels[\n `category${cat.charAt(0).toUpperCase()}${cat.slice(1)}` as keyof FeedbackDialogLabels\n ]\n }\n </button>\n ))}\n </div>\n )}\n\n {/* Title */}\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 {/* Description */}\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 {/* Email (anonymous only) */}\n {isAnonymous && (\n <div className=\"reflet-field\">\n <label className=\"reflet-label\" htmlFor=\"reflet-email\">\n {labels.emailLabel}\n </label>\n <input\n aria-label=\"Email address\"\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 {/* Honeypot */}\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 */}\n {error && <p className=\"reflet-error-msg\">{error}</p>}\n\n {/* Footer */}\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 {/* Powered by */}\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 </div>\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 type FeedbackCategory,\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 /** Categories to show */\n categories?: FeedbackCategory[];\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 /** Default selected category */\n defaultCategory?: FeedbackCategory;\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 defaultCategory = \"feature\",\n categories,\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 categories={categories}\n defaultCategory={defaultCategory}\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 [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const subscribe = useCallback(\n async (feedbackId: string) => {\n setIsLoading(true);\n setError(null);\n try {\n return await client.subscribe(feedbackId);\n } catch (err) {\n const wrapped =\n err instanceof Error ? err : new Error(\"Failed to subscribe\");\n setError(wrapped);\n throw wrapped;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n const unsubscribe = useCallback(\n async (feedbackId: string) => {\n setIsLoading(true);\n setError(null);\n try {\n return await client.unsubscribe(feedbackId);\n } catch (err) {\n const wrapped =\n err instanceof Error ? err : new Error(\"Failed to unsubscribe\");\n setError(wrapped);\n throw wrapped;\n } finally {\n setIsLoading(false);\n }\n },\n [client]\n );\n\n return { error, isLoading, subscribe, unsubscribe };\n}\n","import { useCallback, useEffect, useMemo, useState } 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\nexport function useUnreadChangelogCount(publicKey: string): {\n unreadCount: number;\n markAsRead: () => void;\n} {\n const { data: entries } = useChangelog();\n const [unreadCount, setUnreadCount] = useState(0);\n\n const storageKey = `${CHANGELOG_STORAGE_KEY_PREFIX}${publicKey}`;\n\n useEffect(() => {\n if (!entries || entries.length === 0) {\n setUnreadCount(0);\n return;\n }\n\n let lastSeen = 0;\n try {\n const stored = localStorage.getItem(storageKey);\n lastSeen = stored ? Number(stored) : 0;\n } catch {\n // localStorage unavailable\n }\n\n let count = 0;\n for (const entry of entries) {\n if (entry.publishedAt && entry.publishedAt > lastSeen) {\n count++;\n }\n }\n setUnreadCount(count);\n }, [entries, storageKey]);\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 setUnreadCount(0);\n }\n }, [entries, storageKey]);\n\n return { markAsRead, unreadCount };\n}\n"],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
//#region src/server.d.ts
|
|
1
2
|
/**
|
|
2
3
|
* Server-side utilities for Reflet SDK
|
|
3
4
|
* Works in Node.js, Edge runtimes, Cloudflare Workers, and Convex
|
|
@@ -13,22 +14,22 @@
|
|
|
13
14
|
* ```
|
|
14
15
|
*/
|
|
15
16
|
interface SignUserOptions {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
/** URL to user's avatar image */
|
|
18
|
+
avatar?: string;
|
|
19
|
+
/** User's email address */
|
|
20
|
+
email?: string;
|
|
21
|
+
/** User's unique ID in your system */
|
|
22
|
+
id: string;
|
|
23
|
+
/** Additional metadata */
|
|
24
|
+
metadata?: Record<string, unknown>;
|
|
25
|
+
/** User's display name */
|
|
26
|
+
name?: string;
|
|
26
27
|
}
|
|
27
28
|
interface SignedUserToken {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
/** Token expiration timestamp (ms) */
|
|
30
|
+
expiresAt: number;
|
|
31
|
+
/** The signed JWT token */
|
|
32
|
+
token: string;
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
34
35
|
* Sign a user token for secure client-side identification
|
|
@@ -40,5 +41,6 @@ declare function signUser(user: SignUserOptions, secretKey: string, expiresInSec
|
|
|
40
41
|
* Returns the user data if valid, null otherwise
|
|
41
42
|
*/
|
|
42
43
|
declare function verifyUser(token: string, secretKey: string): Promise<SignUserOptions | null>;
|
|
43
|
-
|
|
44
|
-
export { type SignUserOptions as RefletServerUser,
|
|
44
|
+
//#endregion
|
|
45
|
+
export { type SignUserOptions as RefletServerUser, SignUserOptions, SignedUserToken, signUser, verifyUser };
|
|
46
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;;;;;;;;;;;;UAsBiB;;EAEf;;EAEA;;EAEA;;EAEA,WAAW;;EAEX;;UAGe;;EAEf;;EAEA;;;;;;iBAOoB,SACpB,MAAM,iBACN,mBACA,4BACC,QAAQ;;;;;iBA2CW,WACpB,eACA,oBACC,QAAQ"}
|
package/dist/server.js
CHANGED
|
@@ -1,131 +1,132 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
//#region src/server.ts
|
|
2
|
+
/**
|
|
3
|
+
* Server-side utilities for Reflet SDK
|
|
4
|
+
* Works in Node.js, Edge runtimes, Cloudflare Workers, and Convex
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import { signUser } from 'reflet-sdk/server';
|
|
9
|
+
*
|
|
10
|
+
* const { token } = await signUser(
|
|
11
|
+
* { id: user.id, email: user.email, name: user.name },
|
|
12
|
+
* process.env.REFLET_SECRET_KEY!
|
|
13
|
+
* );
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
const PLUS_REGEX = /\+/g;
|
|
17
|
+
const SLASH_REGEX = /\//g;
|
|
18
|
+
const PADDING_REGEX = /[=]+$/;
|
|
19
|
+
const DASH_REGEX = /-/g;
|
|
20
|
+
const UNDERSCORE_REGEX = /_/g;
|
|
21
|
+
/**
|
|
22
|
+
* Sign a user token for secure client-side identification
|
|
23
|
+
* Uses HMAC-SHA256 via Web Crypto API (works in all runtimes)
|
|
24
|
+
*/
|
|
7
25
|
async function signUser(user, secretKey, expiresInSeconds = 86400) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
token: `${headerB64}.${payloadB64}.${signature}`,
|
|
35
|
-
expiresAt: exp * 1e3
|
|
36
|
-
};
|
|
26
|
+
if (!secretKey) throw new Error("signUser: secretKey is required");
|
|
27
|
+
if (!user.id) throw new Error("signUser: user.id is required");
|
|
28
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
29
|
+
const exp = now + expiresInSeconds;
|
|
30
|
+
const header = {
|
|
31
|
+
alg: "HS256",
|
|
32
|
+
typ: "JWT"
|
|
33
|
+
};
|
|
34
|
+
const payload = {
|
|
35
|
+
avatar: user.avatar,
|
|
36
|
+
email: user.email,
|
|
37
|
+
exp,
|
|
38
|
+
iat: now,
|
|
39
|
+
id: user.id,
|
|
40
|
+
metadata: user.metadata,
|
|
41
|
+
name: user.name,
|
|
42
|
+
sub: user.id
|
|
43
|
+
};
|
|
44
|
+
const headerB64 = base64UrlEncode(JSON.stringify(header));
|
|
45
|
+
const payloadB64 = base64UrlEncode(JSON.stringify(payload));
|
|
46
|
+
const signature = await createHmacSha256Signature(`${headerB64}.${payloadB64}`, await deriveSigningKey(secretKey));
|
|
47
|
+
return {
|
|
48
|
+
expiresAt: exp * 1e3,
|
|
49
|
+
token: `${headerB64}.${payloadB64}.${signature}`
|
|
50
|
+
};
|
|
37
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Verify a user token
|
|
54
|
+
* Returns the user data if valid, null otherwise
|
|
55
|
+
*/
|
|
38
56
|
async function verifyUser(token, secretKey) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
id: userId,
|
|
65
|
-
email: payload.email,
|
|
66
|
-
name: payload.name,
|
|
67
|
-
avatar: payload.avatar,
|
|
68
|
-
metadata: payload.metadata
|
|
69
|
-
};
|
|
70
|
-
} catch {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
57
|
+
if (!(token && secretKey)) return null;
|
|
58
|
+
const parts = token.split(".");
|
|
59
|
+
if (parts.length !== 3) return null;
|
|
60
|
+
const headerB64 = parts[0];
|
|
61
|
+
const payloadB64 = parts[1];
|
|
62
|
+
const signature = parts[2];
|
|
63
|
+
if (!constantTimeEqual(signature, await createHmacSha256Signature(`${headerB64}.${payloadB64}`, await deriveSigningKey(secretKey)))) return null;
|
|
64
|
+
try {
|
|
65
|
+
const payload = JSON.parse(base64UrlDecode(payloadB64));
|
|
66
|
+
if (payload.exp && Date.now() > payload.exp * 1e3) return null;
|
|
67
|
+
const userId = payload.id ?? payload.sub;
|
|
68
|
+
if (!userId) return null;
|
|
69
|
+
return {
|
|
70
|
+
avatar: payload.avatar,
|
|
71
|
+
email: payload.email,
|
|
72
|
+
id: userId,
|
|
73
|
+
metadata: payload.metadata,
|
|
74
|
+
name: payload.name
|
|
75
|
+
};
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
73
79
|
}
|
|
74
80
|
function base64UrlEncode(str) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
const base64 = btoa(binary);
|
|
81
|
-
return base64.replace(PLUS_REGEX, "-").replace(SLASH_REGEX, "_").replace(PADDING_REGEX, "");
|
|
81
|
+
const bytes = new TextEncoder().encode(str);
|
|
82
|
+
let binary = "";
|
|
83
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
84
|
+
return btoa(binary).replace(PLUS_REGEX, "-").replace(SLASH_REGEX, "_").replace(PADDING_REGEX, "");
|
|
82
85
|
}
|
|
83
86
|
function base64UrlDecode(str) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
bytes[i] = binary.charCodeAt(i);
|
|
91
|
-
}
|
|
92
|
-
return new TextDecoder().decode(bytes);
|
|
87
|
+
const padding = 4 - str.length % 4;
|
|
88
|
+
const base64 = (padding < 4 ? str + "=".repeat(padding) : str).replace(DASH_REGEX, "+").replace(UNDERSCORE_REGEX, "/");
|
|
89
|
+
const binary = atob(base64);
|
|
90
|
+
const bytes = new Uint8Array(binary.length);
|
|
91
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
92
|
+
return new TextDecoder().decode(bytes);
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Signing key is the SHA-256 hex digest of the secret key: Reflet stores exactly
|
|
96
|
+
* that digest, so it can verify tokens without ever holding the raw secret.
|
|
97
|
+
*/
|
|
98
|
+
async function deriveSigningKey(secretKey) {
|
|
99
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(secretKey));
|
|
100
|
+
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create HMAC-SHA256 signature using Web Crypto API
|
|
104
|
+
* Works in Node.js 18+, Edge runtimes, Cloudflare Workers, Convex, etc.
|
|
105
|
+
*/
|
|
94
106
|
async function createHmacSha256Signature(data, secret) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
cryptoKey,
|
|
108
|
-
messageData
|
|
109
|
-
);
|
|
110
|
-
const signatureArray = new Uint8Array(signatureBuffer);
|
|
111
|
-
let binary = "";
|
|
112
|
-
for (const byte of signatureArray) {
|
|
113
|
-
binary += String.fromCharCode(byte);
|
|
114
|
-
}
|
|
115
|
-
const base64 = btoa(binary);
|
|
116
|
-
return base64.replace(PLUS_REGEX, "-").replace(SLASH_REGEX, "_").replace(PADDING_REGEX, "");
|
|
107
|
+
const encoder = new TextEncoder();
|
|
108
|
+
const keyData = encoder.encode(secret);
|
|
109
|
+
const messageData = encoder.encode(data);
|
|
110
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyData, {
|
|
111
|
+
hash: "SHA-256",
|
|
112
|
+
name: "HMAC"
|
|
113
|
+
}, false, ["sign"]);
|
|
114
|
+
const signatureBuffer = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
115
|
+
const signatureArray = new Uint8Array(signatureBuffer);
|
|
116
|
+
let binary = "";
|
|
117
|
+
for (const byte of signatureArray) binary += String.fromCharCode(byte);
|
|
118
|
+
return btoa(binary).replace(PLUS_REGEX, "-").replace(SLASH_REGEX, "_").replace(PADDING_REGEX, "");
|
|
117
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Constant-time string comparison to prevent timing attacks
|
|
122
|
+
*/
|
|
118
123
|
function constantTimeEqual(a, b) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
for (let i = 0; i < a.length; i++) {
|
|
124
|
-
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
125
|
-
}
|
|
126
|
-
return result === 0;
|
|
124
|
+
if (a.length !== b.length) return false;
|
|
125
|
+
let result = 0;
|
|
126
|
+
for (let i = 0; i < a.length; i++) result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
127
|
+
return result === 0;
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
+
//#endregion
|
|
129
130
|
export { signUser, verifyUser };
|
|
130
|
-
|
|
131
|
+
|
|
131
132
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"names":[],"mappings":"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","file":"server.js","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 /** User's unique ID in your system */\n id: string;\n /** User's email address */\n email?: string;\n /** User's display name */\n name?: string;\n /** URL to user's avatar image */\n avatar?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface SignedUserToken {\n /** The signed JWT token */\n token: string;\n /** Token expiration timestamp (ms) */\n expiresAt: number;\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 sub: user.id,\n id: user.id,\n email: user.email,\n name: user.name,\n avatar: user.avatar,\n metadata: user.metadata,\n iat: now,\n exp,\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 secretKey\n );\n\n return {\n token: `${headerB64}.${payloadB64}.${signature}`,\n expiresAt: exp * 1000,\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 const [headerB64, payloadB64, signature] = parts;\n\n // Verify signature using constant-time comparison\n const expectedSignature = await createHmacSha256Signature(\n `${headerB64}.${payloadB64}`,\n 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 id: userId,\n email: payload.email,\n name: payload.name,\n avatar: payload.avatar,\n metadata: payload.metadata,\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 * 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 { name: \"HMAC\", hash: \"SHA-256\" },\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"]}
|
|
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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"}
|