@xhub-short/contracts 0.1.0-beta.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,1474 @@
1
+ /**
2
+ * Core types for XHub-short SDK
3
+ * These types are the foundation of the SDK's data model.
4
+ */
5
+ /**
6
+ * Video source configuration
7
+ * Supports both MP4 (native) and HLS streaming
8
+ */
9
+ interface VideoSource {
10
+ /** Video URL - can be MP4 or HLS (.m3u8) */
11
+ url: string;
12
+ /** Source type for playback strategy selection */
13
+ type: 'mp4' | 'hls';
14
+ /** Optional quality variants for adaptive streaming */
15
+ qualities?: VideoQuality[];
16
+ }
17
+ /**
18
+ * Video quality variant for adaptive bitrate streaming
19
+ */
20
+ interface VideoQuality {
21
+ /** Quality label (e.g., '720p', '1080p') */
22
+ label: string;
23
+ /** Bitrate in kbps */
24
+ bitrate: number;
25
+ /** Resolution width */
26
+ width: number;
27
+ /** Resolution height */
28
+ height: number;
29
+ /** URL for this quality */
30
+ url: string;
31
+ }
32
+ /**
33
+ * Video author information
34
+ */
35
+ interface Author {
36
+ /** Unique author ID */
37
+ id: string;
38
+ /** Display name */
39
+ name: string;
40
+ /** Avatar URL */
41
+ avatar?: string;
42
+ /** Verified status */
43
+ isVerified?: boolean;
44
+ }
45
+ /**
46
+ * Video statistics
47
+ */
48
+ interface VideoStats {
49
+ /** Total view count */
50
+ views: number;
51
+ /** Total like count */
52
+ likes: number;
53
+ /** Total comment count */
54
+ comments: number;
55
+ /** Total share count */
56
+ shares: number;
57
+ }
58
+ /**
59
+ * Main video item structure
60
+ * This is the core data model for a video in the feed
61
+ */
62
+ interface VideoItem {
63
+ /** Unique video ID */
64
+ id: string;
65
+ /** Video source configuration */
66
+ source: VideoSource;
67
+ /** Poster/thumbnail image URL */
68
+ poster?: string;
69
+ /** Video duration in seconds */
70
+ duration: number;
71
+ /** Video title/description */
72
+ title?: string;
73
+ /** Author information */
74
+ author: Author;
75
+ /** Video statistics */
76
+ stats: VideoStats;
77
+ /** Whether current user has liked this video */
78
+ isLiked: boolean;
79
+ /** Whether current user is following the author */
80
+ isFollowing: boolean;
81
+ /** Video creation timestamp */
82
+ createdAt: string;
83
+ /** Optional hashtags */
84
+ hashtags?: string[];
85
+ /** Optional music/sound information */
86
+ music?: MusicInfo;
87
+ }
88
+ /**
89
+ * Music/Sound information for a video
90
+ */
91
+ interface MusicInfo {
92
+ /** Music ID */
93
+ id: string;
94
+ /** Music title */
95
+ title: string;
96
+ /** Artist name */
97
+ artist: string;
98
+ /** Cover image URL */
99
+ cover?: string;
100
+ }
101
+ /**
102
+ * Feed pagination response
103
+ */
104
+ interface FeedResponse {
105
+ /** List of video items */
106
+ items: VideoItem[];
107
+ /** Cursor for next page (null if no more data) */
108
+ nextCursor: string | null;
109
+ /** Whether there are more items to load */
110
+ hasMore: boolean;
111
+ }
112
+ /**
113
+ * Feed state for state management
114
+ */
115
+ interface FeedState {
116
+ /** Map of video items by ID (for O(1) lookup and deduplication) */
117
+ itemsMap: Map<string, VideoItem>;
118
+ /** Ordered list of video IDs for rendering */
119
+ orderedIds: string[];
120
+ /** Current cursor for pagination */
121
+ cursor: string | null;
122
+ /** Whether more items can be loaded */
123
+ hasMore: boolean;
124
+ /** Loading state */
125
+ isLoading: boolean;
126
+ /** Error state */
127
+ error: Error | null;
128
+ }
129
+ /**
130
+ * Playback state enum
131
+ */
132
+ type PlaybackState = 'idle' | 'loading' | 'ready' | 'playing' | 'paused' | 'buffering' | 'ended' | 'error';
133
+ /**
134
+ * Player state for a single video
135
+ */
136
+ interface PlayerState {
137
+ /** Current playback state */
138
+ playbackState: PlaybackState;
139
+ /** Current playback time in seconds */
140
+ currentTime: number;
141
+ /** Total duration in seconds */
142
+ duration: number;
143
+ /** Buffered time ranges */
144
+ buffered: number;
145
+ /** Whether video is muted */
146
+ isMuted: boolean;
147
+ /** Current volume (0-1) */
148
+ volume: number;
149
+ /** Playback rate */
150
+ playbackRate: number;
151
+ /** Error information if playback failed */
152
+ error: PlayerError | null;
153
+ }
154
+ /**
155
+ * Player error information
156
+ */
157
+ interface PlayerError {
158
+ /** Error code for programmatic handling */
159
+ code: string;
160
+ /** Human-readable error message */
161
+ message: string;
162
+ /** Whether error is recoverable */
163
+ recoverable: boolean;
164
+ /** Retry count for circuit breaker */
165
+ retryCount: number;
166
+ }
167
+ /**
168
+ * Session snapshot for state persistence
169
+ * Used by Lifecycle Manager for state restoration
170
+ */
171
+ interface SessionSnapshot {
172
+ /** Video items from feed (for offline restore) */
173
+ items: VideoItem[];
174
+ /** Feed cursor position for pagination */
175
+ cursor: string | null;
176
+ /** Index of the currently focused video */
177
+ focusedIndex: number;
178
+ /** Scroll position (optional, for virtual scroller) */
179
+ scrollPosition?: number;
180
+ /**
181
+ * Video playback position in seconds (optional)
182
+ * Only saved when restorePlaybackPosition config is enabled
183
+ * Used to seek() video to exact position when restoring
184
+ */
185
+ playbackTime?: number;
186
+ /**
187
+ * ID of the currently playing video (optional)
188
+ * Backup for focusedIndex in case feed order changed
189
+ */
190
+ currentVideoId?: string;
191
+ /** Snapshot creation time */
192
+ savedAt: number;
193
+ /** SDK version for compatibility checking */
194
+ version: string;
195
+ }
196
+ /**
197
+ * Comment structure
198
+ */
199
+ interface Comment {
200
+ /** Comment ID */
201
+ id: string;
202
+ /** Comment text */
203
+ text: string;
204
+ /** Comment author */
205
+ author: Author;
206
+ /** Like count */
207
+ likes: number;
208
+ /** Whether current user liked this comment */
209
+ isLiked: boolean;
210
+ /** Creation timestamp */
211
+ createdAt: string;
212
+ /** Reply count */
213
+ replyCount: number;
214
+ }
215
+ /**
216
+ * Optimistic action for tracking pending operations
217
+ */
218
+ interface OptimisticAction<T = unknown> {
219
+ /** Unique action ID */
220
+ id: string;
221
+ /** Action type */
222
+ type: 'like' | 'unlike' | 'follow' | 'unfollow' | 'comment';
223
+ /** Target resource ID */
224
+ targetId: string;
225
+ /** Previous state for rollback */
226
+ previousState: T;
227
+ /** Timestamp for timeout handling */
228
+ timestamp: number;
229
+ }
230
+ /**
231
+ * Analytics event types
232
+ */
233
+ type AnalyticsEventType = 'video_view' | 'video_play' | 'video_pause' | 'video_complete' | 'video_seek' | 'video_error' | 'like' | 'unlike' | 'share' | 'comment' | 'follow' | 'unfollow' | 'scroll' | 'impression';
234
+ /**
235
+ * Analytics event structure
236
+ */
237
+ interface AnalyticsEvent {
238
+ /** Event type */
239
+ type: AnalyticsEventType;
240
+ /** Video ID (if applicable) */
241
+ videoId?: string;
242
+ /** Event timestamp */
243
+ timestamp: number;
244
+ /** Additional event data */
245
+ data?: Record<string, unknown>;
246
+ }
247
+ /**
248
+ * SDK configuration options
249
+ */
250
+ interface SDKConfig {
251
+ /** Data source adapter */
252
+ dataSource?: unknown;
253
+ /** Interaction adapter */
254
+ interaction?: unknown;
255
+ /** Storage adapter */
256
+ storage?: unknown;
257
+ /** Analytics adapter */
258
+ analytics?: unknown;
259
+ /** Logger adapter */
260
+ logger?: unknown;
261
+ /** Enable debug mode */
262
+ debug?: boolean;
263
+ /** Maximum number of video DOM nodes (default: 3) */
264
+ maxVideoDomNodes?: number;
265
+ /** Preload strategy */
266
+ preloadStrategy?: 'none' | 'next' | 'adjacent';
267
+ }
268
+ /**
269
+ * Log levels
270
+ */
271
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error';
272
+ /**
273
+ * Log entry structure
274
+ */
275
+ interface LogEntry {
276
+ /** Log level */
277
+ level: LogLevel;
278
+ /** Log message */
279
+ message: string;
280
+ /** Timestamp */
281
+ timestamp: number;
282
+ /** Additional context */
283
+ context?: Record<string, unknown>;
284
+ /** Error stack trace (if error) */
285
+ stack?: string;
286
+ }
287
+
288
+ /**
289
+ * IDataSource - Data Port Interface
290
+ *
291
+ * This interface defines the contract for fetching video data.
292
+ * SDK core never knows about specific APIs - it only talks to this interface.
293
+ *
294
+ * Implementations:
295
+ * - DefaultApiAdapter: Uses config.apiEndpoint to fetch
296
+ * - MockDataAdapter: Returns mock data for testing/development
297
+ * - Custom: Host App can provide their own implementation
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * class MyDataAdapter implements IDataSource {
302
+ * async fetchFeed(cursor?: string): Promise<FeedResponse> {
303
+ * const response = await myApi.getVideos(cursor);
304
+ * return {
305
+ * items: response.data.map(transformToVideoItem),
306
+ * nextCursor: response.pagination.next,
307
+ * hasMore: response.pagination.hasMore,
308
+ * };
309
+ * }
310
+ * }
311
+ * ```
312
+ */
313
+ interface IDataSource {
314
+ /**
315
+ * Fetch a page of videos for the feed
316
+ *
317
+ * @param cursor - Pagination cursor from previous response (undefined for first page)
318
+ * @returns Promise resolving to feed response with items and pagination info
319
+ * @throws Error if network fails or API returns error
320
+ *
321
+ * Implementation notes:
322
+ * - Must handle network errors gracefully
323
+ * - Should return empty items array (not throw) if no data
324
+ * - Cursor format is opaque to SDK - can be page number, timestamp, etc.
325
+ */
326
+ fetchFeed(cursor?: string): Promise<FeedResponse>;
327
+ /**
328
+ * Get detailed information for a specific video
329
+ *
330
+ * @param id - Video ID
331
+ * @returns Promise resolving to video item details
332
+ * @throws Error if video not found or network fails
333
+ *
334
+ * Implementation notes:
335
+ * - May return cached data if available
336
+ * - Should throw specific error for 404 (video deleted/not found)
337
+ */
338
+ getVideoDetail(id: string): Promise<VideoItem>;
339
+ /**
340
+ * Optional: Prefetch videos for smoother scrolling
341
+ *
342
+ * @param ids - Array of video IDs to prefetch
343
+ * @returns Promise resolving when prefetch is complete
344
+ *
345
+ * Implementation notes:
346
+ * - This is optional - implementation can be no-op
347
+ * - Used by Resource Governor for preloading
348
+ * - Should not throw errors - fail silently
349
+ */
350
+ prefetch?(ids: string[]): Promise<void>;
351
+ }
352
+
353
+ /**
354
+ * IInteraction - Interaction Port Interface
355
+ *
356
+ * This interface defines the contract for user interactions with videos.
357
+ * Used with Optimistic UI pattern - UI updates immediately, API call happens async.
358
+ *
359
+ * Flow:
360
+ * 1. User taps like
361
+ * 2. UI updates immediately (optimistic)
362
+ * 3. This adapter is called
363
+ * 4. On error -> UI rolls back
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * class MyInteractionAdapter implements IInteraction {
368
+ * async like(videoId: string): Promise<void> {
369
+ * await api.post(`/videos/${videoId}/like`);
370
+ * }
371
+ *
372
+ * async unlike(videoId: string): Promise<void> {
373
+ * await api.delete(`/videos/${videoId}/like`);
374
+ * }
375
+ * }
376
+ * ```
377
+ */
378
+ interface IInteraction {
379
+ /**
380
+ * Like a video
381
+ *
382
+ * @param videoId - ID of the video to like
383
+ * @returns Promise that resolves when like is persisted
384
+ * @throws Error if API fails (triggers rollback in OptimisticManager)
385
+ *
386
+ * Implementation notes:
387
+ * - Should be idempotent (liking twice = same result)
388
+ * - Throw error to trigger UI rollback
389
+ */
390
+ like(videoId: string): Promise<void>;
391
+ /**
392
+ * Remove like from a video
393
+ *
394
+ * @param videoId - ID of the video to unlike
395
+ * @returns Promise that resolves when unlike is persisted
396
+ * @throws Error if API fails (triggers rollback in OptimisticManager)
397
+ */
398
+ unlike(videoId: string): Promise<void>;
399
+ /**
400
+ * Follow a video author
401
+ *
402
+ * @param authorId - ID of the author to follow
403
+ * @returns Promise that resolves when follow is persisted
404
+ * @throws Error if API fails (triggers rollback in OptimisticManager)
405
+ */
406
+ follow(authorId: string): Promise<void>;
407
+ /**
408
+ * Unfollow a video author
409
+ *
410
+ * @param authorId - ID of the author to unfollow
411
+ * @returns Promise that resolves when unfollow is persisted
412
+ * @throws Error if API fails (triggers rollback in OptimisticManager)
413
+ */
414
+ unfollow(authorId: string): Promise<void>;
415
+ /**
416
+ * Post a comment on a video
417
+ *
418
+ * @param videoId - ID of the video to comment on
419
+ * @param text - Comment text content
420
+ * @returns Promise resolving to the created comment
421
+ * @throws Error if API fails
422
+ *
423
+ * Implementation notes:
424
+ * - Returns the full Comment object for UI display
425
+ * - Comment ID is generated by server
426
+ */
427
+ comment(videoId: string, text: string): Promise<Comment>;
428
+ /**
429
+ * Delete a comment
430
+ *
431
+ * @param commentId - ID of the comment to delete
432
+ * @returns Promise that resolves when comment is deleted
433
+ * @throws Error if API fails or comment not found
434
+ */
435
+ deleteComment(commentId: string): Promise<void>;
436
+ /**
437
+ * Like a comment
438
+ *
439
+ * @param commentId - ID of the comment to like
440
+ * @returns Promise that resolves when like is persisted
441
+ */
442
+ likeComment(commentId: string): Promise<void>;
443
+ /**
444
+ * Unlike a comment
445
+ *
446
+ * @param commentId - ID of the comment to unlike
447
+ * @returns Promise that resolves when unlike is persisted
448
+ */
449
+ unlikeComment(commentId: string): Promise<void>;
450
+ /**
451
+ * Share a video (optional tracking)
452
+ *
453
+ * @param videoId - ID of the video being shared
454
+ * @param platform - Optional platform identifier (e.g., 'facebook', 'twitter')
455
+ * @returns Promise that resolves when share is tracked
456
+ *
457
+ * Implementation notes:
458
+ * - This is primarily for tracking/analytics
459
+ * - Actual share mechanism is handled by Host App
460
+ */
461
+ share?(videoId: string, platform?: string): Promise<void>;
462
+ /**
463
+ * Report a video
464
+ *
465
+ * @param videoId - ID of the video to report
466
+ * @param reason - Report reason code
467
+ * @param description - Optional additional description
468
+ * @returns Promise that resolves when report is submitted
469
+ */
470
+ report?(videoId: string, reason: string, description?: string): Promise<void>;
471
+ }
472
+
473
+ /**
474
+ * IStorage - Storage Port Interface
475
+ *
476
+ * This interface defines the contract for persistent storage.
477
+ * Used by Lifecycle Manager for state restoration when user returns.
478
+ *
479
+ * Implementations:
480
+ * - LocalStorageAdapter: Uses browser localStorage
481
+ * - HybridStorageAdapter: Uses Flutter native storage via bridge
482
+ * - MemoryStorageAdapter: In-memory (for testing)
483
+ *
484
+ * Key Design Decision:
485
+ * - Storage is simple key-value
486
+ * - Complex objects are JSON serialized by the adapter
487
+ * - SDK stores session snapshots for state restoration
488
+ *
489
+ * @example
490
+ * ```typescript
491
+ * class LocalStorageAdapter implements IStorage {
492
+ * async get<T>(key: string): Promise<T | null> {
493
+ * const value = localStorage.getItem(key);
494
+ * return value ? JSON.parse(value) : null;
495
+ * }
496
+ *
497
+ * async set<T>(key: string, value: T): Promise<void> {
498
+ * localStorage.setItem(key, JSON.stringify(value));
499
+ * }
500
+ * }
501
+ * ```
502
+ */
503
+ interface IStorage {
504
+ /**
505
+ * Get a value from storage
506
+ *
507
+ * @param key - Storage key
508
+ * @returns Promise resolving to the stored value or null if not found
509
+ *
510
+ * Implementation notes:
511
+ * - Should return null (not throw) if key doesn't exist
512
+ * - Handle JSON parse errors gracefully (return null)
513
+ */
514
+ get<T>(key: string): Promise<T | null>;
515
+ /**
516
+ * Set a value in storage
517
+ *
518
+ * @param key - Storage key
519
+ * @param value - Value to store (will be serialized)
520
+ * @returns Promise that resolves when value is persisted
521
+ * @throws Error if storage is full (QuotaExceededError)
522
+ *
523
+ * Implementation notes:
524
+ * - Handle QuotaExceededError by implementing LRU eviction
525
+ * - Serialize complex objects to JSON
526
+ */
527
+ set<T>(key: string, value: T): Promise<void>;
528
+ /**
529
+ * Remove a value from storage
530
+ *
531
+ * @param key - Storage key to remove
532
+ * @returns Promise that resolves when value is removed
533
+ *
534
+ * Implementation notes:
535
+ * - Should not throw if key doesn't exist
536
+ */
537
+ remove(key: string): Promise<void>;
538
+ /**
539
+ * Clear all SDK-related storage
540
+ *
541
+ * @returns Promise that resolves when storage is cleared
542
+ *
543
+ * Implementation notes:
544
+ * - Should only clear SDK-namespaced keys
545
+ * - Do not clear unrelated Host App storage
546
+ */
547
+ clear(): Promise<void>;
548
+ /**
549
+ * Get all keys in storage (for debugging/maintenance)
550
+ *
551
+ * @returns Promise resolving to array of storage keys
552
+ *
553
+ * Implementation notes:
554
+ * - Only return SDK-namespaced keys
555
+ * - Used for debugging and LRU eviction
556
+ */
557
+ keys(): Promise<string[]>;
558
+ }
559
+ /**
560
+ * Specialized interface for session snapshot storage
561
+ * Extends base IStorage with type-safe session methods
562
+ */
563
+ interface ISessionStorage extends IStorage {
564
+ /**
565
+ * Save session snapshot for state restoration
566
+ *
567
+ * @param snapshot - Session state to persist
568
+ * @returns Promise that resolves when saved
569
+ *
570
+ * Implementation notes:
571
+ * - Called when user navigates away
572
+ * - Should handle QuotaExceeded by clearing old snapshots
573
+ */
574
+ saveSnapshot(snapshot: SessionSnapshot): Promise<void>;
575
+ /**
576
+ * Load the most recent session snapshot
577
+ *
578
+ * @returns Promise resolving to snapshot or null if none exists
579
+ *
580
+ * Implementation notes:
581
+ * - Called when SDK mounts
582
+ * - May return stale snapshot - let SDK decide if valid
583
+ */
584
+ loadSnapshot(): Promise<SessionSnapshot | null>;
585
+ /**
586
+ * Clear session snapshot
587
+ *
588
+ * @returns Promise that resolves when cleared
589
+ *
590
+ * Implementation notes:
591
+ * - Called when session is explicitly ended
592
+ * - Or when snapshot is too old to be useful
593
+ */
594
+ clearSnapshot(): Promise<void>;
595
+ }
596
+
597
+ /**
598
+ * IAnalytics - Analytics Port Interface
599
+ *
600
+ * This interface defines the contract for analytics tracking.
601
+ * Uses batching strategy - events are queued and sent in batches.
602
+ *
603
+ * Batching Strategy (per ADD):
604
+ * 1. Events are pushed to internal queue
605
+ * 2. Flush conditions:
606
+ * - Queue > 10 items
607
+ * - User changes video
608
+ * - visibilitychange (user tabs away)
609
+ * 3. Uses navigator.sendBeacon for reliability
610
+ *
611
+ * @example
612
+ * ```typescript
613
+ * class MyAnalyticsAdapter implements IAnalytics {
614
+ * private queue: AnalyticsEvent[] = [];
615
+ *
616
+ * track(event: AnalyticsEvent): void {
617
+ * this.queue.push(event);
618
+ * if (this.queue.length >= 10) {
619
+ * this.flush();
620
+ * }
621
+ * }
622
+ *
623
+ * async flush(): Promise<void> {
624
+ * if (this.queue.length === 0) return;
625
+ *
626
+ * const events = [...this.queue];
627
+ * this.queue = [];
628
+ *
629
+ * navigator.sendBeacon('/analytics', JSON.stringify(events));
630
+ * }
631
+ * }
632
+ * ```
633
+ */
634
+ interface IAnalytics {
635
+ /**
636
+ * Track an analytics event
637
+ *
638
+ * @param event - Event to track
639
+ *
640
+ * Implementation notes:
641
+ * - Should NOT throw errors (fire-and-forget)
642
+ * - Queue events internally for batching
643
+ * - Don't block main thread
644
+ */
645
+ track(event: AnalyticsEvent): void;
646
+ /**
647
+ * Flush queued events immediately
648
+ *
649
+ * @returns Promise that resolves when flush is complete
650
+ *
651
+ * Implementation notes:
652
+ * - Called on visibilitychange
653
+ * - Called when user changes video
654
+ * - Use navigator.sendBeacon for reliability
655
+ * - Should not throw - fail silently
656
+ */
657
+ flush(): Promise<void>;
658
+ /**
659
+ * Track video view duration
660
+ *
661
+ * @param videoId - ID of the video
662
+ * @param duration - Watch duration in seconds
663
+ * @param totalDuration - Total video duration in seconds
664
+ *
665
+ * Implementation notes:
666
+ * - Called periodically during playback (heartbeat)
667
+ * - Used to calculate engagement metrics
668
+ */
669
+ trackViewDuration(videoId: string, duration: number, totalDuration: number): void;
670
+ /**
671
+ * Track video completion
672
+ *
673
+ * @param videoId - ID of the video
674
+ * @param watchTime - Total time watched in seconds
675
+ * @param loops - Number of times video looped
676
+ *
677
+ * Implementation notes:
678
+ * - Called when video ends or user navigates away
679
+ * - loops > 0 means user watched multiple times
680
+ */
681
+ trackCompletion(videoId: string, watchTime: number, loops: number): void;
682
+ /**
683
+ * Set user context for analytics
684
+ *
685
+ * @param userId - Optional user ID (null for anonymous)
686
+ * @param properties - Optional user properties
687
+ *
688
+ * Implementation notes:
689
+ * - Called once when SDK initializes
690
+ * - Properties are attached to all subsequent events
691
+ */
692
+ setUser?(userId: string | null, properties?: Record<string, unknown>): void;
693
+ /**
694
+ * Get current queue size (for debugging)
695
+ *
696
+ * @returns Number of events in queue
697
+ */
698
+ getQueueSize?(): number;
699
+ }
700
+ /**
701
+ * Analytics configuration options
702
+ */
703
+ interface AnalyticsConfig {
704
+ /** Batch size threshold for auto-flush (default: 10) */
705
+ batchSize?: number;
706
+ /** Flush interval in milliseconds (default: 30000) */
707
+ flushInterval?: number;
708
+ /** Endpoint URL for sending events */
709
+ endpoint?: string;
710
+ /** Whether to use sendBeacon API (default: true) */
711
+ useSendBeacon?: boolean;
712
+ /** Whether to track automatically (default: true) */
713
+ autoTrack?: boolean;
714
+ }
715
+
716
+ /**
717
+ * ILogger - Logger Port Interface
718
+ *
719
+ * This interface defines the contract for logging.
720
+ * SDK uses internal Circular Buffer Logger pattern.
721
+ *
722
+ * Design (per ADD - Observability Strategy):
723
+ * - Maintain circular buffer of last 50 log entries in RAM
724
+ * - Normal operation: Only write to buffer (silent)
725
+ * - On error: Flush buffer + error to this adapter (loud)
726
+ *
727
+ * This gives context for debugging ("what happened before crash")
728
+ * without constant network traffic.
729
+ *
730
+ * @example
731
+ * ```typescript
732
+ * class ConsoleLoggerAdapter implements ILogger {
733
+ * log(entry: LogEntry): void {
734
+ * const method = entry.level === 'error' ? 'error' : 'log';
735
+ * console[method](`[${entry.level}] ${entry.message}`, entry.context);
736
+ * }
737
+ *
738
+ * flush(entries: LogEntry[]): void {
739
+ * console.group('SDK Log Flush');
740
+ * entries.forEach(e => this.log(e));
741
+ * console.groupEnd();
742
+ * }
743
+ * }
744
+ * ```
745
+ */
746
+ interface ILogger {
747
+ /**
748
+ * Log a single entry
749
+ *
750
+ * @param entry - Log entry to record
751
+ *
752
+ * Implementation notes:
753
+ * - Should not throw errors
754
+ * - Host App decides where logs go (console, Sentry, Firebase, etc.)
755
+ */
756
+ log(entry: LogEntry): void;
757
+ /**
758
+ * Flush multiple log entries (on error)
759
+ *
760
+ * @param entries - Array of recent log entries for context
761
+ *
762
+ * Implementation notes:
763
+ * - Called when SDK encounters an error
764
+ * - Entries are the last N items from circular buffer
765
+ * - Use this to send to error tracking service (Sentry, etc.)
766
+ */
767
+ flush(entries: LogEntry[]): void;
768
+ /**
769
+ * Log debug message (convenience method)
770
+ *
771
+ * @param message - Debug message
772
+ * @param context - Optional context data
773
+ */
774
+ debug(message: string, context?: Record<string, unknown>): void;
775
+ /**
776
+ * Log info message (convenience method)
777
+ *
778
+ * @param message - Info message
779
+ * @param context - Optional context data
780
+ */
781
+ info(message: string, context?: Record<string, unknown>): void;
782
+ /**
783
+ * Log warning message (convenience method)
784
+ *
785
+ * @param message - Warning message
786
+ * @param context - Optional context data
787
+ */
788
+ warn(message: string, context?: Record<string, unknown>): void;
789
+ /**
790
+ * Log error message (convenience method)
791
+ *
792
+ * @param message - Error message
793
+ * @param error - Optional Error object
794
+ * @param context - Optional context data
795
+ */
796
+ error(message: string, error?: Error, context?: Record<string, unknown>): void;
797
+ }
798
+ /**
799
+ * Logger configuration options
800
+ */
801
+ interface LoggerConfig {
802
+ /** Minimum log level to record (default: 'info' in prod, 'debug' in dev) */
803
+ minLevel?: LogLevel;
804
+ /** Maximum entries in circular buffer (default: 50) */
805
+ bufferSize?: number;
806
+ /** Whether to also log to console in dev mode (default: true) */
807
+ consoleInDev?: boolean;
808
+ /** Whether to include timestamps (default: true) */
809
+ includeTimestamp?: boolean;
810
+ /** Whether to redact sensitive data (default: true) */
811
+ redactSensitive?: boolean;
812
+ }
813
+ /**
814
+ * Internal logger instance type
815
+ * Used by SDK internal components
816
+ */
817
+ interface InternalLogger {
818
+ /** Log at debug level */
819
+ debug(message: string, context?: Record<string, unknown>): void;
820
+ /** Log at info level */
821
+ info(message: string, context?: Record<string, unknown>): void;
822
+ /** Log at warn level */
823
+ warn(message: string, context?: Record<string, unknown>): void;
824
+ /** Log at error level - triggers flush */
825
+ error(message: string, error?: Error, context?: Record<string, unknown>): void;
826
+ /** Get buffer contents (for debugging) */
827
+ getBuffer(): LogEntry[];
828
+ /** Manually trigger flush */
829
+ forceFlush(): void;
830
+ }
831
+
832
+ /**
833
+ * Network Adapter Interface
834
+ *
835
+ * Provides network information for adaptive prefetch/preload strategies.
836
+ * Implementations can use Web API (navigator.connection) or native bridge.
837
+ */
838
+ /**
839
+ * Network connection type
840
+ */
841
+ type NetworkType = 'wifi' | 'cellular' | '4g' | '3g' | 'slow' | 'offline';
842
+ /**
843
+ * Network quality metrics
844
+ */
845
+ interface NetworkQuality {
846
+ /** Effective connection type */
847
+ type: NetworkType;
848
+ /** Estimated downlink speed in Mbps */
849
+ downlink?: number;
850
+ /** Round-trip time in ms */
851
+ rtt?: number;
852
+ /** Whether connection is metered (cellular data) */
853
+ isMetered?: boolean;
854
+ }
855
+ /**
856
+ * Network adapter interface (Port)
857
+ *
858
+ * Abstracts network detection for cross-platform support:
859
+ * - Web: Uses Navigator.connection API (with fallbacks)
860
+ * - Flutter WebView: Uses native bridge for accurate info
861
+ *
862
+ * @example
863
+ * ```typescript
864
+ * // Web implementation
865
+ * class WebNetworkAdapter implements INetworkAdapter {
866
+ * async getNetworkType() {
867
+ * const conn = navigator.connection;
868
+ * return conn?.effectiveType ?? 'wifi';
869
+ * }
870
+ * }
871
+ *
872
+ * // Flutter bridge implementation
873
+ * class FlutterNetworkAdapter implements INetworkAdapter {
874
+ * async getNetworkType() {
875
+ * return await flutterBridge.getNetworkType();
876
+ * }
877
+ * }
878
+ * ```
879
+ */
880
+ interface INetworkAdapter {
881
+ /**
882
+ * Get current effective network type
883
+ *
884
+ * @returns Promise resolving to network type
885
+ */
886
+ getNetworkType(): Promise<NetworkType>;
887
+ /**
888
+ * Get detailed network quality metrics
889
+ *
890
+ * @returns Promise resolving to quality metrics
891
+ */
892
+ getNetworkQuality(): Promise<NetworkQuality>;
893
+ /**
894
+ * Subscribe to network changes
895
+ *
896
+ * @param callback - Called when network type changes
897
+ * @returns Unsubscribe function
898
+ */
899
+ onNetworkChange(callback: (type: NetworkType) => void): () => void;
900
+ /**
901
+ * Check if currently online
902
+ *
903
+ * @returns Promise resolving to online status
904
+ */
905
+ isOnline(): Promise<boolean>;
906
+ }
907
+
908
+ /**
909
+ * Video Loader Interface
910
+ *
911
+ * Abstracts video preloading for different video types (MP4 vs HLS).
912
+ * ResourceGovernor uses this to prefetch videos without knowing implementation details.
913
+ */
914
+
915
+ /**
916
+ * Preload status for a video
917
+ */
918
+ type PreloadStatus = 'idle' | 'loading' | 'ready' | 'error';
919
+ /**
920
+ * Preload result with status and optional error
921
+ */
922
+ interface PreloadResult {
923
+ /** Video ID */
924
+ videoId: string;
925
+ /** Preload status */
926
+ status: PreloadStatus;
927
+ /** Preloaded bytes (if available) */
928
+ loadedBytes?: number;
929
+ /** Error if preload failed */
930
+ error?: Error;
931
+ }
932
+ /**
933
+ * Preload configuration
934
+ */
935
+ interface PreloadConfig {
936
+ /** Priority (higher = more important) */
937
+ priority?: number;
938
+ /** Maximum bytes to preload (for range requests) */
939
+ maxBytes?: number;
940
+ /** Timeout in ms */
941
+ timeout?: number;
942
+ }
943
+ /**
944
+ * Video loader interface (Port)
945
+ *
946
+ * Abstracts video preloading strategy based on video type:
947
+ *
948
+ * - **MP4:** Uses fetch with Range header to preload first N bytes
949
+ * - **HLS:** Uses hls.js API (startLoad) to preload initial segments
950
+ * - **Native HLS (Safari):** Uses video.preload="auto" with ghost video element
951
+ *
952
+ * @example
953
+ * ```typescript
954
+ * // ResourceGovernor uses IVideoLoader without knowing MP4 vs HLS
955
+ * class ResourceGovernor {
956
+ * constructor(private videoLoader: IVideoLoader) {}
957
+ *
958
+ * async prefetchNext(videoId: string, source: VideoSource) {
959
+ * if (this.networkType === 'wifi') {
960
+ * await this.videoLoader.preload(videoId, source);
961
+ * }
962
+ * }
963
+ * }
964
+ * ```
965
+ */
966
+ interface IVideoLoader {
967
+ /**
968
+ * Preload a video source
969
+ *
970
+ * Implementation varies by source type:
971
+ * - MP4: Fetch first chunk (500KB-1MB)
972
+ * - HLS: Load manifest + first segment via hls.js
973
+ *
974
+ * @param videoId - Unique video identifier
975
+ * @param source - Video source configuration
976
+ * @param config - Optional preload configuration
977
+ * @returns Promise resolving to preload result
978
+ */
979
+ preload(videoId: string, source: VideoSource, config?: PreloadConfig): Promise<PreloadResult>;
980
+ /**
981
+ * Cancel an in-progress preload
982
+ *
983
+ * @param videoId - Video ID to cancel
984
+ */
985
+ cancelPreload(videoId: string): void;
986
+ /**
987
+ * Check if a video is preloaded
988
+ *
989
+ * @param videoId - Video ID to check
990
+ * @returns Whether video data is cached/preloaded
991
+ */
992
+ isPreloaded(videoId: string): boolean;
993
+ /**
994
+ * Get preload status for a video
995
+ *
996
+ * @param videoId - Video ID to check
997
+ * @returns Current preload status
998
+ */
999
+ getPreloadStatus(videoId: string): PreloadStatus;
1000
+ /**
1001
+ * Clear preloaded data for a video (memory management)
1002
+ *
1003
+ * @param videoId - Video ID to clear
1004
+ */
1005
+ clearPreload(videoId: string): void;
1006
+ /**
1007
+ * Clear all preloaded data
1008
+ */
1009
+ clearAll(): void;
1010
+ /**
1011
+ * Get total preloaded bytes (for memory monitoring)
1012
+ *
1013
+ * @returns Total bytes currently preloaded
1014
+ */
1015
+ getTotalPreloadedBytes(): number;
1016
+ }
1017
+ /**
1018
+ * Poster/thumbnail preloader interface
1019
+ *
1020
+ * Separate from video preloader for simpler implementation
1021
+ * and to ensure posters are always loaded (even on slow networks)
1022
+ */
1023
+ interface IPosterLoader {
1024
+ /**
1025
+ * Preload a poster image
1026
+ *
1027
+ * @param url - Poster image URL
1028
+ * @returns Promise resolving when image is loaded
1029
+ */
1030
+ preload(url: string): Promise<void>;
1031
+ /**
1032
+ * Check if poster is cached
1033
+ *
1034
+ * @param url - Poster URL to check
1035
+ * @returns Whether poster is in cache
1036
+ */
1037
+ isCached(url: string): boolean;
1038
+ /**
1039
+ * Clear poster cache
1040
+ */
1041
+ clearCache(): void;
1042
+ }
1043
+
1044
+ /**
1045
+ * UI Component Types for XHub-short SDK
1046
+ *
1047
+ * These types define the props for headless UI components and their wired versions.
1048
+ * Used by:
1049
+ * - @xhub-short/ui (headless components receive these as props)
1050
+ * - @xhub-short/sdk (wired components inject these via HOC)
1051
+ *
1052
+ * Architecture: Two-Tier Export Pattern (Headless UI + SDK Wiring)
1053
+ *
1054
+ * NOTE: This package has NO DEPENDENCIES. React types are defined as generics
1055
+ * and will be resolved by the consuming package (@xhub-short/ui or @xhub-short/sdk).
1056
+ */
1057
+
1058
+ /**
1059
+ * Generic ReactNode type (resolved by consuming package)
1060
+ * Use React.ReactNode when importing in @xhub-short/ui
1061
+ */
1062
+ type UINode = unknown;
1063
+ /**
1064
+ * Generic React ref type
1065
+ */
1066
+ type UIRef<T> = {
1067
+ current: T | null;
1068
+ } | ((instance: T | null) => void);
1069
+ /**
1070
+ * Generic React component type
1071
+ */
1072
+ type UIComponent<P = object> = (props: P) => UINode;
1073
+ /**
1074
+ * Feed state from useFeed() hook
1075
+ */
1076
+ interface UIFeedState {
1077
+ /** List of video items */
1078
+ videos: VideoItem[];
1079
+ /** Whether feed is loading */
1080
+ isLoading: boolean;
1081
+ /** Whether there are more items to load */
1082
+ hasMore: boolean;
1083
+ /** Error if any */
1084
+ error: Error | null;
1085
+ /** Load more items */
1086
+ loadMore: () => Promise<void>;
1087
+ /** Refresh feed */
1088
+ refresh: () => Promise<void>;
1089
+ }
1090
+ /**
1091
+ * Swipe/scroll state from useSwipeGesture() hook
1092
+ */
1093
+ interface UISwipeState {
1094
+ /** Current active video index */
1095
+ activeIndex: number;
1096
+ /** Whether user is currently swiping */
1097
+ isSwiping: boolean;
1098
+ /** Drag offset in pixels (during swipe) */
1099
+ dragOffset: number;
1100
+ /** Swipe direction */
1101
+ direction: 'up' | 'down' | null;
1102
+ /** Go to specific index */
1103
+ goToIndex: (index: number) => void;
1104
+ /** Go to next video */
1105
+ goToNext: () => void;
1106
+ /** Go to previous video */
1107
+ goToPrev: () => void;
1108
+ }
1109
+ /**
1110
+ * Resource allocation state from useResourceAllocation() hook
1111
+ */
1112
+ interface UIResourceState {
1113
+ /** Whether this slot has an allocated video DOM node */
1114
+ isAllocated: boolean;
1115
+ /** Whether video data is preloaded */
1116
+ isPreloaded: boolean;
1117
+ /** Whether this is the active (focused) slot */
1118
+ isActive: boolean;
1119
+ /** Distance from active slot (0 = active, 1 = adjacent, etc.) */
1120
+ distance: number;
1121
+ }
1122
+ /**
1123
+ * Player state from usePlayerForVideo() hook
1124
+ */
1125
+ interface UIPlayerState {
1126
+ /** Current playback state */
1127
+ state: PlayerState['playbackState'];
1128
+ /** Current time in seconds */
1129
+ currentTime: number;
1130
+ /** Total duration in seconds */
1131
+ duration: number;
1132
+ /** Buffered percentage (0-1) */
1133
+ buffered: number;
1134
+ /** Whether video is muted */
1135
+ isMuted: boolean;
1136
+ /** Volume level (0-1) */
1137
+ volume: number;
1138
+ /** Whether video is playing */
1139
+ isPlaying: boolean;
1140
+ /** Whether video is loading */
1141
+ isLoading: boolean;
1142
+ /** Error if any */
1143
+ error: Error | null;
1144
+ }
1145
+ /**
1146
+ * Player controls from usePlayerForVideo() hook
1147
+ */
1148
+ interface UIPlayerControls {
1149
+ /** Play video */
1150
+ play: () => void;
1151
+ /** Pause video */
1152
+ pause: () => void;
1153
+ /** Toggle play/pause */
1154
+ toggle: () => void;
1155
+ /** Seek to time in seconds */
1156
+ seek: (time: number) => void;
1157
+ /** Set volume (0-1) */
1158
+ setVolume: (volume: number) => void;
1159
+ /** Toggle mute */
1160
+ toggleMute: () => void;
1161
+ /** Set muted state */
1162
+ setMuted: (muted: boolean) => void;
1163
+ }
1164
+ /**
1165
+ * Interaction state from useOptimistic() hook
1166
+ */
1167
+ interface UIInteractionState {
1168
+ /** Whether video is liked by current user */
1169
+ isLiked: boolean;
1170
+ /** Like count */
1171
+ likeCount: number;
1172
+ /** Comment count */
1173
+ commentCount: number;
1174
+ /** Share count */
1175
+ shareCount: number;
1176
+ /** Whether like action is pending */
1177
+ isLikePending: boolean;
1178
+ }
1179
+ /**
1180
+ * Interaction actions from useOptimistic() hook
1181
+ */
1182
+ interface UIInteractionActions {
1183
+ /** Toggle like state */
1184
+ toggleLike: () => Promise<void>;
1185
+ /** Share video */
1186
+ share: () => Promise<void>;
1187
+ /** Open comments */
1188
+ openComments: () => void;
1189
+ }
1190
+ /**
1191
+ * Author interaction state from useOptimistic() hook
1192
+ */
1193
+ interface UIAuthorState {
1194
+ /** Author info */
1195
+ author: Author;
1196
+ /** Whether current user is following */
1197
+ isFollowing: boolean;
1198
+ /** Whether follow action is pending */
1199
+ isFollowPending: boolean;
1200
+ }
1201
+ /**
1202
+ * Author interaction actions from useOptimistic() hook
1203
+ */
1204
+ interface UIAuthorActions {
1205
+ /** Toggle follow state */
1206
+ toggleFollow: () => Promise<void>;
1207
+ /** Open author profile */
1208
+ openProfile: () => void;
1209
+ }
1210
+ /**
1211
+ * VideoFeed headless component props
1212
+ */
1213
+ interface VideoFeedHeadlessProps {
1214
+ /** Feed state (videos, loading, etc.) */
1215
+ feedState: UIFeedState;
1216
+ /** Swipe/scroll state */
1217
+ swipeState: UISwipeState;
1218
+ /** Container height (usually 100vh) */
1219
+ height?: number | string;
1220
+ /** Additional CSS classes */
1221
+ className?: string;
1222
+ /** Render function for each video slot */
1223
+ renderSlot: (video: VideoItem, index: number) => UINode;
1224
+ /** Called when active index changes */
1225
+ onIndexChange?: (index: number) => void;
1226
+ /** Called when reaching near end of feed */
1227
+ onEndReached?: () => void;
1228
+ /** Threshold for triggering onEndReached (default: 2) */
1229
+ endReachedThreshold?: number;
1230
+ }
1231
+ /**
1232
+ * VideoSlot headless component props
1233
+ */
1234
+ interface VideoSlotHeadlessProps {
1235
+ /** Video item to display */
1236
+ video: VideoItem;
1237
+ /** Resource allocation state */
1238
+ resourceState: UIResourceState;
1239
+ /** Player state */
1240
+ playerState: UIPlayerState;
1241
+ /** Player controls */
1242
+ playerControls: UIPlayerControls;
1243
+ /** Additional CSS classes */
1244
+ className?: string;
1245
+ /** Children (overlay content) */
1246
+ children?: UINode;
1247
+ /** Called when video becomes visible */
1248
+ onVisible?: () => void;
1249
+ /** Called when video becomes hidden */
1250
+ onHidden?: () => void;
1251
+ }
1252
+ /**
1253
+ * VideoPlayer headless component props
1254
+ */
1255
+ interface VideoPlayerHeadlessProps {
1256
+ /** Video source URL */
1257
+ src: string;
1258
+ /** Source type */
1259
+ type: 'mp4' | 'hls';
1260
+ /** Poster image URL */
1261
+ poster?: string;
1262
+ /** Whether video should autoplay */
1263
+ autoPlay?: boolean;
1264
+ /** Whether video should loop */
1265
+ loop?: boolean;
1266
+ /** Whether video is muted */
1267
+ muted?: boolean;
1268
+ /** Initial volume (0-1) */
1269
+ volume?: number;
1270
+ /** Additional CSS classes */
1271
+ className?: string;
1272
+ /** Video element ref callback */
1273
+ videoRef?: UIRef<HTMLVideoElement>;
1274
+ /** Called when video can play */
1275
+ onCanPlay?: () => void;
1276
+ /** Called when video starts playing (play event) */
1277
+ onPlay?: () => void;
1278
+ /** Called when playback resumes after buffering (playing event) */
1279
+ onPlaying?: () => void;
1280
+ /** Called when video is paused */
1281
+ onPause?: () => void;
1282
+ /** Called when video ends */
1283
+ onEnded?: () => void;
1284
+ /** Called on time update */
1285
+ onTimeUpdate?: (currentTime: number) => void;
1286
+ /** Called when duration is known */
1287
+ onDurationChange?: (duration: number) => void;
1288
+ /** Called when video is waiting for data */
1289
+ onWaiting?: () => void;
1290
+ /** Called on error */
1291
+ onError?: (error: Error) => void;
1292
+ /** Called when first frame is captured */
1293
+ onFirstFrameCapture?: (dataUrl: string) => void;
1294
+ }
1295
+ /**
1296
+ * ProgressBar headless component props
1297
+ */
1298
+ interface ProgressBarHeadlessProps {
1299
+ /** Current time in seconds */
1300
+ currentTime: number;
1301
+ /** Total duration in seconds */
1302
+ duration: number;
1303
+ /** Buffered percentage (0-1) */
1304
+ buffered?: number;
1305
+ /** Whether seeking is enabled */
1306
+ seekable?: boolean;
1307
+ /** Additional CSS classes */
1308
+ className?: string;
1309
+ /** Called when user seeks */
1310
+ onSeek?: (time: number) => void;
1311
+ /** Called when user starts seeking */
1312
+ onSeekStart?: () => void;
1313
+ /** Called when user ends seeking */
1314
+ onSeekEnd?: () => void;
1315
+ }
1316
+ /**
1317
+ * ActionBar headless component props
1318
+ */
1319
+ interface ActionBarHeadlessProps {
1320
+ /** Interaction state */
1321
+ interactionState: UIInteractionState;
1322
+ /** Interaction actions */
1323
+ interactionActions: UIInteractionActions;
1324
+ /** Additional CSS classes */
1325
+ className?: string;
1326
+ /** Children (action buttons) */
1327
+ children?: UINode;
1328
+ }
1329
+ /**
1330
+ * ActionButton (Like, Comment, Share) headless component props
1331
+ */
1332
+ interface ActionButtonHeadlessProps {
1333
+ /** Button icon (inactive state) */
1334
+ icon: UINode;
1335
+ /** Button icon (active state, e.g., filled heart) */
1336
+ activeIcon?: UINode;
1337
+ /** Whether button is in active state */
1338
+ isActive?: boolean;
1339
+ /** Count to display */
1340
+ count?: number;
1341
+ /** Whether action is pending */
1342
+ isPending?: boolean;
1343
+ /** Click handler */
1344
+ onClick?: () => void;
1345
+ /** Additional CSS classes */
1346
+ className?: string;
1347
+ /** Accessible label */
1348
+ ariaLabel?: string;
1349
+ }
1350
+ /**
1351
+ * AuthorInfo headless component props
1352
+ */
1353
+ interface AuthorInfoHeadlessProps {
1354
+ /** Author state */
1355
+ authorState: UIAuthorState;
1356
+ /** Author actions */
1357
+ authorActions: UIAuthorActions;
1358
+ /** Whether to show follow button */
1359
+ showFollowButton?: boolean;
1360
+ /** Additional CSS classes */
1361
+ className?: string;
1362
+ /** Children (for compound pattern) */
1363
+ children?: UINode;
1364
+ }
1365
+ /**
1366
+ * ErrorBoundary component props
1367
+ */
1368
+ interface ErrorBoundaryProps {
1369
+ /** Children to render */
1370
+ children: UINode;
1371
+ /** Error handling mode */
1372
+ mode?: 'show-error' | 'auto-skip' | 'auto-skip-with-toast';
1373
+ /** Custom error fallback */
1374
+ fallback?: UINode | ((error: Error, reset: () => void) => UINode);
1375
+ /** Called when error occurs */
1376
+ onError?: (error: Error) => void;
1377
+ /** Called when reset is triggered */
1378
+ onReset?: () => void;
1379
+ }
1380
+ /**
1381
+ * Skeleton component props
1382
+ */
1383
+ interface SkeletonProps {
1384
+ /** Width */
1385
+ width?: number | string;
1386
+ /** Height */
1387
+ height?: number | string;
1388
+ /** Border radius */
1389
+ borderRadius?: number | string;
1390
+ /** Animation type */
1391
+ animation?: 'pulse' | 'wave' | 'none';
1392
+ /** Additional CSS classes */
1393
+ className?: string;
1394
+ }
1395
+ /**
1396
+ * VideoFeed wired component props
1397
+ * feedState and swipeState are injected by SDK
1398
+ */
1399
+ interface VideoFeedProps extends Omit<VideoFeedHeadlessProps, 'feedState' | 'swipeState'> {
1400
+ /** Override feed state (optional) */
1401
+ feedState?: UIFeedState;
1402
+ /** Override swipe state (optional) */
1403
+ swipeState?: UISwipeState;
1404
+ }
1405
+ /**
1406
+ * VideoSlot wired component props
1407
+ * resourceState, playerState, playerControls are injected by SDK
1408
+ */
1409
+ interface VideoSlotProps extends Omit<VideoSlotHeadlessProps, 'resourceState' | 'playerState' | 'playerControls'> {
1410
+ /** Override resource state (optional) */
1411
+ resourceState?: UIResourceState;
1412
+ /** Override player state (optional) */
1413
+ playerState?: UIPlayerState;
1414
+ /** Override player controls (optional) */
1415
+ playerControls?: UIPlayerControls;
1416
+ }
1417
+ /**
1418
+ * VideoPlayer wired component props
1419
+ * Uses video from context, only needs overrides
1420
+ */
1421
+ interface VideoPlayerWiredProps extends Partial<VideoPlayerHeadlessProps> {
1422
+ /** Video item (required for wired version) */
1423
+ video: VideoItem;
1424
+ }
1425
+ /**
1426
+ * ActionBar wired component props
1427
+ * interactionState and interactionActions are injected by SDK
1428
+ */
1429
+ interface ActionBarProps extends Omit<ActionBarHeadlessProps, 'interactionState' | 'interactionActions'> {
1430
+ /** Video to get interaction state for */
1431
+ video: VideoItem;
1432
+ /** Override interaction state (optional) */
1433
+ interactionState?: UIInteractionState;
1434
+ /** Override interaction actions (optional) */
1435
+ interactionActions?: UIInteractionActions;
1436
+ }
1437
+ /**
1438
+ * AuthorInfo wired component props
1439
+ * authorState and authorActions are injected by SDK
1440
+ */
1441
+ interface AuthorInfoProps extends Omit<AuthorInfoHeadlessProps, 'authorState' | 'authorActions'> {
1442
+ /** Video to get author from */
1443
+ video: VideoItem;
1444
+ /** Override author state (optional) */
1445
+ authorState?: UIAuthorState;
1446
+ /** Override author actions (optional) */
1447
+ authorActions?: UIAuthorActions;
1448
+ }
1449
+ /**
1450
+ * Hook configuration for HOC factory
1451
+ */
1452
+ interface WiredComponentConfig<THooks, THeadlessProps, TWiredProps> {
1453
+ /** Function that calls SDK hooks and returns hook results */
1454
+ useHooks: (props: TWiredProps) => THooks;
1455
+ /** Function that maps hook results to headless component props */
1456
+ mapHooksToProps: (hooks: THooks, props: TWiredProps) => Partial<THeadlessProps>;
1457
+ }
1458
+ /**
1459
+ * HOC factory return type
1460
+ */
1461
+ type WiredComponent<TWiredProps> = UIComponent<TWiredProps>;
1462
+ /**
1463
+ * Icon component type
1464
+ */
1465
+ type IconComponent = UIComponent<{
1466
+ className?: string;
1467
+ size?: number | string;
1468
+ }>;
1469
+ /**
1470
+ * Format count function type (1000 -> "1K")
1471
+ */
1472
+ type FormatCountFn = (count: number) => string;
1473
+
1474
+ export type { ActionBarHeadlessProps, ActionBarProps, ActionButtonHeadlessProps, AnalyticsConfig, AnalyticsEvent, AnalyticsEventType, Author, AuthorInfoHeadlessProps, AuthorInfoProps, Comment, ErrorBoundaryProps, FeedResponse, FeedState, FormatCountFn, IAnalytics, IDataSource, IInteraction, ILogger, INetworkAdapter, IPosterLoader, ISessionStorage, IStorage, IVideoLoader, IconComponent, InternalLogger, LogEntry, LogLevel, LoggerConfig, MusicInfo, NetworkQuality, NetworkType, OptimisticAction, PlaybackState, PlayerError, PlayerState, PreloadConfig, PreloadResult, PreloadStatus, ProgressBarHeadlessProps, SDKConfig, SessionSnapshot, SkeletonProps, UIAuthorActions, UIAuthorState, UIComponent, UIFeedState, UIInteractionActions, UIInteractionState, UINode, UIPlayerControls, UIPlayerState, UIRef, UIResourceState, UISwipeState, VideoFeedHeadlessProps, VideoFeedProps, VideoItem, VideoPlayerHeadlessProps, VideoPlayerWiredProps, VideoQuality, VideoSlotHeadlessProps, VideoSlotProps, VideoSource, VideoStats, WiredComponent, WiredComponentConfig };