reflet-sdk 0.1.4 → 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 CHANGED
@@ -18,22 +18,25 @@ bun add reflet-sdk
18
18
 
19
19
  ### 1. Get Your API Keys
20
20
 
21
- Go to your Reflet dashboard → Board Settings → API Keys & Widget to generate your keys:
21
+ Go to your Reflet dashboard → Settings → API Keys & Widget to generate your keys:
22
22
 
23
23
  - **Public Key** (`fb_pub_...`) - Safe to use in frontend code
24
24
  - **Secret Key** (`fb_sec_...`) - Keep secure, use only on your server
25
25
 
26
- ### 2. Basic Usage (Vanilla JavaScript/TypeScript)
26
+ ### 2. Basic Usage (Any JavaScript/TypeScript Environment)
27
+
28
+ The core SDK works in any JavaScript environment - browser, Node.js, Deno, Bun, or any framework.
27
29
 
28
30
  ```typescript
29
31
  import { Reflet } from 'reflet-sdk';
30
32
 
33
+ // Initialize the client
31
34
  const reflet = new Reflet({
32
35
  publicKey: 'fb_pub_your_key_here',
33
36
  user: {
34
- id: 'user_123',
35
- email: 'jane@example.com',
36
- name: 'Jane Doe',
37
+ id: 'user_123', // Required: unique user ID from your system
38
+ email: 'jane@example.com', // Optional: for notifications
39
+ name: 'Jane Doe', // Optional: display name
37
40
  },
38
41
  });
39
42
 
@@ -49,7 +52,7 @@ const { feedbackId } = await reflet.create({
49
52
  description: 'Would love to have a dark theme option!',
50
53
  });
51
54
 
52
- // Vote on feedback
55
+ // Vote on feedback (toggles vote on/off)
53
56
  await reflet.vote('feedback_id');
54
57
 
55
58
  // Add a comment
@@ -59,26 +62,33 @@ await reflet.comment({
59
62
  });
60
63
  ```
61
64
 
62
- ### 3. React Usage
65
+ ### 3. React Integration
66
+
67
+ For React applications, use the provided hooks for a better developer experience.
63
68
 
64
69
  ```tsx
65
70
  import { RefletProvider, useFeedbackList, useVote, useCreateFeedback } from 'reflet-sdk/react';
66
71
 
67
- // Wrap your app with the provider
72
+ // 1. Wrap your app with the provider (do this once at the root)
68
73
  function App() {
69
- const currentUser = useCurrentUser(); // Your auth system
74
+ // Get the current user from your authentication system
75
+ const currentUser = getCurrentUser(); // Replace with your auth logic
70
76
 
71
77
  return (
72
78
  <RefletProvider
73
79
  publicKey="fb_pub_your_key_here"
74
- user={currentUser}
80
+ user={currentUser ? {
81
+ id: currentUser.id,
82
+ email: currentUser.email,
83
+ name: currentUser.name,
84
+ } : undefined}
75
85
  >
76
- <FeedbackPage />
86
+ <YourApp />
77
87
  </RefletProvider>
78
88
  );
79
89
  }
80
90
 
81
- // Use hooks in your components
91
+ // 2. Use hooks anywhere in your app
82
92
  function FeedbackPage() {
83
93
  const { data, isLoading, error } = useFeedbackList({ sortBy: 'votes' });
84
94
  const { mutate: vote } = useVote();
@@ -103,39 +113,221 @@ function FeedbackPage() {
103
113
  }
104
114
  ```
105
115
 
106
- ### 4. Server-Side User Signing (Most Secure)
116
+ ### 4. Floating Feedback Widget
117
+
118
+ One component adds a floating button to your app. Opening it screenshots the
119
+ current viewport, lets the user draw on that screenshot, and lets them point at
120
+ an element so the report carries the React component behind it.
121
+
122
+ ```bash
123
+ npx reflet-cli init
124
+ ```
125
+
126
+ Or mount it yourself, once, as the last child of your app shell:
127
+
128
+ ```tsx
129
+ import { RefletFeedback } from 'reflet-sdk/feedback';
107
130
 
108
- For production apps, sign user tokens on your server to prevent spoofing:
131
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
132
+ return (
133
+ <html lang="en">
134
+ <body>
135
+ {children}
136
+ <RefletFeedback publicKey={process.env.NEXT_PUBLIC_REFLET_PUBLIC_KEY} />
137
+ </body>
138
+ </html>
139
+ );
140
+ }
141
+ ```
142
+
143
+ The entry ships its own `"use client"` directive, so a Next.js layout can stay a
144
+ Server Component. With Vite, read the key from
145
+ `import.meta.env.VITE_REFLET_PUBLIC_KEY` and render the widget next to `<App />`.
146
+
147
+ Every report carries the URL, page title, browser, OS, device, viewport, locale
148
+ and timezone, plus the last 30 console errors and warnings. Pointing at an
149
+ element adds a selector that resolves back to it, the React component stack, and
150
+ the source file and line when the build exposes them (development and preview
151
+ builds do; production builds strip React's debug data).
152
+
153
+ | Prop | Type | Default | Description |
154
+ |------|------|---------|-------------|
155
+ | `publicKey` | `string` | — | Your `fb_pub_…` key. Optional inside `RefletProvider`. |
156
+ | `user` | `RefletUser` | — | Who is reporting. Hides the email field. |
157
+ | `position` | `"bottom-right" \| "bottom-left" \| "top-right" \| "top-left"` | `"bottom-right"` | Which corner the button sits in. |
158
+ | `enabled` | `boolean` | `true` | Render the widget. Gate it to staff or beta users. |
159
+ | `dismissForDays` | `number` | — | Let a reporter hide the launcher in this browser for this many days. |
160
+ | `captureOnOpen` | `boolean` | `true` | Screenshot as soon as the panel opens. |
161
+ | `captureConsole` | `boolean` | `true` | Record console errors and warnings. |
162
+ | `hotkey` | `string \| null` | `null` | Shortcut toggling the panel, e.g. `"mod+shift+f"`. |
163
+ | `theme` | `"auto" \| "light" \| "dark"` | `"auto"` | Colour scheme. |
164
+ | `primaryColor` | `string` | — | Any CSS color for the button and accents. |
165
+ | `offset` | `number` | `20` | Pixels between the button and the viewport edge. |
166
+ | `metadata` | `Record<string, string>` | — | Extra fields merged into every report. |
167
+ | `categories` | `("bug" \| "idea" \| "question")[]` | all three | Category chips to show. |
168
+ | `labels` | `Partial<FeedbackWidgetLabels>` | — | Override any string for i18n. |
169
+ | `onSubmit` | `(result: { feedbackId: string }) => void` | — | Fires after a successful send. |
170
+
171
+ The panel renders in a shadow root, so your CSS cannot reach it and its CSS
172
+ cannot reach your app. Screenshots are rendered from the DOM rather than through
173
+ `getDisplayMedia`, so users are never asked for a screen-share permission —
174
+ the trade-off is that cross-origin images without CORS headers, iframes and
175
+ canvas content may come out blank.
176
+
177
+ ### 5. Server-Side User Signing (required for voting and commenting)
178
+
179
+ `user` is a client-asserted identity: the browser can claim to be anyone, so
180
+ the API accepts it only to attribute a report or a survey answer. Voting,
181
+ commenting and subscribing require a token signed on your server with
182
+ `signUser` — anything else is rejected with a 401.
183
+
184
+ **On your server** (works with any backend: Express, Fastify, Hono, serverless functions, etc.):
109
185
 
110
186
  ```typescript
111
- // On your server (Next.js API route, Express, etc.)
112
187
  import { signUser } from 'reflet-sdk/server';
113
188
 
114
- export async function getUserToken(user: User) {
189
+ // This function uses Web Crypto API - works in Node.js 18+,
190
+ // Deno, Bun, Cloudflare Workers, Vercel Edge, etc.
191
+ export async function generateUserToken(user: YourUserType) {
115
192
  const { token, expiresAt } = await signUser(
116
193
  {
117
- id: user.id,
118
- email: user.email,
119
- name: user.name,
120
- avatar: user.avatarUrl,
194
+ id: user.id, // Required: unique identifier
195
+ email: user.email, // Optional
196
+ name: user.name, // Optional
197
+ avatar: user.avatar, // Optional
121
198
  },
122
- process.env.REFLET_SECRET_KEY!
199
+ process.env.REFLET_SECRET_KEY! // Your secret key from Reflet dashboard
123
200
  );
124
201
 
125
202
  return { token, expiresAt };
126
203
  }
127
204
  ```
128
205
 
129
- ```tsx
130
- // On the client
206
+ **On the client** (pass the token from your server):
207
+
208
+ ```typescript
131
209
  import { Reflet } from 'reflet-sdk';
132
210
 
133
211
  const reflet = new Reflet({
134
212
  publicKey: 'fb_pub_your_key_here',
135
- userToken: tokenFromServer, // JWT from your server
213
+ userToken: tokenFromServer, // JWT received from your server
214
+ });
215
+ ```
216
+
217
+ ---
218
+
219
+ ## Important: What NOT To Do
220
+
221
+ ### Security
222
+
223
+ ```typescript
224
+ // ❌ WRONG: Never expose your secret key in client-side code
225
+ const reflet = new Reflet({
226
+ publicKey: 'fb_pub_xxx',
227
+ secretKey: 'fb_sec_xxx', // NEVER DO THIS - secret keys are for servers only
136
228
  });
229
+
230
+ // ❌ WRONG: Never hardcode secret keys
231
+ const token = await signUser(user, 'fb_sec_hardcoded_key');
232
+
233
+ // ✅ CORRECT: Use environment variables on the server
234
+ const token = await signUser(user, process.env.REFLET_SECRET_KEY!);
137
235
  ```
138
236
 
237
+ ### User Identification
238
+
239
+ ```typescript
240
+ // ❌ WRONG: Don't use unstable or guessable user IDs
241
+ const reflet = new Reflet({
242
+ publicKey: 'fb_pub_xxx',
243
+ user: { id: 'user@email.com' }, // Email as ID - can change, easily guessed
244
+ });
245
+
246
+ // ❌ WRONG: Don't use sequential IDs
247
+ const reflet = new Reflet({
248
+ publicKey: 'fb_pub_xxx',
249
+ user: { id: '1' }, // Sequential - easily enumerable
250
+ });
251
+
252
+ // ✅ CORRECT: Use your system's stable, unique user identifier
253
+ const reflet = new Reflet({
254
+ publicKey: 'fb_pub_xxx',
255
+ user: { id: 'usr_a1b2c3d4e5' }, // UUID, CUID, or database ID
256
+ });
257
+ ```
258
+
259
+ ### Client Initialization
260
+
261
+ ```typescript
262
+ // ❌ WRONG: Don't create multiple client instances
263
+ function handleVote(id: string) {
264
+ const reflet = new Reflet({ publicKey: 'fb_pub_xxx' }); // New instance every call
265
+ await reflet.vote(id);
266
+ }
267
+
268
+ // ✅ CORRECT: Create one instance and reuse it
269
+ const reflet = new Reflet({ publicKey: 'fb_pub_xxx', user: currentUser });
270
+
271
+ function handleVote(id: string) {
272
+ await reflet.vote(id);
273
+ }
274
+ ```
275
+
276
+ ### React Provider
277
+
278
+ ```tsx
279
+ // ❌ WRONG: Don't use hooks outside the provider
280
+ function App() {
281
+ return (
282
+ <div>
283
+ <FeedbackList /> {/* Error: No RefletProvider */}
284
+ <RefletProvider publicKey="fb_pub_xxx">
285
+ <OtherStuff />
286
+ </RefletProvider>
287
+ </div>
288
+ );
289
+ }
290
+
291
+ // ✅ CORRECT: Wrap at the root, use hooks inside
292
+ function App() {
293
+ return (
294
+ <RefletProvider publicKey="fb_pub_xxx" user={currentUser}>
295
+ <FeedbackList />
296
+ <OtherStuff />
297
+ </RefletProvider>
298
+ );
299
+ }
300
+ ```
301
+
302
+ ### Token Handling
303
+
304
+ ```typescript
305
+ // ❌ WRONG: Don't sign tokens on the client
306
+ import { signUser } from 'reflet-sdk/server';
307
+ // This exposes your secret key in client bundle!
308
+
309
+ // ❌ WRONG: Don't mix user and userToken
310
+ const reflet = new Reflet({
311
+ publicKey: 'fb_pub_xxx',
312
+ user: { id: '123' },
313
+ userToken: 'jwt_token', // Pick one, not both
314
+ });
315
+
316
+ // ✅ CORRECT: Use user for simple cases (client-side identification)
317
+ const reflet = new Reflet({
318
+ publicKey: 'fb_pub_xxx',
319
+ user: { id: '123', email: 'user@example.com' },
320
+ });
321
+
322
+ // ✅ CORRECT: Use userToken for production (server-signed)
323
+ const reflet = new Reflet({
324
+ publicKey: 'fb_pub_xxx',
325
+ userToken: tokenFromYourServer,
326
+ });
327
+ ```
328
+
329
+ ---
330
+
139
331
  ## API Reference
140
332
 
141
333
  ### `Reflet` Client
@@ -148,63 +340,75 @@ const reflet = new Reflet(config: RefletConfig);
148
340
 
149
341
  | Option | Type | Required | Description |
150
342
  |--------|------|----------|-------------|
151
- | `publicKey` | `string` | Yes | Your board's public API key |
152
- | `user` | `RefletUser` | No | User information for identification |
153
- | `userToken` | `string` | No | Signed JWT from your server (alternative to `user`) |
343
+ | `publicKey` | `string` | Yes | Your organization's public API key (`fb_pub_...`) |
344
+ | `user` | `RefletUser` | No | Client-asserted identity — attributes reports only, never grants voting or commenting |
345
+ | `userToken` | `string` | No | Signed JWT from your server (`signUser`), required for voting, commenting and subscribing |
154
346
  | `baseUrl` | `string` | No | Custom API endpoint (defaults to Reflet production API) |
155
347
 
348
+ > **Note:** Provide either `user` OR `userToken`, not both. With neither, the user can only read feedback and submit anonymous reports. With `user` alone, reports are attributed but voting, commenting and subscribing stay unavailable.
349
+
156
350
  #### Methods
157
351
 
158
- | Method | Description |
159
- |--------|-------------|
160
- | `list(params?)` | List feedback with optional filtering |
161
- | `get(feedbackId)` | Get single feedback with full details |
162
- | `create(params)` | Submit new feedback |
163
- | `vote(feedbackId, type?)` | Vote on feedback (toggle) |
164
- | `comment(params)` | Add a comment |
165
- | `subscribe(feedbackId)` | Subscribe to updates |
166
- | `unsubscribe(feedbackId)` | Unsubscribe from updates |
167
- | `getConfig()` | Get board configuration |
168
- | `getRoadmap()` | Get roadmap data |
169
- | `getChangelog(limit?)` | Get changelog entries |
352
+ | Method | Description | Requires User |
353
+ |--------|-------------|---------------|
354
+ | `list(params?)` | List feedback with optional filtering | No |
355
+ | `get(feedbackId)` | Get single feedback with full details | No |
356
+ | `getConfig()` | Get organization configuration | No |
357
+ | `getRoadmap()` | Get roadmap data | No |
358
+ | `getChangelog(limit?)` | Get changelog entries | No |
359
+ | `create(params)` | Submit new feedback | **Yes** |
360
+ | `vote(feedbackId, type?)` | Vote on feedback (toggle) | **Yes** |
361
+ | `comment(params)` | Add a comment | **Yes** |
362
+ | `getComments(feedbackId)` | Get comments for a feedback | No |
363
+ | `subscribe(feedbackId)` | Subscribe to updates | **Yes** |
364
+ | `unsubscribe(feedbackId)` | Unsubscribe from updates | **Yes** |
170
365
 
171
366
  ### React Hooks
172
367
 
173
- | Hook | Description |
174
- |------|-------------|
175
- | `useFeedbackList(options?)` | Fetch feedback list with filters |
176
- | `useFeedback(feedbackId)` | Fetch single feedback item |
177
- | `useComments(feedbackId)` | Fetch comments for feedback |
178
- | `useVote()` | Vote mutation hook |
179
- | `useCreateFeedback()` | Create feedback mutation hook |
180
- | `useAddComment()` | Add comment mutation hook |
181
- | `useSubscription()` | Subscribe/unsubscribe hooks |
182
- | `useBoardConfig()` | Fetch board configuration |
183
- | `useRoadmap()` | Fetch roadmap data |
184
- | `useChangelog(limit?)` | Fetch changelog entries |
368
+ All hooks must be used inside a `<RefletProvider>`.
369
+
370
+ | Hook | Description | Requires User |
371
+ |------|-------------|---------------|
372
+ | `useFeedbackList(options?)` | Fetch feedback list with filters | No |
373
+ | `useFeedback(feedbackId)` | Fetch single feedback item | No |
374
+ | `useComments(feedbackId)` | Fetch comments for feedback | No |
375
+ | `useOrganizationConfig()` | Fetch organization configuration | No |
376
+ | `useRoadmap()` | Fetch roadmap data | No |
377
+ | `useChangelog(limit?)` | Fetch changelog entries | No |
378
+ | `useVote()` | Vote mutation hook | **Yes** |
379
+ | `useCreateFeedback()` | Create feedback mutation hook | **Yes** |
380
+ | `useAddComment()` | Add comment mutation hook | **Yes** |
381
+ | `useSubscription()` | Subscribe/unsubscribe hooks | **Yes** |
185
382
 
186
383
  ### Server Utilities
187
384
 
188
- Works in Node.js, Edge runtimes, Cloudflare Workers, and Convex.
385
+ Works in any JavaScript runtime with Web Crypto API support: Node.js 18+, Deno, Bun, Cloudflare Workers, Vercel Edge, AWS Lambda, etc.
189
386
 
190
387
  ```typescript
191
388
  import { signUser, verifyUser } from 'reflet-sdk/server';
192
389
 
193
390
  // Sign a user token (async - uses Web Crypto API)
194
- const { token, expiresAt } = await signUser(user, secretKey, expiresInSeconds?);
391
+ const { token, expiresAt } = await signUser(
392
+ { id: 'user_123', email: 'user@example.com', name: 'Jane' },
393
+ secretKey,
394
+ expiresInSeconds // Optional, defaults to 24 hours
395
+ );
195
396
 
196
- // Verify a token (optional - server-side validation)
397
+ // Verify a token (optional - for server-side validation)
197
398
  const user = await verifyUser(token, secretKey);
399
+ // Returns user data if valid, null if invalid/expired
198
400
  ```
199
401
 
402
+ ---
403
+
200
404
  ## Types
201
405
 
202
406
  ```typescript
203
407
  interface RefletUser {
204
- id: string;
205
- email?: string;
206
- name?: string;
207
- avatar?: string;
408
+ id: string; // Required: unique identifier from your system
409
+ email?: string; // Optional: used for notifications
410
+ name?: string; // Optional: display name
411
+ avatar?: string; // Optional: avatar URL
208
412
  }
209
413
 
210
414
  interface FeedbackItem {
@@ -214,14 +418,14 @@ interface FeedbackItem {
214
418
  status: FeedbackStatus;
215
419
  voteCount: number;
216
420
  commentCount: number;
217
- hasVoted: boolean;
421
+ hasVoted: boolean; // Whether current user voted
218
422
  author?: FeedbackAuthor;
219
423
  tags: FeedbackTag[];
220
424
  createdAt: string;
221
425
  }
222
426
 
223
427
  interface FeedbackDetail extends FeedbackItem {
224
- isSubscribed: boolean;
428
+ isSubscribed: boolean; // Whether current user is subscribed
225
429
  comments: Comment[];
226
430
  }
227
431
 
@@ -234,111 +438,195 @@ type FeedbackStatus =
234
438
  | 'closed';
235
439
  ```
236
440
 
441
+ ---
442
+
237
443
  ## Error Handling
238
444
 
239
- The SDK provides typed errors for better error handling:
445
+ The SDK provides typed errors for precise error handling:
240
446
 
241
447
  ```typescript
242
448
  import { RefletError, RefletAuthError, RefletNotFoundError } from 'reflet-sdk';
243
449
 
244
450
  try {
245
- await reflet.vote('invalid_id');
451
+ await reflet.vote('feedback_id');
246
452
  } catch (error) {
247
453
  if (error instanceof RefletNotFoundError) {
454
+ // Feedback item doesn't exist
248
455
  console.log('Feedback not found');
249
456
  } else if (error instanceof RefletAuthError) {
250
- console.log('Authentication required');
457
+ // User not identified or invalid token
458
+ console.log('Please sign in to vote');
251
459
  } else if (error instanceof RefletError) {
252
- console.log('API error:', error.message);
460
+ // Other API errors (rate limit, server error, etc.)
461
+ console.log('Error:', error.message, 'Status:', error.status);
253
462
  }
254
463
  }
255
464
  ```
256
465
 
257
- ## Examples
466
+ ### Common Error Scenarios
258
467
 
259
- ### Feedback Widget Component
468
+ | Error | Cause | Solution |
469
+ |-------|-------|----------|
470
+ | `RefletAuthError` | No signed token provided | Pass a `userToken` from `signUser`; `user` alone cannot vote or comment |
471
+ | `RefletAuthError` | Invalid or expired token | Generate a new token on your server |
472
+ | `RefletNotFoundError` | Invalid feedback ID | Check the ID exists |
473
+ | `RefletError` (403) | Organization is private | Use a secret key or make organization public |
474
+ | `RefletError` (429) | Rate limited | Reduce request frequency |
260
475
 
261
- ```tsx
262
- import { RefletProvider, useFeedbackList, useCreateFeedback, useVote } from 'reflet-sdk/react';
263
- import { useState } from 'react';
476
+ ---
264
477
 
265
- function FeedbackWidget() {
266
- const [isOpen, setIsOpen] = useState(false);
267
- const { data, refetch } = useFeedbackList({ limit: 5, sortBy: 'votes' });
268
- const { mutate: createFeedback, isLoading } = useCreateFeedback();
269
- const { mutate: vote } = useVote();
270
- const [title, setTitle] = useState('');
478
+ ## Framework Integration Examples
271
479
 
272
- const handleSubmit = async (e: React.FormEvent) => {
273
- e.preventDefault();
274
- await createFeedback({ title, description: '' });
275
- setTitle('');
276
- refetch();
277
- };
480
+ ### Generic Pattern (Any Framework)
278
481
 
279
- return (
280
- <div className="feedback-widget">
281
- <button onClick={() => setIsOpen(!isOpen)}>
282
- Feedback
283
- </button>
284
-
285
- {isOpen && (
286
- <div className="feedback-panel">
287
- <form onSubmit={handleSubmit}>
288
- <input
289
- value={title}
290
- onChange={(e) => setTitle(e.target.value)}
291
- placeholder="What's on your mind?"
292
- />
293
- <button type="submit" disabled={isLoading}>
294
- Submit
295
- </button>
296
- </form>
297
-
298
- <ul>
299
- {data?.items.map((item) => (
300
- <li key={item.id}>
301
- <button onClick={() => vote({ feedbackId: item.id })}>
302
- {item.voteCount}
303
- </button>
304
- <span>{item.title}</span>
305
- </li>
306
- ))}
307
- </ul>
308
- </div>
309
- )}
310
- </div>
482
+ ```typescript
483
+ // 1. Create a singleton client instance
484
+ // feedback-client.ts
485
+ import { Reflet } from 'reflet-sdk';
486
+
487
+ let refletInstance: Reflet | null = null;
488
+
489
+ export function getRefletClient(user?: { id: string; email?: string; name?: string }) {
490
+ if (!refletInstance || user) {
491
+ refletInstance = new Reflet({
492
+ publicKey: process.env.REFLET_PUBLIC_KEY || 'fb_pub_xxx',
493
+ user,
494
+ });
495
+ }
496
+ return refletInstance;
497
+ }
498
+
499
+ // 2. Use it anywhere
500
+ const reflet = getRefletClient(currentUser);
501
+ const feedback = await reflet.list();
502
+ ```
503
+
504
+ ### Server-Side Token Generation (Any Backend)
505
+
506
+ ```typescript
507
+ // api/reflet-token.ts (or your preferred API route pattern)
508
+ import { signUser } from 'reflet-sdk/server';
509
+
510
+ export async function handleTokenRequest(authenticatedUser: YourUserType) {
511
+ // Verify user is authenticated with YOUR auth system first
512
+ if (!authenticatedUser) {
513
+ throw new Error('Unauthorized');
514
+ }
515
+
516
+ const { token, expiresAt } = await signUser(
517
+ {
518
+ id: authenticatedUser.id,
519
+ email: authenticatedUser.email,
520
+ name: authenticatedUser.displayName,
521
+ },
522
+ process.env.REFLET_SECRET_KEY!
311
523
  );
524
+
525
+ return { token, expiresAt };
312
526
  }
313
527
  ```
314
528
 
315
- ### Next.js App Router Integration
529
+ ### React with Any State Management
316
530
 
317
531
  ```tsx
318
- // app/providers.tsx
319
- 'use client';
320
-
532
+ // Works with Redux, Zustand, Jotai, MobX, or plain React state
321
533
  import { RefletProvider } from 'reflet-sdk/react';
322
- import { useSession } from 'next-auth/react';
323
534
 
324
- export function Providers({ children }: { children: React.ReactNode }) {
325
- const { data: session } = useSession();
535
+ function App() {
536
+ // Get user from your state management solution
537
+ const user = useYourAuthHook(); // useSelector, useStore, useAtom, etc.
326
538
 
327
539
  return (
328
540
  <RefletProvider
329
- publicKey={process.env.NEXT_PUBLIC_REFLET_KEY!}
330
- user={session?.user ? {
331
- id: session.user.id,
332
- email: session.user.email!,
333
- name: session.user.name!,
334
- } : undefined}
541
+ publicKey="fb_pub_xxx"
542
+ user={user ? { id: user.id, email: user.email, name: user.name } : undefined}
335
543
  >
336
- {children}
544
+ <YourApp />
337
545
  </RefletProvider>
338
546
  );
339
547
  }
340
548
  ```
341
549
 
550
+ ### Vanilla JavaScript (No Framework)
551
+
552
+ ```html
553
+ <script type="module">
554
+ import { Reflet } from 'https://esm.sh/reflet-sdk';
555
+
556
+ const reflet = new Reflet({
557
+ publicKey: 'fb_pub_xxx',
558
+ user: { id: 'user_123' },
559
+ });
560
+
561
+ // Load and display feedback
562
+ const { items } = await reflet.list();
563
+
564
+ document.getElementById('feedback-list').innerHTML = items
565
+ .map(item => `
566
+ <div>
567
+ <h3>${item.title}</h3>
568
+ <p>${item.description}</p>
569
+ <button onclick="vote('${item.id}')">${item.voteCount} votes</button>
570
+ </div>
571
+ `)
572
+ .join('');
573
+
574
+ window.vote = async (id) => {
575
+ await reflet.vote(id);
576
+ location.reload(); // Simple refresh, or update DOM manually
577
+ };
578
+ </script>
579
+ ```
580
+
581
+ ---
582
+
583
+ ## Troubleshooting
584
+
585
+ ### "Authentication required" error
586
+
587
+ **Cause:** Trying to vote, comment, or create feedback without user identification.
588
+
589
+ **Solution:** Provide a server-signed `userToken` when initializing the client:
590
+
591
+ ```typescript
592
+ const reflet = new Reflet({
593
+ publicKey: 'fb_pub_xxx',
594
+ user: { id: 'your_user_id' }, // Add this
595
+ });
596
+ ```
597
+
598
+ ### "Organization not found" or 404 errors
599
+
600
+ **Cause:** Invalid public key or organization doesn't exist.
601
+
602
+ **Solution:**
603
+ 1. Verify your public key in Reflet dashboard → Settings → API Keys
604
+ 2. Ensure the organization exists and is active
605
+
606
+ ### "Network error" or connection issues
607
+
608
+ **Cause:** Cannot reach Reflet API.
609
+
610
+ **Solution:**
611
+ 1. Check your internet connection
612
+ 2. Verify no firewall/proxy is blocking requests
613
+ 3. The SDK connects to `https://harmless-clam-802.convex.site` by default
614
+
615
+ ### Token expired errors
616
+
617
+ **Cause:** Server-signed token has expired (default: 24 hours).
618
+
619
+ **Solution:** Generate and send a fresh token from your server:
620
+
621
+ ```typescript
622
+ // On your server
623
+ const { token } = await signUser(user, secretKey, 86400); // 24 hours
624
+
625
+ // Refresh before expiry or on 401 errors
626
+ ```
627
+
628
+ ---
629
+
342
630
  ## License
343
631
 
344
632
  MIT