@xhub-short/adapters 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,1954 @@
1
+ import { IDataSource, VideoItem, FeedResponse, ILogger, LogLevel, LogEntry, IStorage, ISessionStorage, SessionSnapshot, IInteraction, Comment, IAnalytics, AnalyticsEvent, INetworkAdapter, NetworkType, NetworkQuality, IVideoLoader, VideoSource, PreloadConfig, PreloadResult, PreloadStatus, IPosterLoader } from '@xhub-short/contracts';
2
+
3
+ /**
4
+ * MockDataAdapter - Development/Testing Data Source
5
+ *
6
+ * Provides mock video data for development without requiring a backend API.
7
+ * Simulates pagination and network delay for realistic testing.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const mockAdapter = new MockDataAdapter({ delay: 500 });
12
+ * const feed = await mockAdapter.fetchFeed();
13
+ * ```
14
+ */
15
+ declare class MockDataAdapter implements IDataSource {
16
+ private readonly videos;
17
+ private readonly pageSize;
18
+ private readonly delay;
19
+ constructor(options?: MockDataAdapterOptions);
20
+ /**
21
+ * Fetch a page of mock videos
22
+ */
23
+ fetchFeed(cursor?: string): Promise<FeedResponse>;
24
+ /**
25
+ * Get a single video by ID
26
+ */
27
+ getVideoDetail(id: string): Promise<VideoItem>;
28
+ /**
29
+ * Prefetch videos (no-op for mock)
30
+ */
31
+ prefetch(_ids: string[]): Promise<void>;
32
+ /**
33
+ * Simulate network delay
34
+ */
35
+ private simulateDelay;
36
+ }
37
+ /**
38
+ * Configuration options for MockDataAdapter
39
+ */
40
+ interface MockDataAdapterOptions {
41
+ /** Custom mock video data */
42
+ videos?: VideoItem[];
43
+ /** Number of items per page (default: 3) */
44
+ pageSize?: number;
45
+ /** Simulated network delay in ms (default: 300) */
46
+ delay?: number;
47
+ }
48
+
49
+ /**
50
+ * Configuration options for MockLoggerAdapter
51
+ */
52
+ interface MockLoggerAdapterOptions {
53
+ /** Maximum entries in circular buffer (default: 50 per ADD spec) */
54
+ bufferSize?: number;
55
+ /** Minimum log level to record (default: 'debug') */
56
+ minLevel?: LogLevel;
57
+ /** Whether to also output to console (default: false) */
58
+ consoleOutput?: boolean;
59
+ }
60
+ /**
61
+ * MockLoggerAdapter - In-memory ILogger implementation for testing
62
+ *
63
+ * Implements Circular Buffer pattern as specified in ADD.md Section 7.3:
64
+ * - Maintains buffer of last N log entries in RAM
65
+ * - Normal operation: Only write to buffer (silent)
66
+ * - On error: Flush buffer for context
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const logger = new MockLoggerAdapter({ bufferSize: 50 });
71
+ * logger.info('User action', { videoId: '123' });
72
+ * logger.error('Failed to load', new Error('Network error'));
73
+ *
74
+ * // Get buffer for debugging
75
+ * const entries = logger.getBuffer();
76
+ * ```
77
+ *
78
+ * @see ILogger - Interface definition in @xhub-short/contracts
79
+ */
80
+ declare class MockLoggerAdapter implements ILogger {
81
+ private buffer;
82
+ private readonly maxSize;
83
+ private readonly minLevel;
84
+ private readonly consoleOutput;
85
+ constructor(options?: MockLoggerAdapterOptions);
86
+ /**
87
+ * Log a single entry to the circular buffer
88
+ *
89
+ * @param entry - Log entry to record
90
+ */
91
+ log(entry: LogEntry): void;
92
+ /**
93
+ * Flush log entries (called on error for context)
94
+ *
95
+ * @param entries - Array of log entries to flush
96
+ */
97
+ flush(entries: LogEntry[]): void;
98
+ /**
99
+ * Log debug message
100
+ *
101
+ * @param message - Debug message
102
+ * @param context - Optional context data
103
+ */
104
+ debug(message: string, context?: Record<string, unknown>): void;
105
+ /**
106
+ * Log info message
107
+ *
108
+ * @param message - Info message
109
+ * @param context - Optional context data
110
+ */
111
+ info(message: string, context?: Record<string, unknown>): void;
112
+ /**
113
+ * Log warning message
114
+ *
115
+ * @param message - Warning message
116
+ * @param context - Optional context data
117
+ */
118
+ warn(message: string, context?: Record<string, unknown>): void;
119
+ /**
120
+ * Log error message
121
+ *
122
+ * @param message - Error message
123
+ * @param error - Optional Error object
124
+ * @param context - Optional context data
125
+ */
126
+ error(message: string, error?: Error, context?: Record<string, unknown>): void;
127
+ /**
128
+ * Get current buffer contents (for testing)
129
+ *
130
+ * @returns Copy of current log buffer
131
+ */
132
+ getBuffer(): LogEntry[];
133
+ /**
134
+ * Get buffer size (for testing)
135
+ *
136
+ * @returns Number of entries in buffer
137
+ */
138
+ getBufferSize(): number;
139
+ /**
140
+ * Clear the buffer (for testing)
141
+ */
142
+ clearBuffer(): void;
143
+ /**
144
+ * Get entries by level (for testing)
145
+ *
146
+ * @param level - Log level to filter by
147
+ * @returns Entries matching the level
148
+ */
149
+ getEntriesByLevel(level: LogLevel): LogEntry[];
150
+ /**
151
+ * Force flush and return buffer contents
152
+ *
153
+ * @returns All entries in buffer before clearing
154
+ */
155
+ forceFlush(): LogEntry[];
156
+ /**
157
+ * Output entry to console (when consoleOutput is enabled)
158
+ */
159
+ private outputToConsole;
160
+ }
161
+
162
+ /**
163
+ * Configuration options for MockStorageAdapter
164
+ */
165
+ interface MockStorageAdapterOptions {
166
+ /** Initial data to populate storage */
167
+ initialData?: Record<string, unknown>;
168
+ /** Simulated delay in ms (default: 0) */
169
+ delay?: number;
170
+ /** Error rate for random failures (0-1) */
171
+ errorRate?: number;
172
+ /** Simulate QuotaExceededError on set operations */
173
+ simulateQuotaError?: boolean;
174
+ }
175
+ /**
176
+ * MockStorageAdapter - In-memory IStorage implementation for testing
177
+ *
178
+ * Simulates localStorage/sessionStorage behavior without persisting to disk.
179
+ * Supports configurable delays and error simulation for testing edge cases.
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * const storage = new MockStorageAdapter({ delay: 100 });
184
+ * await storage.set('key', { foo: 'bar' });
185
+ * const value = await storage.get('key');
186
+ * ```
187
+ *
188
+ * @see IStorage - Interface definition in @xhub-short/contracts
189
+ */
190
+ declare class MockStorageAdapter implements IStorage {
191
+ protected store: Map<string, string>;
192
+ protected readonly delay: number;
193
+ protected readonly errorRate: number;
194
+ protected readonly simulateQuotaError: boolean;
195
+ constructor(options?: MockStorageAdapterOptions);
196
+ /**
197
+ * Get a value from storage
198
+ *
199
+ * @param key - Storage key
200
+ * @returns Promise resolving to the stored value or null if not found
201
+ */
202
+ get<T>(key: string): Promise<T | null>;
203
+ /**
204
+ * Set a value in storage
205
+ *
206
+ * @param key - Storage key
207
+ * @param value - Value to store (will be serialized)
208
+ * @throws DOMException if simulateQuotaError is enabled
209
+ */
210
+ set<T>(key: string, value: T): Promise<void>;
211
+ /**
212
+ * Remove a value from storage
213
+ *
214
+ * @param key - Storage key to remove
215
+ */
216
+ remove(key: string): Promise<void>;
217
+ /**
218
+ * Clear all SDK-related storage
219
+ */
220
+ clear(): Promise<void>;
221
+ /**
222
+ * Get all keys in storage
223
+ *
224
+ * @returns Promise resolving to array of storage keys
225
+ */
226
+ keys(): Promise<string[]>;
227
+ /**
228
+ * Get storage size (for testing)
229
+ *
230
+ * @returns Number of entries in storage
231
+ */
232
+ getSize(): number;
233
+ /**
234
+ * Check if key exists (for testing)
235
+ *
236
+ * @param key - Storage key to check
237
+ * @returns Whether key exists
238
+ */
239
+ has(key: string): boolean;
240
+ /**
241
+ * Get raw storage Map (for testing)
242
+ *
243
+ * @returns Copy of internal storage map
244
+ */
245
+ getRawStore(): Map<string, string>;
246
+ /**
247
+ * Simulate network/processing delay
248
+ */
249
+ protected simulateDelay(): Promise<void>;
250
+ /**
251
+ * Maybe throw error based on error rate
252
+ */
253
+ protected maybeThrowError(): void;
254
+ }
255
+ /**
256
+ * Configuration options for MockSessionStorageAdapter
257
+ */
258
+ interface MockSessionStorageAdapterOptions extends MockStorageAdapterOptions {
259
+ /** Maximum age of snapshot in ms before considered stale (default: 24h) */
260
+ maxSnapshotAge?: number;
261
+ }
262
+ /**
263
+ * MockSessionStorageAdapter - ISessionStorage implementation for testing
264
+ *
265
+ * Extends MockStorageAdapter with session snapshot functionality.
266
+ * Used by LifecycleManager for state restoration.
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const sessionStorage = new MockSessionStorageAdapter();
271
+ *
272
+ * // Save snapshot when user navigates away
273
+ * await sessionStorage.saveSnapshot({
274
+ * currentVideoId: 'video-123',
275
+ * timestamp: 30.5,
276
+ * cursor: 'abc123',
277
+ * currentIndex: 5,
278
+ * savedAt: Date.now(),
279
+ * });
280
+ *
281
+ * // Restore snapshot when user returns
282
+ * const snapshot = await sessionStorage.loadSnapshot();
283
+ * ```
284
+ *
285
+ * @see ISessionStorage - Interface definition in @xhub-short/contracts
286
+ */
287
+ declare class MockSessionStorageAdapter extends MockStorageAdapter implements ISessionStorage {
288
+ private readonly maxSnapshotAge;
289
+ constructor(options?: MockSessionStorageAdapterOptions);
290
+ /**
291
+ * Save session snapshot for state restoration
292
+ *
293
+ * @param snapshot - Session state to persist
294
+ */
295
+ saveSnapshot(snapshot: SessionSnapshot): Promise<void>;
296
+ /**
297
+ * Load the most recent session snapshot
298
+ *
299
+ * @returns Promise resolving to snapshot or null if none exists
300
+ */
301
+ loadSnapshot(): Promise<SessionSnapshot | null>;
302
+ /**
303
+ * Clear session snapshot
304
+ */
305
+ clearSnapshot(): Promise<void>;
306
+ /**
307
+ * Check if snapshot exists (for testing)
308
+ *
309
+ * @returns Whether snapshot exists
310
+ */
311
+ hasSnapshot(): boolean;
312
+ /**
313
+ * Get snapshot age in ms (for testing)
314
+ *
315
+ * @returns Age in ms or null if no snapshot
316
+ */
317
+ getSnapshotAge(): Promise<number | null>;
318
+ }
319
+
320
+ /**
321
+ * Configuration options for MockInteractionAdapter
322
+ */
323
+ interface MockInteractionAdapterOptions {
324
+ /** Simulated delay in ms (default: 0) */
325
+ delay?: number;
326
+ /** Error rate for random failures (0-1) */
327
+ errorRate?: number;
328
+ /** Force like operations to fail (for rollback testing) */
329
+ failLike?: boolean;
330
+ /** Force unlike operations to fail */
331
+ failUnlike?: boolean;
332
+ /** Force follow operations to fail */
333
+ failFollow?: boolean;
334
+ /** Force unfollow operations to fail */
335
+ failUnfollow?: boolean;
336
+ /** Force comment operations to fail */
337
+ failComment?: boolean;
338
+ /** Force deleteComment operations to fail */
339
+ failDeleteComment?: boolean;
340
+ /** Initial liked video IDs */
341
+ initialLikedVideos?: string[];
342
+ /** Initial followed author IDs */
343
+ initialFollowedAuthors?: string[];
344
+ /** Initial liked comment IDs */
345
+ initialLikedComments?: string[];
346
+ }
347
+ /**
348
+ * MockInteractionAdapter - In-memory IInteraction implementation for testing
349
+ *
350
+ * Provides mock implementations for all user interaction methods.
351
+ * Tracks state changes in-memory for verification in tests.
352
+ * Supports error simulation for testing optimistic UI rollback.
353
+ *
354
+ * @example
355
+ * ```typescript
356
+ * const interaction = new MockInteractionAdapter({ delay: 100 });
357
+ *
358
+ * // Simulate like action
359
+ * await interaction.like('video-123');
360
+ * console.log(interaction.isVideoLiked('video-123')); // true
361
+ *
362
+ * // Test rollback scenario
363
+ * const failingInteraction = new MockInteractionAdapter({ failLike: true });
364
+ * await failingInteraction.like('video-123'); // throws Error
365
+ * ```
366
+ *
367
+ * @see IInteraction - Interface definition in @xhub-short/contracts
368
+ */
369
+ declare class MockInteractionAdapter implements IInteraction {
370
+ private readonly likedVideos;
371
+ private readonly followedAuthors;
372
+ private readonly likedComments;
373
+ private readonly comments;
374
+ private readonly delay;
375
+ private readonly errorRate;
376
+ private readonly failLike;
377
+ private readonly failUnlike;
378
+ private readonly failFollow;
379
+ private readonly failUnfollow;
380
+ private readonly failComment;
381
+ private readonly failDeleteComment;
382
+ constructor(options?: MockInteractionAdapterOptions);
383
+ /**
384
+ * Like a video
385
+ *
386
+ * @param videoId - ID of the video to like
387
+ * @throws Error if failLike is enabled or random error occurs
388
+ */
389
+ like(videoId: string): Promise<void>;
390
+ /**
391
+ * Remove like from a video
392
+ *
393
+ * @param videoId - ID of the video to unlike
394
+ * @throws Error if failUnlike is enabled or random error occurs
395
+ */
396
+ unlike(videoId: string): Promise<void>;
397
+ /**
398
+ * Follow a video author
399
+ *
400
+ * @param authorId - ID of the author to follow
401
+ * @throws Error if failFollow is enabled or random error occurs
402
+ */
403
+ follow(authorId: string): Promise<void>;
404
+ /**
405
+ * Unfollow a video author
406
+ *
407
+ * @param authorId - ID of the author to unfollow
408
+ * @throws Error if failUnfollow is enabled or random error occurs
409
+ */
410
+ unfollow(authorId: string): Promise<void>;
411
+ /**
412
+ * Post a comment on a video
413
+ *
414
+ * @param _videoId - ID of the video to comment on (unused in mock)
415
+ * @param text - Comment text content
416
+ * @returns Promise resolving to the created comment
417
+ * @throws Error if failComment is enabled or random error occurs
418
+ */
419
+ comment(_videoId: string, text: string): Promise<Comment>;
420
+ /**
421
+ * Delete a comment
422
+ *
423
+ * @param commentId - ID of the comment to delete
424
+ * @throws Error if comment not found, failDeleteComment is enabled, or random error occurs
425
+ */
426
+ deleteComment(commentId: string): Promise<void>;
427
+ /**
428
+ * Like a comment
429
+ *
430
+ * @param commentId - ID of the comment to like
431
+ */
432
+ likeComment(commentId: string): Promise<void>;
433
+ /**
434
+ * Unlike a comment
435
+ *
436
+ * @param commentId - ID of the comment to unlike
437
+ */
438
+ unlikeComment(commentId: string): Promise<void>;
439
+ /**
440
+ * Share a video (tracking only)
441
+ *
442
+ * @param _videoId - ID of the video being shared
443
+ * @param _platform - Optional platform identifier
444
+ */
445
+ share(_videoId: string, _platform?: string): Promise<void>;
446
+ /**
447
+ * Report a video
448
+ *
449
+ * @param _videoId - ID of the video to report
450
+ * @param _reason - Report reason code
451
+ * @param _description - Optional additional description
452
+ */
453
+ report(_videoId: string, _reason: string, _description?: string): Promise<void>;
454
+ /**
455
+ * Check if video is liked (for testing)
456
+ *
457
+ * @param videoId - Video ID to check
458
+ * @returns Whether video is liked
459
+ */
460
+ isVideoLiked(videoId: string): boolean;
461
+ /**
462
+ * Check if author is followed (for testing)
463
+ *
464
+ * @param authorId - Author ID to check
465
+ * @returns Whether author is followed
466
+ */
467
+ isAuthorFollowed(authorId: string): boolean;
468
+ /**
469
+ * Check if comment is liked (for testing)
470
+ *
471
+ * @param commentId - Comment ID to check
472
+ * @returns Whether comment is liked
473
+ */
474
+ isCommentLiked(commentId: string): boolean;
475
+ /**
476
+ * Get all liked video IDs (for testing)
477
+ *
478
+ * @returns Array of liked video IDs
479
+ */
480
+ getLikedVideos(): string[];
481
+ /**
482
+ * Get all followed author IDs (for testing)
483
+ *
484
+ * @returns Array of followed author IDs
485
+ */
486
+ getFollowedAuthors(): string[];
487
+ /**
488
+ * Get all comments (for testing)
489
+ *
490
+ * @returns Map of comment ID to Comment
491
+ */
492
+ getComments(): Map<string, Comment>;
493
+ /**
494
+ * Get a specific comment (for testing)
495
+ *
496
+ * @param commentId - Comment ID
497
+ * @returns Comment or undefined
498
+ */
499
+ getComment(commentId: string): Comment | undefined;
500
+ /**
501
+ * Reset all state (for testing)
502
+ */
503
+ reset(): void;
504
+ /**
505
+ * Simulate network/processing delay
506
+ */
507
+ private simulateDelay;
508
+ /**
509
+ * Maybe throw error based on error rate
510
+ */
511
+ private maybeThrowError;
512
+ }
513
+
514
+ /**
515
+ * Configuration options for MockAnalyticsAdapter
516
+ */
517
+ interface MockAnalyticsAdapterOptions {
518
+ /** Batch size threshold for auto-flush (default: 10) */
519
+ batchSize?: number;
520
+ /** Whether to auto-flush when batch size is reached (default: false) */
521
+ autoFlush?: boolean;
522
+ /** Simulated delay for flush in ms (default: 0) */
523
+ flushDelay?: number;
524
+ /** Error rate for flush failures (0-1) */
525
+ errorRate?: number;
526
+ }
527
+ /**
528
+ * MockAnalyticsAdapter - In-memory IAnalytics implementation for testing
529
+ *
530
+ * Provides mock implementations for analytics tracking with event queue batching.
531
+ * Events are stored in-memory for verification in tests.
532
+ *
533
+ * Batching Strategy (per ADD spec):
534
+ * 1. Events are pushed to internal queue via track()
535
+ * 2. Auto-flush conditions (when autoFlush enabled):
536
+ * - Queue >= batchSize items
537
+ * 3. Manual flush via flush() method
538
+ *
539
+ * @example
540
+ * ```typescript
541
+ * const analytics = new MockAnalyticsAdapter({ batchSize: 10, autoFlush: true });
542
+ *
543
+ * // Track events
544
+ * analytics.track({ type: 'video_view', videoId: '123', timestamp: Date.now() });
545
+ *
546
+ * // Get tracked events for verification
547
+ * const events = analytics.getTrackedEvents();
548
+ *
549
+ * // Manual flush
550
+ * await analytics.flush();
551
+ * ```
552
+ *
553
+ * @see IAnalytics - Interface definition in @xhub-short/contracts
554
+ */
555
+ declare class MockAnalyticsAdapter implements IAnalytics {
556
+ private eventQueue;
557
+ private flushedEvents;
558
+ private userId;
559
+ private userProperties;
560
+ private readonly batchSize;
561
+ private readonly autoFlush;
562
+ private readonly flushDelay;
563
+ private readonly errorRate;
564
+ constructor(options?: MockAnalyticsAdapterOptions);
565
+ /**
566
+ * Track an analytics event
567
+ *
568
+ * @param event - Event to track
569
+ */
570
+ track(event: AnalyticsEvent): void;
571
+ /**
572
+ * Flush queued events immediately
573
+ *
574
+ * @returns Promise that resolves when flush is complete
575
+ */
576
+ flush(): Promise<void>;
577
+ /**
578
+ * Track video view duration
579
+ *
580
+ * @param videoId - ID of the video
581
+ * @param duration - Watch duration in seconds
582
+ * @param totalDuration - Total video duration in seconds
583
+ */
584
+ trackViewDuration(videoId: string, duration: number, totalDuration: number): void;
585
+ /**
586
+ * Track video completion
587
+ *
588
+ * @param videoId - ID of the video
589
+ * @param watchTime - Total time watched in seconds
590
+ * @param loops - Number of times video looped
591
+ */
592
+ trackCompletion(videoId: string, watchTime: number, loops: number): void;
593
+ /**
594
+ * Set user context for analytics
595
+ *
596
+ * @param userId - Optional user ID (null for anonymous)
597
+ * @param properties - Optional user properties
598
+ */
599
+ setUser(userId: string | null, properties?: Record<string, unknown>): void;
600
+ /**
601
+ * Get current queue size
602
+ *
603
+ * @returns Number of events in queue
604
+ */
605
+ getQueueSize(): number;
606
+ /**
607
+ * Get all tracked events (current queue)
608
+ *
609
+ * @returns Copy of current event queue
610
+ */
611
+ getTrackedEvents(): AnalyticsEvent[];
612
+ /**
613
+ * Get all flushed event batches
614
+ *
615
+ * @returns Array of flushed event batches
616
+ */
617
+ getFlushedBatches(): AnalyticsEvent[][];
618
+ /**
619
+ * Get total flushed events count
620
+ *
621
+ * @returns Total number of flushed events
622
+ */
623
+ getTotalFlushedCount(): number;
624
+ /**
625
+ * Get events by type (from current queue)
626
+ *
627
+ * @param type - Event type to filter by
628
+ * @returns Events matching the type
629
+ */
630
+ getEventsByType(type: AnalyticsEvent['type']): AnalyticsEvent[];
631
+ /**
632
+ * Get events for a specific video (from current queue)
633
+ *
634
+ * @param videoId - Video ID to filter by
635
+ * @returns Events for the video
636
+ */
637
+ getEventsByVideoId(videoId: string): AnalyticsEvent[];
638
+ /**
639
+ * Get current user context
640
+ *
641
+ * @returns User ID and properties
642
+ */
643
+ getUserContext(): {
644
+ userId: string | null;
645
+ properties: Record<string, unknown>;
646
+ };
647
+ /**
648
+ * Check if specific event type was tracked
649
+ *
650
+ * @param type - Event type to check
651
+ * @returns Whether event type was tracked
652
+ */
653
+ hasTracked(type: AnalyticsEvent['type']): boolean;
654
+ /**
655
+ * Get last tracked event
656
+ *
657
+ * @returns Last event or undefined
658
+ */
659
+ getLastEvent(): AnalyticsEvent | undefined;
660
+ /**
661
+ * Reset all state
662
+ */
663
+ reset(): void;
664
+ /**
665
+ * Clear only the event queue (keep flushed history)
666
+ */
667
+ clearQueue(): void;
668
+ }
669
+
670
+ /**
671
+ * Mock Network Adapter
672
+ *
673
+ * Simulates network detection for testing and development.
674
+ * Can be configured with initial network type and simulate network changes.
675
+ */
676
+
677
+ /**
678
+ * Configuration options for MockNetworkAdapter
679
+ */
680
+ interface MockNetworkAdapterOptions {
681
+ /** Initial network type (default: 'wifi') */
682
+ initialType?: NetworkType;
683
+ /** Initial downlink speed in Mbps (default: 10) */
684
+ initialDownlink?: number;
685
+ /** Initial RTT in ms (default: 50) */
686
+ initialRtt?: number;
687
+ /** Whether network is metered (default: false) */
688
+ isMetered?: boolean;
689
+ /** Whether initially online (default: true) */
690
+ isOnline?: boolean;
691
+ }
692
+ /**
693
+ * Mock implementation of INetworkAdapter
694
+ *
695
+ * Useful for:
696
+ * - Unit testing network-dependent logic
697
+ * - Development without real network APIs
698
+ * - Simulating various network conditions
699
+ *
700
+ * @example
701
+ * ```typescript
702
+ * const adapter = new MockNetworkAdapter({ initialType: 'cellular' });
703
+ *
704
+ * // Simulate network change
705
+ * adapter.setNetworkType('wifi');
706
+ *
707
+ * // Test slow network
708
+ * adapter.setNetworkType('slow');
709
+ * adapter.setDownlink(0.5);
710
+ * ```
711
+ */
712
+ declare class MockNetworkAdapter implements INetworkAdapter {
713
+ private currentType;
714
+ private downlink;
715
+ private rtt;
716
+ private metered;
717
+ private online;
718
+ private listeners;
719
+ constructor(options?: MockNetworkAdapterOptions);
720
+ getNetworkType(): Promise<NetworkType>;
721
+ getNetworkQuality(): Promise<NetworkQuality>;
722
+ onNetworkChange(callback: (type: NetworkType) => void): () => void;
723
+ isOnline(): Promise<boolean>;
724
+ /**
725
+ * Set network type and notify listeners
726
+ */
727
+ setNetworkType(type: NetworkType): void;
728
+ /**
729
+ * Set downlink speed in Mbps
730
+ */
731
+ setDownlink(mbps: number): void;
732
+ /**
733
+ * Set RTT in milliseconds
734
+ */
735
+ setRtt(ms: number): void;
736
+ /**
737
+ * Set metered status (e.g., cellular data)
738
+ */
739
+ setMetered(metered: boolean): void;
740
+ /**
741
+ * Set online/offline status
742
+ */
743
+ setOnline(online: boolean): void;
744
+ /**
745
+ * Simulate network change sequence for testing
746
+ */
747
+ simulateNetworkChanges(sequence: NetworkType[], delayMs?: number): Promise<void>;
748
+ /**
749
+ * Reset to default state
750
+ */
751
+ reset(): void;
752
+ private notifyListeners;
753
+ }
754
+
755
+ /**
756
+ * Mock Video Loader
757
+ *
758
+ * Simulates video preloading for testing and development.
759
+ * Does not actually load video data, just tracks preload state.
760
+ */
761
+
762
+ /**
763
+ * Configuration options for MockVideoLoader
764
+ */
765
+ interface MockVideoLoaderOptions {
766
+ /** Simulated preload delay in ms (default: 100) */
767
+ preloadDelayMs?: number;
768
+ /** Whether preloads should succeed (default: true) */
769
+ shouldSucceed?: boolean;
770
+ /** Error message for failed preloads */
771
+ errorMessage?: string;
772
+ /** Simulated bytes per preload (default: 500000 = 500KB) */
773
+ bytesPerPreload?: number;
774
+ }
775
+ /**
776
+ * Tracked preload state
777
+ */
778
+ interface PreloadState {
779
+ status: PreloadStatus;
780
+ loadedBytes: number;
781
+ error?: Error;
782
+ abortController?: AbortController;
783
+ }
784
+ /**
785
+ * Mock implementation of IVideoLoader
786
+ *
787
+ * Useful for:
788
+ * - Unit testing ResourceGovernor logic
789
+ * - Development without real video loading
790
+ * - Simulating various preload scenarios
791
+ *
792
+ * @example
793
+ * ```typescript
794
+ * const loader = new MockVideoLoader({ preloadDelayMs: 50 });
795
+ *
796
+ * await loader.preload('video-1', { url: '...', type: 'mp4' });
797
+ * expect(loader.isPreloaded('video-1')).toBe(true);
798
+ *
799
+ * // Simulate failure
800
+ * loader.setShouldSucceed(false);
801
+ * ```
802
+ */
803
+ declare class MockVideoLoader implements IVideoLoader {
804
+ private preloads;
805
+ private preloadDelayMs;
806
+ private shouldSucceed;
807
+ private errorMessage;
808
+ private bytesPerPreload;
809
+ constructor(options?: MockVideoLoaderOptions);
810
+ preload(_videoId: string, _source: VideoSource, _config?: PreloadConfig): Promise<PreloadResult>;
811
+ cancelPreload(videoId: string): void;
812
+ isPreloaded(videoId: string): boolean;
813
+ getPreloadStatus(videoId: string): PreloadStatus;
814
+ clearPreload(videoId: string): void;
815
+ clearAll(): void;
816
+ getTotalPreloadedBytes(): number;
817
+ /**
818
+ * Set whether preloads should succeed
819
+ */
820
+ setShouldSucceed(shouldSucceed: boolean): void;
821
+ /**
822
+ * Set preload delay in milliseconds
823
+ */
824
+ setPreloadDelay(ms: number): void;
825
+ /**
826
+ * Set error message for failed preloads
827
+ */
828
+ setErrorMessage(message: string): void;
829
+ /**
830
+ * Get all tracked preloads (for testing assertions)
831
+ */
832
+ getPreloads(): Map<string, PreloadState>;
833
+ /**
834
+ * Reset all state
835
+ */
836
+ reset(): void;
837
+ }
838
+ /**
839
+ * Mock implementation of IPosterLoader
840
+ *
841
+ * Separate class for poster preloading to match interface contracts
842
+ */
843
+ declare class MockPosterLoader implements IPosterLoader {
844
+ private posterCache;
845
+ private preloadDelayMs;
846
+ constructor(preloadDelayMs?: number);
847
+ preload(url: string): Promise<void>;
848
+ isCached(url: string): boolean;
849
+ clearCache(): void;
850
+ /**
851
+ * Set preload delay in milliseconds
852
+ */
853
+ setPreloadDelay(ms: number): void;
854
+ /**
855
+ * Reset all state
856
+ */
857
+ reset(): void;
858
+ }
859
+
860
+ /**
861
+ * Preset Adapters - Type Definitions
862
+ *
863
+ * Types cho REST Preset Adapter factory
864
+ * Cho phép Host App chỉ cần config thay vì viết adapter từ đầu
865
+ */
866
+
867
+ /**
868
+ * Authentication configuration
869
+ */
870
+ interface AuthConfig {
871
+ /**
872
+ * Get current access token
873
+ * Return null if not logged in
874
+ */
875
+ getAccessToken: () => string | null | Promise<string | null>;
876
+ /**
877
+ * Refresh token when access token expires
878
+ * Return new access token
879
+ * Throw error if refresh fails
880
+ */
881
+ refreshToken?: () => Promise<string>;
882
+ /**
883
+ * Callback when auth fails completely (after refresh also fails)
884
+ * Usually used to redirect to login page
885
+ */
886
+ onAuthError?: (error: AuthError) => void;
887
+ /**
888
+ * Custom headers for all requests
889
+ * Example: { 'X-App-Version': '1.0.0' }
890
+ */
891
+ headers?: Record<string, string>;
892
+ /**
893
+ * Token header name
894
+ * @default 'Authorization'
895
+ */
896
+ tokenHeader?: string;
897
+ /**
898
+ * Token prefix in header
899
+ * @default 'Bearer'
900
+ * Set '' if no prefix needed
901
+ */
902
+ tokenPrefix?: string;
903
+ }
904
+ /**
905
+ * Auth error information
906
+ */
907
+ interface AuthError {
908
+ status: number;
909
+ message: string;
910
+ originalError?: Error;
911
+ }
912
+ /**
913
+ * REST endpoint mapping
914
+ */
915
+ interface RESTEndpointMap {
916
+ /**
917
+ * Feed-related endpoints
918
+ */
919
+ feed: {
920
+ /**
921
+ * GET endpoint for listing videos
922
+ * Query params auto-added: cursor, limit
923
+ * Example: '/videos', '/reels', '/shorts'
924
+ */
925
+ list: string;
926
+ /**
927
+ * GET endpoint for video detail
928
+ * :id will be replaced with video ID
929
+ * Example: '/videos/:id', '/reels/:id'
930
+ */
931
+ detail: string;
932
+ };
933
+ /**
934
+ * Interaction endpoints
935
+ */
936
+ interaction: {
937
+ /** POST /videos/:id/like */
938
+ like: string;
939
+ /** DELETE /videos/:id/like */
940
+ unlike: string;
941
+ /** POST /users/:id/follow */
942
+ follow: string;
943
+ /** DELETE /users/:id/follow */
944
+ unfollow: string;
945
+ /** POST /videos/:id/comments */
946
+ comment: string;
947
+ /** DELETE /comments/:id */
948
+ deleteComment: string;
949
+ /** POST /videos/:id/share (optional) */
950
+ share?: string;
951
+ };
952
+ /**
953
+ * Analytics endpoints (optional)
954
+ * If not provided, analytics adapter will be no-op
955
+ */
956
+ analytics?: {
957
+ /** POST endpoint for batch events */
958
+ batch: string;
959
+ };
960
+ }
961
+ /**
962
+ * HTTP request configuration
963
+ */
964
+ interface RESTRequestConfig {
965
+ /**
966
+ * Default query params for all requests
967
+ * Example: { app_id: 'my-app', version: '2' }
968
+ */
969
+ defaultParams?: Record<string, string>;
970
+ /**
971
+ * Request timeout in milliseconds
972
+ * @default 10000 (10s)
973
+ */
974
+ timeout?: number;
975
+ /**
976
+ * Retry configuration
977
+ */
978
+ retry?: RetryConfig;
979
+ /**
980
+ * Pagination param names
981
+ * @default { cursor: 'cursor', limit: 'limit' }
982
+ */
983
+ pagination?: {
984
+ cursor: string;
985
+ limit: string;
986
+ };
987
+ }
988
+ /**
989
+ * Retry configuration
990
+ */
991
+ interface RetryConfig {
992
+ /**
993
+ * Max retry attempts
994
+ * @default 3
995
+ */
996
+ maxRetries: number;
997
+ /**
998
+ * Delay between retries (ms)
999
+ * @default 1000
1000
+ */
1001
+ retryDelay: number;
1002
+ /**
1003
+ * HTTP status codes to retry on
1004
+ * @default [408, 429, 500, 502, 503, 504]
1005
+ */
1006
+ retryOn: number[];
1007
+ /**
1008
+ * Use exponential backoff
1009
+ * @default true
1010
+ */
1011
+ exponentialBackoff?: boolean;
1012
+ }
1013
+ /**
1014
+ * Response transform configuration
1015
+ */
1016
+ interface TransformConfig {
1017
+ /**
1018
+ * Transform single video item from API response
1019
+ * If not provided, uses default transform
1020
+ */
1021
+ videoItem?: (apiResponse: unknown) => VideoItem;
1022
+ /**
1023
+ * Transform feed response from API
1024
+ * If not provided, uses default transform
1025
+ */
1026
+ feedResponse?: (apiResponse: unknown) => {
1027
+ items: unknown[];
1028
+ nextCursor: string | null;
1029
+ hasMore: boolean;
1030
+ };
1031
+ /**
1032
+ * Field mapping for default transforms
1033
+ * Used when API field names differ from defaults
1034
+ */
1035
+ fieldMap?: FieldMapConfig;
1036
+ }
1037
+ /**
1038
+ * Field mapping configuration for auto-transform
1039
+ */
1040
+ interface FieldMapConfig {
1041
+ /**
1042
+ * VideoItem field mapping
1043
+ * Key: SDK field path, Value: API field path (dot notation)
1044
+ * Example: { 'author.name': 'creator.display_name' }
1045
+ */
1046
+ video?: Record<string, string>;
1047
+ /**
1048
+ * FeedResponse field mapping
1049
+ */
1050
+ feed?: {
1051
+ items?: string;
1052
+ nextCursor?: string;
1053
+ hasMore?: string;
1054
+ };
1055
+ }
1056
+ /**
1057
+ * REST Preset Adapter configuration
1058
+ */
1059
+ interface RESTPresetConfig {
1060
+ /**
1061
+ * Base URL for all API requests
1062
+ * Example: 'https://api.myapp.com/v1'
1063
+ */
1064
+ baseUrl: string;
1065
+ /**
1066
+ * Auth configuration
1067
+ */
1068
+ auth: AuthConfig;
1069
+ /**
1070
+ * Endpoint mapping
1071
+ */
1072
+ endpoints: RESTEndpointMap;
1073
+ /**
1074
+ * Response transforms (optional)
1075
+ * If not provided, uses default transforms
1076
+ */
1077
+ transforms?: TransformConfig;
1078
+ /**
1079
+ * Request configuration (optional)
1080
+ */
1081
+ request?: RESTRequestConfig;
1082
+ /**
1083
+ * Logger adapter for error/warning logging
1084
+ * If not provided, uses console in dev, silent in prod
1085
+ */
1086
+ logger?: ILogger;
1087
+ }
1088
+ /**
1089
+ * Factory output - all preset adapters
1090
+ */
1091
+ interface PresetAdapters {
1092
+ dataSource: IDataSource;
1093
+ interaction: IInteraction;
1094
+ analytics: IAnalytics;
1095
+ }
1096
+ /**
1097
+ * HTTP request options (internal)
1098
+ */
1099
+ interface HttpRequestOptions {
1100
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
1101
+ path: string;
1102
+ params?: Record<string, string | number | null | undefined>;
1103
+ body?: unknown;
1104
+ pathParams?: Record<string, string>;
1105
+ }
1106
+ /**
1107
+ * Default retry config
1108
+ */
1109
+ declare const DEFAULT_RETRY_CONFIG: Required<RetryConfig>;
1110
+ /**
1111
+ * Default request config
1112
+ */
1113
+ declare const DEFAULT_REQUEST_CONFIG: Required<RESTRequestConfig>;
1114
+
1115
+ /**
1116
+ * createRESTAdapters - Factory function for REST preset adapters
1117
+ *
1118
+ * Tạo bộ adapters hoàn chỉnh từ config đơn giản.
1119
+ * Host App chỉ cần cung cấp baseUrl, auth, và endpoint mapping.
1120
+ *
1121
+ * @example
1122
+ * ```typescript
1123
+ * import { createRESTAdapters } from '@xhub-short/adapters/preset';
1124
+ *
1125
+ * const adapters = createRESTAdapters({
1126
+ * baseUrl: 'https://api.myapp.com/v1',
1127
+ * auth: {
1128
+ * getAccessToken: () => localStorage.getItem('token'),
1129
+ * refreshToken: async () => {
1130
+ * const res = await fetch('/auth/refresh', { method: 'POST' });
1131
+ * const { token } = await res.json();
1132
+ * return token;
1133
+ * },
1134
+ * },
1135
+ * endpoints: {
1136
+ * feed: { list: '/videos', detail: '/videos/:id' },
1137
+ * interaction: {
1138
+ * like: '/videos/:id/like',
1139
+ * unlike: '/videos/:id/like',
1140
+ * follow: '/users/:id/follow',
1141
+ * unfollow: '/users/:id/follow',
1142
+ * comment: '/videos/:id/comments',
1143
+ * deleteComment: '/comments/:id',
1144
+ * },
1145
+ * },
1146
+ * });
1147
+ *
1148
+ * const sdk = createSDK({ adapters });
1149
+ * ```
1150
+ */
1151
+
1152
+ /**
1153
+ * Create REST preset adapters from config
1154
+ *
1155
+ * @param config - REST preset configuration
1156
+ * @returns Object containing dataSource, interaction, and analytics adapters
1157
+ */
1158
+ declare function createRESTAdapters(config: RESTPresetConfig): PresetAdapters;
1159
+
1160
+ /**
1161
+ * Browser Video Loader - Real video preloading implementation
1162
+ *
1163
+ * Preloads video data using appropriate strategy:
1164
+ * - MP4: Fetch with Range header (first N bytes)
1165
+ * - HLS: Load manifest + first segment (if hls.js available)
1166
+ *
1167
+ * Uses browser's Cache API for storage when available.
1168
+ */
1169
+
1170
+ /**
1171
+ * Configuration for BrowserVideoLoader
1172
+ */
1173
+ interface BrowserVideoLoaderConfig {
1174
+ /** Logger for debugging */
1175
+ logger?: ILogger;
1176
+ /** Default max bytes to preload for MP4 (default: 512KB) */
1177
+ defaultMaxBytes?: number;
1178
+ /** Default timeout in ms (default: 10000) */
1179
+ defaultTimeout?: number;
1180
+ /** Use Cache API when available (default: true) */
1181
+ useCache?: boolean;
1182
+ /** Cache name for stored video chunks (default: 'sv-video-cache') */
1183
+ cacheName?: string;
1184
+ }
1185
+ /**
1186
+ * BrowserVideoLoader - Real browser video preloading
1187
+ *
1188
+ * Strategies:
1189
+ * - **MP4:** Fetch first chunk (500KB-1MB) via Range request
1190
+ * - **HLS:** Not implemented (requires hls.js injection)
1191
+ * - **Native HLS (Safari):** Falls back to standard preload
1192
+ *
1193
+ * @example
1194
+ * ```typescript
1195
+ * const loader = new BrowserVideoLoader();
1196
+ *
1197
+ * // Preload next video
1198
+ * const result = await loader.preload('video-123', {
1199
+ * url: 'https://example.com/video.mp4',
1200
+ * type: 'mp4',
1201
+ * });
1202
+ *
1203
+ * console.log(result.status); // 'ready'
1204
+ * console.log(result.loadedBytes); // 524288 (512KB)
1205
+ * ```
1206
+ */
1207
+ declare class BrowserVideoLoader implements IVideoLoader {
1208
+ private readonly logger?;
1209
+ private readonly config;
1210
+ private readonly preloads;
1211
+ private cache;
1212
+ constructor(config?: BrowserVideoLoaderConfig);
1213
+ /**
1214
+ * Initialize Cache API
1215
+ */
1216
+ private initCache;
1217
+ /**
1218
+ * Preload video source
1219
+ */
1220
+ preload(videoId: string, source: VideoSource, config?: PreloadConfig): Promise<PreloadResult>;
1221
+ /**
1222
+ * Preload MP4 video using Range request
1223
+ */
1224
+ private preloadMP4;
1225
+ /**
1226
+ * Preload HLS - fetch manifest only
1227
+ * Full segment preload requires hls.js which is optional
1228
+ */
1229
+ private preloadHLS;
1230
+ /**
1231
+ * Cancel preload
1232
+ */
1233
+ cancelPreload(videoId: string): void;
1234
+ /**
1235
+ * Check if video is preloaded
1236
+ */
1237
+ isPreloaded(videoId: string): boolean;
1238
+ /**
1239
+ * Get preload status
1240
+ */
1241
+ getPreloadStatus(videoId: string): PreloadStatus;
1242
+ /**
1243
+ * Clear preloaded data for a video
1244
+ */
1245
+ clearPreload(videoId: string): void;
1246
+ /**
1247
+ * Clear all preloaded data
1248
+ */
1249
+ clearAll(): void;
1250
+ /**
1251
+ * Get total preloaded bytes
1252
+ */
1253
+ getTotalPreloadedBytes(): number;
1254
+ }
1255
+ /**
1256
+ * BrowserPosterLoader - Browser image preloading
1257
+ *
1258
+ * Uses Image object for preloading poster/thumbnail images.
1259
+ * Simple and effective for warming browser cache.
1260
+ *
1261
+ * @example
1262
+ * ```typescript
1263
+ * const posterLoader = new BrowserPosterLoader();
1264
+ *
1265
+ * await posterLoader.preload('https://example.com/thumb.jpg');
1266
+ * console.log(posterLoader.isCached('https://example.com/thumb.jpg')); // true
1267
+ * ```
1268
+ */
1269
+ declare class BrowserPosterLoader implements IPosterLoader {
1270
+ private readonly logger?;
1271
+ private readonly cache;
1272
+ private readonly pending;
1273
+ private readonly timeout;
1274
+ constructor(config?: {
1275
+ logger?: ILogger;
1276
+ timeout?: number;
1277
+ });
1278
+ /**
1279
+ * Preload a poster image
1280
+ */
1281
+ preload(url: string): Promise<void>;
1282
+ /**
1283
+ * Check if poster is cached
1284
+ */
1285
+ isCached(url: string): boolean;
1286
+ /**
1287
+ * Clear poster cache
1288
+ */
1289
+ clearCache(): void;
1290
+ }
1291
+ /**
1292
+ * Create browser video loader with default config
1293
+ */
1294
+ declare function createBrowserVideoLoader(config?: BrowserVideoLoaderConfig): BrowserVideoLoader;
1295
+ /**
1296
+ * Create browser poster loader with default config
1297
+ */
1298
+ declare function createBrowserPosterLoader(config?: {
1299
+ logger?: ILogger;
1300
+ timeout?: number;
1301
+ }): BrowserPosterLoader;
1302
+
1303
+ /**
1304
+ * LocalStorage Adapter - Browser localStorage implementation
1305
+ *
1306
+ * Real implementation of ISessionStorage using browser localStorage.
1307
+ * Automatically handles JSON serialization and SDK namespacing.
1308
+ */
1309
+
1310
+ /**
1311
+ * Configuration for LocalStorageAdapter
1312
+ */
1313
+ interface LocalStorageConfig {
1314
+ /** Storage key prefix for namespacing (default: 'sv-') */
1315
+ prefix?: string;
1316
+ /** Maximum age of snapshot in ms (default: 24h) */
1317
+ maxSnapshotAge?: number;
1318
+ /** Logger for debugging */
1319
+ logger?: ILogger;
1320
+ }
1321
+ /**
1322
+ * LocalStorageAdapter - Real browser localStorage implementation
1323
+ *
1324
+ * Features:
1325
+ * - Auto JSON serialization/deserialization
1326
+ * - SDK key namespacing (avoids conflicts with host app)
1327
+ * - Graceful error handling (returns null instead of throwing)
1328
+ * - Session snapshot with automatic staleness checking
1329
+ *
1330
+ * @example
1331
+ * ```typescript
1332
+ * const storage = new LocalStorageAdapter({ prefix: 'myapp-' });
1333
+ *
1334
+ * await storage.set('user', { id: '123', name: 'John' });
1335
+ * const user = await storage.get('user');
1336
+ * ```
1337
+ */
1338
+ declare class LocalStorageAdapter implements IStorage {
1339
+ protected readonly prefix: string;
1340
+ protected readonly logger?: ILogger;
1341
+ constructor(config?: LocalStorageConfig);
1342
+ /**
1343
+ * Build namespaced key
1344
+ */
1345
+ protected buildKey(key: string): string;
1346
+ /**
1347
+ * Get a value from localStorage
1348
+ */
1349
+ get<T>(key: string): Promise<T | null>;
1350
+ /**
1351
+ * Set a value in localStorage
1352
+ */
1353
+ set<T>(key: string, value: T): Promise<void>;
1354
+ /**
1355
+ * Remove a value from localStorage
1356
+ */
1357
+ remove(key: string): Promise<void>;
1358
+ /**
1359
+ * Clear all SDK-namespaced keys
1360
+ */
1361
+ clear(): Promise<void>;
1362
+ /**
1363
+ * Get all SDK-namespaced keys
1364
+ */
1365
+ keys(): Promise<string[]>;
1366
+ /**
1367
+ * Clear old entries when quota exceeded (simple LRU-like behavior)
1368
+ */
1369
+ private clearOldEntries;
1370
+ }
1371
+ /**
1372
+ * LocalSessionStorageAdapter - ISessionStorage implementation
1373
+ *
1374
+ * Extends LocalStorageAdapter with session snapshot methods.
1375
+ * Used by LifecycleManager for state restoration.
1376
+ *
1377
+ * @example
1378
+ * ```typescript
1379
+ * const sessionStorage = new LocalSessionStorageAdapter();
1380
+ *
1381
+ * // Save snapshot when user leaves
1382
+ * await sessionStorage.saveSnapshot({
1383
+ * currentIndex: 5,
1384
+ * cursor: 'abc123',
1385
+ * savedAt: Date.now(),
1386
+ * });
1387
+ *
1388
+ * // Restore when user returns
1389
+ * const snapshot = await sessionStorage.loadSnapshot();
1390
+ * if (snapshot) {
1391
+ * // Restore state
1392
+ * }
1393
+ * ```
1394
+ */
1395
+ declare class LocalSessionStorageAdapter extends LocalStorageAdapter implements ISessionStorage {
1396
+ private readonly maxSnapshotAge;
1397
+ constructor(config?: LocalStorageConfig);
1398
+ /**
1399
+ * Save session snapshot
1400
+ */
1401
+ saveSnapshot(snapshot: SessionSnapshot): Promise<void>;
1402
+ /**
1403
+ * Load session snapshot (returns null if stale)
1404
+ */
1405
+ loadSnapshot(): Promise<SessionSnapshot | null>;
1406
+ /**
1407
+ * Clear session snapshot
1408
+ */
1409
+ clearSnapshot(): Promise<void>;
1410
+ }
1411
+ /**
1412
+ * Create localStorage adapter with default config
1413
+ */
1414
+ declare function createLocalStorageAdapter(config?: LocalStorageConfig): LocalStorageAdapter;
1415
+ /**
1416
+ * Create session storage adapter with default config
1417
+ */
1418
+ declare function createSessionStorageAdapter(config?: LocalStorageConfig): LocalSessionStorageAdapter;
1419
+
1420
+ /**
1421
+ * Web Network Adapter - Browser Navigator.connection implementation
1422
+ *
1423
+ * Real implementation of INetworkAdapter using Web APIs.
1424
+ * Provides network detection with fallbacks for browsers without Network Information API.
1425
+ */
1426
+
1427
+ /**
1428
+ * Configuration for WebNetworkAdapter
1429
+ */
1430
+ interface WebNetworkConfig {
1431
+ /** Logger for debugging */
1432
+ logger?: ILogger;
1433
+ /** Fallback network type when API not available (default: 'wifi') */
1434
+ fallbackType?: NetworkType;
1435
+ /** Fallback downlink in Mbps (default: 10) */
1436
+ fallbackDownlink?: number;
1437
+ }
1438
+ /**
1439
+ * WebNetworkAdapter - Browser Network Information API implementation
1440
+ *
1441
+ * Features:
1442
+ * - Uses Navigator.connection when available
1443
+ * - Graceful fallback for unsupported browsers
1444
+ * - Online/offline detection via navigator.onLine
1445
+ * - Network change listeners via connection.onchange
1446
+ *
1447
+ * @example
1448
+ * ```typescript
1449
+ * const network = new WebNetworkAdapter();
1450
+ *
1451
+ * const type = await network.getNetworkType();
1452
+ * console.log('Network:', type); // 'wifi', '4g', 'offline', etc.
1453
+ *
1454
+ * // Listen for changes
1455
+ * const unsubscribe = network.onNetworkChange((type) => {
1456
+ * console.log('Network changed to:', type);
1457
+ * });
1458
+ * ```
1459
+ */
1460
+ declare class WebNetworkAdapter implements INetworkAdapter {
1461
+ private readonly logger?;
1462
+ private readonly fallbackType;
1463
+ private readonly fallbackDownlink;
1464
+ private readonly listeners;
1465
+ private connection;
1466
+ private boundOnlineHandler;
1467
+ private boundOfflineHandler;
1468
+ private boundChangeHandler;
1469
+ constructor(config?: WebNetworkConfig);
1470
+ /**
1471
+ * Get current network type
1472
+ */
1473
+ getNetworkType(): Promise<NetworkType>;
1474
+ /**
1475
+ * Get detailed network quality
1476
+ */
1477
+ getNetworkQuality(): Promise<NetworkQuality>;
1478
+ /**
1479
+ * Subscribe to network changes
1480
+ */
1481
+ onNetworkChange(callback: (type: NetworkType) => void): () => void;
1482
+ /**
1483
+ * Check if currently online
1484
+ */
1485
+ isOnline(): Promise<boolean>;
1486
+ /**
1487
+ * Cleanup listeners
1488
+ */
1489
+ destroy(): void;
1490
+ private setupListeners;
1491
+ private handleOnline;
1492
+ private handleOffline;
1493
+ private handleConnectionChange;
1494
+ private notifyListeners;
1495
+ }
1496
+ /**
1497
+ * Create web network adapter with default config
1498
+ */
1499
+ declare function createWebNetworkAdapter(config?: WebNetworkConfig): WebNetworkAdapter;
1500
+
1501
+ /**
1502
+ * createBrowserAdapters - Full preset factory for browser environment
1503
+ *
1504
+ * Creates all adapters needed for SDK with a single config object.
1505
+ * Combines REST API adapters with browser-specific adapters.
1506
+ *
1507
+ * @example
1508
+ * ```typescript
1509
+ * import { createBrowserAdapters } from '@xhub-short/adapters';
1510
+ *
1511
+ * const adapters = createBrowserAdapters({
1512
+ * baseUrl: 'https://api.myapp.com/v1',
1513
+ * auth: { getAccessToken: () => token },
1514
+ * endpoints: { ... },
1515
+ * });
1516
+ *
1517
+ * const sdk = createSDK({
1518
+ * dataSource: adapters.dataSource,
1519
+ * interaction: adapters.interaction,
1520
+ * analytics: adapters.analytics,
1521
+ * storage: adapters.storage,
1522
+ * network: adapters.network,
1523
+ * videoLoader: adapters.videoLoader,
1524
+ * posterLoader: adapters.posterLoader,
1525
+ * });
1526
+ * ```
1527
+ */
1528
+
1529
+ /**
1530
+ * Configuration for createBrowserAdapters
1531
+ */
1532
+ interface BrowserAdaptersConfig extends RESTPresetConfig {
1533
+ /** Storage configuration */
1534
+ storage?: LocalStorageConfig;
1535
+ /** Network configuration */
1536
+ network?: WebNetworkConfig;
1537
+ /** Video loader configuration */
1538
+ videoLoader?: BrowserVideoLoaderConfig;
1539
+ /** Poster loader timeout */
1540
+ posterTimeout?: number;
1541
+ }
1542
+ /**
1543
+ * Full adapter set returned by createBrowserAdapters
1544
+ */
1545
+ interface FullPresetAdapters {
1546
+ /** Data source adapter (REST API) */
1547
+ dataSource: IDataSource;
1548
+ /** Interaction adapter (REST API) */
1549
+ interaction: IInteraction;
1550
+ /** Analytics adapter (REST API or no-op) */
1551
+ analytics: IAnalytics;
1552
+ /** Session storage adapter (localStorage) */
1553
+ storage: ISessionStorage;
1554
+ /** Network adapter (Navigator.connection) */
1555
+ network: INetworkAdapter;
1556
+ /** Video preloader (fetch + Cache API) */
1557
+ videoLoader: IVideoLoader;
1558
+ /** Poster preloader (Image) */
1559
+ posterLoader: IPosterLoader;
1560
+ /** Logger (passed through) */
1561
+ logger?: ILogger;
1562
+ }
1563
+ /**
1564
+ * Create full browser adapter set from config
1565
+ *
1566
+ * This is the recommended way to integrate SDK for web applications.
1567
+ * Creates all adapters with sensible defaults.
1568
+ *
1569
+ * @param config - Combined configuration
1570
+ * @returns Full adapter set for SDK
1571
+ *
1572
+ * @example
1573
+ * ```typescript
1574
+ * // Minimal config
1575
+ * const adapters = createBrowserAdapters({
1576
+ * baseUrl: 'https://api.myapp.com',
1577
+ * auth: { getAccessToken: () => localStorage.getItem('token') },
1578
+ * endpoints: {
1579
+ * feed: { list: '/videos', detail: '/videos/:id' },
1580
+ * interaction: {
1581
+ * like: '/videos/:id/like',
1582
+ * unlike: '/videos/:id/like',
1583
+ * follow: '/users/:id/follow',
1584
+ * unfollow: '/users/:id/follow',
1585
+ * comment: '/videos/:id/comments',
1586
+ * deleteComment: '/comments/:id',
1587
+ * },
1588
+ * },
1589
+ * });
1590
+ *
1591
+ * // Full config with all options
1592
+ * const adapters = createBrowserAdapters({
1593
+ * baseUrl: 'https://api.myapp.com',
1594
+ * auth: {
1595
+ * getAccessToken: () => localStorage.getItem('token'),
1596
+ * refreshToken: () => refreshService.refresh(),
1597
+ * onAuthError: () => router.push('/login'),
1598
+ * },
1599
+ * endpoints: { ... },
1600
+ * request: {
1601
+ * timeout: 15000,
1602
+ * retry: { maxRetries: 3 },
1603
+ * },
1604
+ * storage: {
1605
+ * prefix: 'myapp-',
1606
+ * maxSnapshotAge: 12 * 60 * 60 * 1000, // 12 hours
1607
+ * },
1608
+ * videoLoader: {
1609
+ * defaultMaxBytes: 1024 * 1024, // 1MB
1610
+ * },
1611
+ * logger: myLogger,
1612
+ * });
1613
+ * ```
1614
+ */
1615
+ declare function createBrowserAdapters(config: BrowserAdaptersConfig): FullPresetAdapters;
1616
+
1617
+ /**
1618
+ * HTTP Client - Shared HTTP client với auth/retry
1619
+ *
1620
+ * Features:
1621
+ * - Auto auth token injection
1622
+ * - Token refresh on 401
1623
+ * - Retry with exponential backoff
1624
+ * - Request timeout
1625
+ * - Error normalization
1626
+ */
1627
+
1628
+ /**
1629
+ * HTTP Client configuration
1630
+ */
1631
+ interface HttpClientConfig {
1632
+ baseUrl: string;
1633
+ auth: AuthConfig;
1634
+ request?: RESTRequestConfig;
1635
+ logger?: ILogger;
1636
+ }
1637
+ /**
1638
+ * HTTP error with status code
1639
+ */
1640
+ declare class HttpError extends Error {
1641
+ readonly status: number;
1642
+ readonly body?: unknown | undefined;
1643
+ constructor(status: number, message: string, body?: unknown | undefined);
1644
+ }
1645
+ /**
1646
+ * HTTP Client class
1647
+ */
1648
+ declare class HttpClient {
1649
+ private readonly config;
1650
+ private readonly retryConfig;
1651
+ private readonly requestConfig;
1652
+ private isRefreshing;
1653
+ private refreshPromise;
1654
+ constructor(config: HttpClientConfig);
1655
+ /**
1656
+ * Make HTTP request with auth and retry
1657
+ */
1658
+ request<T>(options: HttpRequestOptions): Promise<T>;
1659
+ /**
1660
+ * Build full URL from path and path params
1661
+ */
1662
+ private buildUrl;
1663
+ /**
1664
+ * Append query params to URL
1665
+ */
1666
+ private appendQueryParams;
1667
+ /**
1668
+ * Build headers with auth token
1669
+ */
1670
+ private buildHeaders;
1671
+ /**
1672
+ * Execute request with retry logic
1673
+ */
1674
+ private executeWithRetry;
1675
+ /**
1676
+ * Handle response status and parse result
1677
+ */
1678
+ private handleResponse;
1679
+ /**
1680
+ * Handle 401 unauthorized - attempt token refresh
1681
+ */
1682
+ private handleUnauthorized;
1683
+ /**
1684
+ * Update auth header with new token
1685
+ */
1686
+ private updateAuthHeader;
1687
+ /**
1688
+ * Check if error should trigger retry
1689
+ */
1690
+ private shouldRetry;
1691
+ /**
1692
+ * Wait before retry with backoff
1693
+ */
1694
+ private waitBeforeRetry;
1695
+ /**
1696
+ * Execute fetch with timeout
1697
+ */
1698
+ private doFetch;
1699
+ /**
1700
+ * Parse response JSON
1701
+ */
1702
+ private parseResponse;
1703
+ /**
1704
+ * Safely parse JSON from response
1705
+ */
1706
+ private safeParseJson;
1707
+ /**
1708
+ * Refresh token with deduplication
1709
+ */
1710
+ private refreshTokenIfNeeded;
1711
+ /**
1712
+ * Sleep utility
1713
+ */
1714
+ private sleep;
1715
+ }
1716
+
1717
+ /**
1718
+ * Default Transforms - Auto-transform API responses to SDK format
1719
+ *
1720
+ * Default convention: snake_case API → camelCase SDK
1721
+ * Tries multiple common field names for flexibility
1722
+ */
1723
+
1724
+ /**
1725
+ * Default video item transform
1726
+ *
1727
+ * Handles common API response formats:
1728
+ * - snake_case (Laravel, Django, Rails)
1729
+ * - camelCase
1730
+ * - Nested structures
1731
+ */
1732
+ declare function defaultVideoItemTransform(apiResponse: unknown, fieldMap?: FieldMapConfig['video'], logger?: ILogger): VideoItem;
1733
+ /**
1734
+ * Default feed response transform
1735
+ */
1736
+ declare function defaultFeedResponseTransform(apiResponse: unknown, fieldMap?: FieldMapConfig['feed'], logger?: ILogger): {
1737
+ items: unknown[];
1738
+ nextCursor: string | null;
1739
+ hasMore: boolean;
1740
+ };
1741
+ /**
1742
+ * Create transforms with custom overrides
1743
+ */
1744
+ interface ResolvedTransforms {
1745
+ videoItem: (data: unknown) => VideoItem;
1746
+ feedResponse: (data: unknown) => {
1747
+ items: unknown[];
1748
+ nextCursor: string | null;
1749
+ hasMore: boolean;
1750
+ };
1751
+ }
1752
+ declare function createTransforms(config?: TransformConfig, logger?: ILogger): ResolvedTransforms;
1753
+
1754
+ /**
1755
+ * REST Data Adapter - IDataSource implementation for REST APIs
1756
+ *
1757
+ * Implements:
1758
+ * - fetchFeed: GET /videos with pagination
1759
+ * - getVideoDetail: GET /videos/:id
1760
+ * - prefetch: Optional prefetch support
1761
+ */
1762
+
1763
+ /**
1764
+ * REST Data Adapter configuration
1765
+ */
1766
+ interface RESTDataAdapterConfig {
1767
+ httpClient: HttpClient;
1768
+ endpoints: RESTEndpointMap['feed'];
1769
+ transforms: ResolvedTransforms;
1770
+ pagination?: RESTRequestConfig['pagination'];
1771
+ logger?: ILogger;
1772
+ }
1773
+ /**
1774
+ * REST Data Adapter
1775
+ */
1776
+ declare class RESTDataAdapter implements IDataSource {
1777
+ private readonly httpClient;
1778
+ private readonly endpoints;
1779
+ private readonly transforms;
1780
+ private readonly pagination;
1781
+ private readonly logger?;
1782
+ constructor(config: RESTDataAdapterConfig);
1783
+ /**
1784
+ * Fetch feed with pagination
1785
+ */
1786
+ fetchFeed(cursor?: string): Promise<FeedResponse>;
1787
+ /**
1788
+ * Get video detail by ID
1789
+ */
1790
+ getVideoDetail(id: string): Promise<VideoItem>;
1791
+ /**
1792
+ * Optional: Prefetch videos
1793
+ * This is a no-op by default, can be overridden if API supports batch fetch
1794
+ */
1795
+ prefetch(ids: string[]): Promise<void>;
1796
+ /**
1797
+ * Unwrap response if wrapped in data/result field
1798
+ */
1799
+ private unwrapResponse;
1800
+ /**
1801
+ * Create fallback video item when transform fails
1802
+ */
1803
+ private createFallbackVideoItem;
1804
+ }
1805
+
1806
+ /**
1807
+ * REST Interaction Adapter - IInteraction implementation for REST APIs
1808
+ *
1809
+ * Implements all user interaction methods:
1810
+ * - like/unlike
1811
+ * - follow/unfollow
1812
+ * - comment/deleteComment
1813
+ * - share (optional)
1814
+ */
1815
+
1816
+ /**
1817
+ * REST Interaction Adapter configuration
1818
+ */
1819
+ interface RESTInteractionAdapterConfig {
1820
+ httpClient: HttpClient;
1821
+ endpoints: RESTEndpointMap['interaction'];
1822
+ logger?: ILogger;
1823
+ }
1824
+ /**
1825
+ * REST Interaction Adapter
1826
+ */
1827
+ declare class RESTInteractionAdapter implements IInteraction {
1828
+ private readonly httpClient;
1829
+ private readonly endpoints;
1830
+ private readonly logger?;
1831
+ constructor(config: RESTInteractionAdapterConfig);
1832
+ /**
1833
+ * Like a video
1834
+ */
1835
+ like(videoId: string): Promise<void>;
1836
+ /**
1837
+ * Unlike a video
1838
+ */
1839
+ unlike(videoId: string): Promise<void>;
1840
+ /**
1841
+ * Follow an author
1842
+ */
1843
+ follow(authorId: string): Promise<void>;
1844
+ /**
1845
+ * Unfollow an author
1846
+ */
1847
+ unfollow(authorId: string): Promise<void>;
1848
+ /**
1849
+ * Post a comment
1850
+ */
1851
+ comment(videoId: string, text: string): Promise<Comment>;
1852
+ /**
1853
+ * Delete a comment
1854
+ */
1855
+ deleteComment(commentId: string): Promise<void>;
1856
+ /**
1857
+ * Like a comment
1858
+ */
1859
+ likeComment(commentId: string): Promise<void>;
1860
+ /**
1861
+ * Unlike a comment
1862
+ */
1863
+ unlikeComment(commentId: string): Promise<void>;
1864
+ /**
1865
+ * Share a video (optional tracking)
1866
+ */
1867
+ share(videoId: string, platform?: string): Promise<void>;
1868
+ /**
1869
+ * Transform API comment response to Comment type
1870
+ */
1871
+ private transformComment;
1872
+ /**
1873
+ * Unwrap response if wrapped
1874
+ */
1875
+ private unwrapResponse;
1876
+ }
1877
+
1878
+ /**
1879
+ * REST Analytics Adapter - IAnalytics implementation for REST APIs
1880
+ *
1881
+ * Implements batching strategy per ADD.md:
1882
+ * - Events queued internally
1883
+ * - Flush conditions: queue > 10, video change, visibility change
1884
+ * - Uses sendBeacon for reliability when available
1885
+ */
1886
+
1887
+ /**
1888
+ * REST Analytics Adapter configuration
1889
+ */
1890
+ interface RESTAnalyticsAdapterConfig {
1891
+ httpClient: HttpClient;
1892
+ endpoints: NonNullable<RESTEndpointMap['analytics']>;
1893
+ batchSize?: number;
1894
+ flushInterval?: number;
1895
+ logger?: ILogger;
1896
+ }
1897
+ /**
1898
+ * REST Analytics Adapter
1899
+ */
1900
+ declare class RESTAnalyticsAdapter implements IAnalytics {
1901
+ private readonly httpClient;
1902
+ private readonly endpoint;
1903
+ private readonly batchSize;
1904
+ private readonly logger?;
1905
+ private queue;
1906
+ private flushTimer;
1907
+ private userId;
1908
+ private userProperties;
1909
+ constructor(config: RESTAnalyticsAdapterConfig);
1910
+ /**
1911
+ * Track an analytics event
1912
+ * Fire-and-forget - doesn't throw
1913
+ */
1914
+ track(event: AnalyticsEvent): void;
1915
+ /**
1916
+ * Flush queued events
1917
+ */
1918
+ flush(): Promise<void>;
1919
+ /**
1920
+ * Track video view duration (heartbeat)
1921
+ */
1922
+ trackViewDuration(videoId: string, duration: number, totalDuration: number): void;
1923
+ /**
1924
+ * Track video completion
1925
+ */
1926
+ trackCompletion(videoId: string, watchTime: number, loops: number): void;
1927
+ /**
1928
+ * Set user context
1929
+ */
1930
+ setUser(userId: string | null, properties?: Record<string, unknown>): void;
1931
+ /**
1932
+ * Get current queue size (for debugging)
1933
+ */
1934
+ getQueueSize(): number;
1935
+ /**
1936
+ * Cleanup - stop flush interval
1937
+ */
1938
+ destroy(): void;
1939
+ /**
1940
+ * Try to send via sendBeacon (for reliability on page unload)
1941
+ */
1942
+ private trySendBeacon;
1943
+ /**
1944
+ * Build full URL for sendBeacon
1945
+ */
1946
+ private buildFullUrl;
1947
+ }
1948
+ /**
1949
+ * Create a no-op analytics adapter
1950
+ * Used when analytics endpoint is not configured
1951
+ */
1952
+ declare function createNoOpAnalyticsAdapter(): IAnalytics;
1953
+
1954
+ export { type AuthConfig, type AuthError, type BrowserAdaptersConfig, BrowserPosterLoader, BrowserVideoLoader, type BrowserVideoLoaderConfig, DEFAULT_REQUEST_CONFIG, DEFAULT_RETRY_CONFIG, type FieldMapConfig, type FullPresetAdapters, HttpClient, HttpError, LocalSessionStorageAdapter, LocalStorageAdapter, type LocalStorageConfig, MockAnalyticsAdapter, type MockAnalyticsAdapterOptions, MockDataAdapter, type MockDataAdapterOptions, MockInteractionAdapter, type MockInteractionAdapterOptions, MockLoggerAdapter, type MockLoggerAdapterOptions, MockNetworkAdapter, type MockNetworkAdapterOptions, MockPosterLoader, MockSessionStorageAdapter, type MockSessionStorageAdapterOptions, MockStorageAdapter, type MockStorageAdapterOptions, MockVideoLoader, type MockVideoLoaderOptions, type PresetAdapters, RESTAnalyticsAdapter, RESTDataAdapter, type RESTEndpointMap, RESTInteractionAdapter, type RESTPresetConfig, type RESTRequestConfig, type ResolvedTransforms, type RetryConfig, type TransformConfig, WebNetworkAdapter, type WebNetworkConfig, createBrowserAdapters, createBrowserPosterLoader, createBrowserVideoLoader, createLocalStorageAdapter, createNoOpAnalyticsAdapter, createRESTAdapters, createSessionStorageAdapter, createTransforms, createWebNetworkAdapter, defaultFeedResponseTransform, defaultVideoItemTransform };