@feedvalue/core 0.1.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.
@@ -0,0 +1,626 @@
1
+ /**
2
+ * @feedvalue/core - Type Definitions
3
+ *
4
+ * Core types for the FeedValue SDK. These types are shared across
5
+ * all framework packages (React, Vue) and the vanilla API.
6
+ */
7
+ /**
8
+ * Widget position on screen
9
+ */
10
+ type WidgetPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | 'center';
11
+ /**
12
+ * Widget theme mode
13
+ */
14
+ type WidgetTheme = 'light' | 'dark' | 'auto';
15
+ /**
16
+ * Trigger icon type
17
+ */
18
+ type TriggerIconType = 'none' | 'chat' | 'message' | 'feedback' | 'comment' | 'help' | 'lightbulb';
19
+ /**
20
+ * Custom field types
21
+ */
22
+ type CustomFieldType = 'text' | 'email' | 'emoji';
23
+ /**
24
+ * Emoji sentiment values
25
+ */
26
+ type EmojiSentiment = 'angry' | 'disappointed' | 'satisfied' | 'excited';
27
+ /**
28
+ * Custom field definition
29
+ */
30
+ interface CustomField {
31
+ id: string;
32
+ type: CustomFieldType;
33
+ label: string;
34
+ placeholder?: string;
35
+ required: boolean;
36
+ order: number;
37
+ }
38
+ /**
39
+ * Widget styling configuration
40
+ */
41
+ interface WidgetStyling {
42
+ primaryColor: string;
43
+ backgroundColor: string;
44
+ textColor: string;
45
+ buttonTextColor: string;
46
+ borderRadius: string;
47
+ customCSS?: string | undefined;
48
+ }
49
+ /**
50
+ * Widget UI configuration
51
+ */
52
+ interface WidgetUIConfig {
53
+ position: WidgetPosition;
54
+ triggerText: string;
55
+ triggerIcon: TriggerIconType;
56
+ formTitle: string;
57
+ submitButtonText: string;
58
+ thankYouMessage: string;
59
+ showBranding: boolean;
60
+ customFields?: CustomField[] | undefined;
61
+ }
62
+ /**
63
+ * Full widget configuration (from API)
64
+ */
65
+ interface WidgetConfig {
66
+ widgetId: string;
67
+ widgetKey: string;
68
+ appId: string;
69
+ config: WidgetUIConfig;
70
+ styling: WidgetStyling;
71
+ }
72
+ /**
73
+ * Options passed to FeedValue.init()
74
+ */
75
+ interface FeedValueOptions {
76
+ /** Widget ID from FeedValue dashboard */
77
+ widgetId: string;
78
+ /** API base URL (defaults to production) */
79
+ apiBaseUrl?: string | undefined;
80
+ /** Enable debug logging */
81
+ debug?: boolean | undefined;
82
+ /** Initial configuration overrides */
83
+ config?: Partial<FeedValueConfig> | undefined;
84
+ /**
85
+ * Headless mode - disables all DOM rendering.
86
+ * Use this when you want full control over the UI.
87
+ * The SDK will still fetch config and provide all API methods
88
+ * (open, close, submit, etc.) but won't render any elements.
89
+ *
90
+ * @default false
91
+ */
92
+ headless?: boolean | undefined;
93
+ }
94
+ /**
95
+ * Runtime configuration (can be changed after init)
96
+ */
97
+ interface FeedValueConfig {
98
+ /** Widget theme */
99
+ theme: WidgetTheme;
100
+ /** Widget position override */
101
+ position?: WidgetPosition;
102
+ /** Auto-show widget on init */
103
+ autoShow: boolean;
104
+ /** Enable debug logging */
105
+ debug: boolean;
106
+ /** Locale for internationalization */
107
+ locale: string;
108
+ }
109
+ /**
110
+ * Widget state (for framework integration)
111
+ * Used by useSyncExternalStore in React and reactive refs in Vue
112
+ */
113
+ interface FeedValueState {
114
+ /** Widget is ready (config loaded) */
115
+ isReady: boolean;
116
+ /** Modal is open */
117
+ isOpen: boolean;
118
+ /** Widget is visible on page */
119
+ isVisible: boolean;
120
+ /** Current error (if any) */
121
+ error: Error | null;
122
+ /** Feedback submission in progress */
123
+ isSubmitting: boolean;
124
+ }
125
+ /**
126
+ * Feedback submission data
127
+ */
128
+ interface FeedbackData {
129
+ /** Feedback message content */
130
+ message: string;
131
+ /** Sentiment rating (from emoji field) */
132
+ sentiment?: EmojiSentiment | undefined;
133
+ /** Custom field values */
134
+ customFieldValues?: Record<string, string> | undefined;
135
+ /** Page metadata */
136
+ metadata?: FeedbackMetadata | undefined;
137
+ }
138
+ /**
139
+ * Feedback metadata (auto-collected)
140
+ */
141
+ interface FeedbackMetadata {
142
+ /** Current page URL */
143
+ page_url: string;
144
+ /** Referrer URL */
145
+ referrer?: string | undefined;
146
+ /** User agent string */
147
+ user_agent?: string | undefined;
148
+ }
149
+ /**
150
+ * Event types emitted by FeedValue
151
+ */
152
+ interface FeedValueEvents {
153
+ /** Widget is ready (config loaded) */
154
+ ready: () => void;
155
+ /** Modal opened */
156
+ open: () => void;
157
+ /** Modal closed */
158
+ close: () => void;
159
+ /** Feedback submitted successfully */
160
+ submit: (feedback: FeedbackData) => void;
161
+ /** Error occurred */
162
+ error: (error: Error) => void;
163
+ /** State changed (for generic listeners) */
164
+ stateChange: (state: FeedValueState) => void;
165
+ }
166
+ /**
167
+ * Event handler type
168
+ */
169
+ type EventHandler<K extends keyof FeedValueEvents> = FeedValueEvents[K];
170
+ /**
171
+ * User data for identification
172
+ */
173
+ interface UserData {
174
+ /** User email */
175
+ email?: string;
176
+ /** User display name */
177
+ name?: string;
178
+ /** Custom user properties */
179
+ [key: string]: string | undefined;
180
+ }
181
+ /**
182
+ * User traits for identify()
183
+ */
184
+ interface UserTraits {
185
+ /** User email */
186
+ email?: string;
187
+ /** User display name */
188
+ name?: string;
189
+ /** Company or organization */
190
+ company?: string;
191
+ /** User plan/tier */
192
+ plan?: string;
193
+ /** Custom traits */
194
+ [key: string]: unknown;
195
+ }
196
+ /**
197
+ * User identification data for API submissions
198
+ * Sent with feedback when identify() or setData() has been called
199
+ */
200
+ interface SubmissionUserData {
201
+ /** User ID (from identify()) */
202
+ user_id?: string;
203
+ /** User email (from setData or identify traits) */
204
+ email?: string;
205
+ /** User display name (from setData or identify traits) */
206
+ name?: string;
207
+ /** Additional user traits (from identify()) */
208
+ traits?: Record<string, unknown>;
209
+ /** Custom user data (from setData()) */
210
+ custom_data?: Record<string, string>;
211
+ }
212
+ /**
213
+ * API response for widget config
214
+ */
215
+ interface ConfigResponse {
216
+ widget_id: string;
217
+ widget_key: string;
218
+ config: Partial<WidgetUIConfig>;
219
+ styling: Partial<WidgetStyling>;
220
+ submission_token?: string;
221
+ token_expires_at?: number;
222
+ }
223
+ /**
224
+ * API response for feedback submission
225
+ */
226
+ interface FeedbackResponse {
227
+ success: boolean;
228
+ feedback_id: string;
229
+ message?: string;
230
+ blocked?: boolean;
231
+ }
232
+ /**
233
+ * FeedValue instance interface
234
+ * This is the public API for the widget
235
+ */
236
+ interface FeedValueInstance {
237
+ /** Initialize the widget (fetch config, prepare DOM) */
238
+ init(): Promise<void>;
239
+ /** Destroy the widget (cleanup DOM, event listeners) */
240
+ destroy(): void;
241
+ /** Open the feedback modal */
242
+ open(): void;
243
+ /** Close the feedback modal */
244
+ close(): void;
245
+ /** Toggle the feedback modal */
246
+ toggle(): void;
247
+ /** Show the trigger button */
248
+ show(): void;
249
+ /** Hide the trigger button */
250
+ hide(): void;
251
+ /** Check if modal is open */
252
+ isOpen(): boolean;
253
+ /** Check if trigger is visible */
254
+ isVisible(): boolean;
255
+ /** Check if widget is ready */
256
+ isReady(): boolean;
257
+ /** Check if running in headless mode (no DOM rendering) */
258
+ isHeadless(): boolean;
259
+ /** Set user data (email, name, custom fields) */
260
+ setData(data: Partial<UserData>): void;
261
+ /** Identify user with ID and traits */
262
+ identify(userId: string, traits?: UserTraits): void;
263
+ /** Reset user data and state */
264
+ reset(): void;
265
+ /** Programmatically submit feedback */
266
+ submit(feedback: Partial<FeedbackData>): Promise<void>;
267
+ /** Subscribe to an event */
268
+ on<K extends keyof FeedValueEvents>(event: K, callback: EventHandler<K>): void;
269
+ /** Subscribe to an event for a single emission */
270
+ once<K extends keyof FeedValueEvents>(event: K, callback: EventHandler<K>): void;
271
+ /** Unsubscribe from an event */
272
+ off<K extends keyof FeedValueEvents>(event: K, callback?: EventHandler<K>): void;
273
+ /** Returns a promise that resolves when the widget is ready */
274
+ waitUntilReady(): Promise<void>;
275
+ /** Update runtime configuration */
276
+ setConfig(config: Partial<FeedValueConfig>): void;
277
+ /** Get current configuration */
278
+ getConfig(): FeedValueConfig;
279
+ /** Subscribe to state changes */
280
+ subscribe(callback: () => void): () => void;
281
+ /** Get current state snapshot */
282
+ getSnapshot(): FeedValueState;
283
+ }
284
+
285
+ /**
286
+ * @feedvalue/core - FeedValue Class
287
+ *
288
+ * Main FeedValue SDK class. Provides the public API for interacting
289
+ * with the feedback widget.
290
+ *
291
+ * For vanilla JavaScript usage, this class also handles DOM rendering.
292
+ * Framework packages (React, Vue) use this class as a headless core.
293
+ */
294
+
295
+ /**
296
+ * FeedValue SDK
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * // Initialize
301
+ * const feedvalue = FeedValue.init({ widgetId: 'abc123' });
302
+ *
303
+ * // Control
304
+ * feedvalue.open();
305
+ * feedvalue.close();
306
+ *
307
+ * // User data
308
+ * feedvalue.setData({ email: 'user@example.com' });
309
+ * feedvalue.identify('user-123', { plan: 'pro' });
310
+ *
311
+ * // Events
312
+ * feedvalue.on('submit', (feedback) => {
313
+ * console.log('Feedback submitted:', feedback);
314
+ * });
315
+ * ```
316
+ */
317
+ declare class FeedValue implements FeedValueInstance {
318
+ private readonly widgetId;
319
+ private readonly apiClient;
320
+ private readonly emitter;
321
+ private readonly headless;
322
+ private config;
323
+ private widgetConfig;
324
+ private state;
325
+ private stateSubscribers;
326
+ private stateSnapshot;
327
+ private _userData;
328
+ private _userId;
329
+ private _userTraits;
330
+ private triggerButton;
331
+ private modal;
332
+ private overlay;
333
+ private stylesInjected;
334
+ private autoCloseTimeout;
335
+ /**
336
+ * Create a new FeedValue instance
337
+ * Use FeedValue.init() for public API
338
+ */
339
+ private constructor();
340
+ /**
341
+ * Initialize FeedValue
342
+ * Returns existing instance if already initialized for this widgetId
343
+ */
344
+ static init(options: FeedValueOptions): FeedValue;
345
+ /**
346
+ * Get existing instance by widgetId
347
+ */
348
+ static getInstance(widgetId: string): FeedValue | undefined;
349
+ /**
350
+ * Initialize the widget
351
+ */
352
+ init(): Promise<void>;
353
+ /**
354
+ * Destroy the widget
355
+ */
356
+ destroy(): void;
357
+ open(): void;
358
+ close(): void;
359
+ toggle(): void;
360
+ show(): void;
361
+ hide(): void;
362
+ isOpen(): boolean;
363
+ isVisible(): boolean;
364
+ isReady(): boolean;
365
+ isHeadless(): boolean;
366
+ setData(data: Partial<UserData>): void;
367
+ identify(userId: string, traits?: UserTraits): void;
368
+ reset(): void;
369
+ /**
370
+ * Get current user data (for debugging/testing)
371
+ */
372
+ getUserData(): {
373
+ userId: string | null;
374
+ data: UserData;
375
+ traits: UserTraits;
376
+ };
377
+ submit(feedback: Partial<FeedbackData>): Promise<void>;
378
+ /**
379
+ * Build user data object for API submission
380
+ * Combines data from identify() and setData() calls
381
+ */
382
+ private buildSubmissionUserData;
383
+ /**
384
+ * Validate feedback data before submission
385
+ * @throws Error if validation fails
386
+ */
387
+ private validateFeedback;
388
+ on<K extends keyof FeedValueEvents>(event: K, callback: EventHandler<K>): void;
389
+ /**
390
+ * Subscribe to an event for a single emission.
391
+ * The handler will be automatically removed after the first call.
392
+ */
393
+ once<K extends keyof FeedValueEvents>(event: K, callback: EventHandler<K>): void;
394
+ off<K extends keyof FeedValueEvents>(event: K, callback?: EventHandler<K>): void;
395
+ /**
396
+ * Returns a promise that resolves when the widget is ready.
397
+ * Useful for programmatic initialization flows.
398
+ *
399
+ * @throws {Error} If initialization fails
400
+ */
401
+ waitUntilReady(): Promise<void>;
402
+ setConfig(config: Partial<FeedValueConfig>): void;
403
+ getConfig(): FeedValueConfig;
404
+ /**
405
+ * Get widget configuration (from API)
406
+ */
407
+ getWidgetConfig(): WidgetConfig | null;
408
+ /**
409
+ * Subscribe to state changes
410
+ * Used by React's useSyncExternalStore
411
+ */
412
+ subscribe(callback: () => void): () => void;
413
+ /**
414
+ * Get current state snapshot
415
+ * Used by React's useSyncExternalStore
416
+ */
417
+ getSnapshot(): FeedValueState;
418
+ /**
419
+ * Update state and notify subscribers
420
+ */
421
+ private updateState;
422
+ /**
423
+ * Render widget DOM elements (for vanilla usage)
424
+ */
425
+ private renderWidget;
426
+ /**
427
+ * Sanitize CSS to block potentially dangerous patterns
428
+ * Prevents CSS injection attacks via url(), @import, and other vectors
429
+ */
430
+ private sanitizeCSS;
431
+ /**
432
+ * Inject CSS styles
433
+ */
434
+ private injectStyles;
435
+ /**
436
+ * Get base CSS styles
437
+ */
438
+ private getBaseStyles;
439
+ /**
440
+ * Get trigger button position styles
441
+ */
442
+ private getPositionStyles;
443
+ /**
444
+ * Get modal position styles
445
+ */
446
+ private getModalPositionStyles;
447
+ /**
448
+ * Render trigger button using safe DOM methods
449
+ */
450
+ private renderTrigger;
451
+ /**
452
+ * Render modal using safe DOM methods (no innerHTML)
453
+ */
454
+ private renderModal;
455
+ /**
456
+ * Handle form submission
457
+ */
458
+ private handleFormSubmit;
459
+ /**
460
+ * Show error message (safe - uses textContent)
461
+ */
462
+ private showError;
463
+ /**
464
+ * Show success message using safe DOM methods
465
+ */
466
+ private showSuccess;
467
+ /**
468
+ * Reset form to initial state
469
+ */
470
+ private resetForm;
471
+ /**
472
+ * Debug logging
473
+ */
474
+ private log;
475
+ }
476
+
477
+ /**
478
+ * @feedvalue/core - Type-Safe Event Emitter
479
+ *
480
+ * A minimal, type-safe event emitter for FeedValue events.
481
+ * Provides compile-time type checking for event names and handlers.
482
+ */
483
+
484
+ /**
485
+ * Type-safe event emitter for FeedValue events
486
+ *
487
+ * @example
488
+ * ```ts
489
+ * const emitter = new TypedEventEmitter();
490
+ *
491
+ * emitter.on('ready', () => console.log('Ready!'));
492
+ * emitter.on('submit', (feedback) => console.log('Submitted:', feedback));
493
+ *
494
+ * emitter.emit('ready');
495
+ * emitter.emit('submit', { message: 'Great app!' });
496
+ * ```
497
+ */
498
+ declare class TypedEventEmitter {
499
+ private listeners;
500
+ /**
501
+ * Subscribe to an event
502
+ *
503
+ * @param event - Event name
504
+ * @param handler - Event handler function
505
+ */
506
+ on<K extends keyof FeedValueEvents>(event: K, handler: EventHandler<K>): void;
507
+ /**
508
+ * Subscribe to an event for a single emission.
509
+ * The handler will be automatically removed after the first call.
510
+ *
511
+ * @param event - Event name
512
+ * @param handler - Event handler function
513
+ */
514
+ once<K extends keyof FeedValueEvents>(event: K, handler: EventHandler<K>): void;
515
+ /**
516
+ * Unsubscribe from an event
517
+ *
518
+ * @param event - Event name
519
+ * @param handler - Optional handler to remove (removes all if not provided)
520
+ */
521
+ off<K extends keyof FeedValueEvents>(event: K, handler?: EventHandler<K>): void;
522
+ /**
523
+ * Emit an event to all subscribers
524
+ *
525
+ * @param event - Event name
526
+ * @param args - Arguments to pass to handlers
527
+ */
528
+ emit<K extends keyof FeedValueEvents>(event: K, ...args: Parameters<EventHandler<K>>): void;
529
+ /**
530
+ * Remove all event listeners
531
+ */
532
+ removeAllListeners(): void;
533
+ }
534
+
535
+ /**
536
+ * @feedvalue/core - API Client
537
+ *
538
+ * Handles all communication with the FeedValue API.
539
+ * Includes request deduplication, caching, and error handling.
540
+ */
541
+
542
+ /**
543
+ * Default API base URL
544
+ */
545
+ declare const DEFAULT_API_BASE_URL = "https://api.feedvalue.com";
546
+ /**
547
+ * API client for FeedValue
548
+ */
549
+ declare class ApiClient {
550
+ private baseUrl;
551
+ private debug;
552
+ private pendingRequests;
553
+ private configCache;
554
+ private submissionToken;
555
+ private tokenExpiresAt;
556
+ private fingerprint;
557
+ constructor(baseUrl?: string, debug?: boolean);
558
+ /**
559
+ * Validate widget ID to prevent path injection attacks
560
+ * @throws Error if widget ID is invalid
561
+ */
562
+ private validateWidgetId;
563
+ /**
564
+ * Set client fingerprint for anti-abuse protection
565
+ */
566
+ setFingerprint(fingerprint: string): void;
567
+ /**
568
+ * Get client fingerprint
569
+ */
570
+ getFingerprint(): string | null;
571
+ /**
572
+ * Check if submission token is valid
573
+ */
574
+ hasValidToken(): boolean;
575
+ /**
576
+ * Fetch widget configuration
577
+ * Uses caching and request deduplication
578
+ */
579
+ fetchConfig(widgetId: string): Promise<ConfigResponse>;
580
+ /**
581
+ * Actually fetch config from API
582
+ */
583
+ private doFetchConfig;
584
+ /**
585
+ * Submit feedback with optional user data
586
+ */
587
+ submitFeedback(widgetId: string, feedback: FeedbackData, userData?: SubmissionUserData): Promise<FeedbackResponse>;
588
+ /**
589
+ * Parse error from response
590
+ */
591
+ private parseError;
592
+ /**
593
+ * Clear all caches
594
+ */
595
+ clearCache(): void;
596
+ /**
597
+ * Debug logging
598
+ */
599
+ private log;
600
+ }
601
+
602
+ /**
603
+ * Simple fingerprint generation for anti-abuse protection.
604
+ * Uses a session-based UUID stored in sessionStorage.
605
+ *
606
+ * This replaces the previous complex fingerprinting system (canvas, WebGL, audio)
607
+ * which was overkill for an MVP feedback widget and raised privacy concerns.
608
+ */
609
+ /**
610
+ * Generate or retrieve a session fingerprint.
611
+ *
612
+ * The fingerprint is:
613
+ * - Generated once per browser session using crypto.randomUUID()
614
+ * - Stored in sessionStorage for consistency within a session
615
+ * - Automatically cleared when the browser tab/window is closed
616
+ *
617
+ * @returns A unique fingerprint string for the current session
618
+ */
619
+ declare function generateFingerprint(): string;
620
+ /**
621
+ * Clear the stored fingerprint.
622
+ * Useful for testing or when user requests data reset.
623
+ */
624
+ declare function clearFingerprint(): void;
625
+
626
+ export { ApiClient, type ConfigResponse, type CustomField, type CustomFieldType, DEFAULT_API_BASE_URL, type EmojiSentiment, type EventHandler, FeedValue, type FeedValueConfig, type FeedValueEvents, type FeedValueInstance, type FeedValueOptions, type FeedValueState, type FeedbackData, type FeedbackMetadata, type FeedbackResponse, type SubmissionUserData, type TriggerIconType, TypedEventEmitter, type UserData, type UserTraits, type WidgetConfig, type WidgetPosition, type WidgetStyling, type WidgetTheme, type WidgetUIConfig, clearFingerprint, generateFingerprint };