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 +415 -127
- 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 -302
- 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 +1248 -340
- 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-U7TQSXOE.js +0 -282
- package/dist/chunk-U7TQSXOE.js.map +0 -1
- package/dist/index.js.map +0 -1
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 →
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
86
|
+
<YourApp />
|
|
77
87
|
</RefletProvider>
|
|
78
88
|
);
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
// Use hooks in your
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
130
|
-
|
|
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
|
|
152
|
-
| `user` | `RefletUser` | No |
|
|
153
|
-
| `userToken` | `string` | No | Signed JWT from your server (
|
|
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
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
| `
|
|
165
|
-
| `
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
|
176
|
-
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
182
|
-
| `
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
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
|
|
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(
|
|
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
|
|
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('
|
|
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
|
-
|
|
457
|
+
// User not identified or invalid token
|
|
458
|
+
console.log('Please sign in to vote');
|
|
251
459
|
} else if (error instanceof RefletError) {
|
|
252
|
-
|
|
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
|
-
|
|
466
|
+
### Common Error Scenarios
|
|
258
467
|
|
|
259
|
-
|
|
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
|
-
|
|
262
|
-
import { RefletProvider, useFeedbackList, useCreateFeedback, useVote } from 'reflet-sdk/react';
|
|
263
|
-
import { useState } from 'react';
|
|
476
|
+
---
|
|
264
477
|
|
|
265
|
-
|
|
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
|
-
|
|
273
|
-
e.preventDefault();
|
|
274
|
-
await createFeedback({ title, description: '' });
|
|
275
|
-
setTitle('');
|
|
276
|
-
refetch();
|
|
277
|
-
};
|
|
480
|
+
### Generic Pattern (Any Framework)
|
|
278
481
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
###
|
|
529
|
+
### React with Any State Management
|
|
316
530
|
|
|
317
531
|
```tsx
|
|
318
|
-
//
|
|
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
|
-
|
|
325
|
-
|
|
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=
|
|
330
|
-
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
|
-
|
|
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
|