@xhub-short/adapters 0.1.0-beta.1 → 0.1.0-beta.10

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +755 -5
  2. package/dist/index.js +1128 -21
  3. package/package.json +4 -3
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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';
1
+ import { IDataSource, VideoItem, FeedResponse, ILogger, LogLevel, LogEntry, IStorage, ISessionStorage, SessionSnapshot, IInteraction, Comment, ICommentAdapter, MockCommentAdapterConfig, CommentListResponse, ReplyListResponse, PostCommentPayload, CommentItem, PostReplyPayload, ReplyItem, EditCommentPayload, DeleteCommentPayload, ReportCommentPayload, IAnalytics, AnalyticsEvent, INetworkAdapter, NetworkType, NetworkQuality, IVideoLoader, VideoSource, PreloadConfig, PreloadResult, PreloadStatus, IPosterLoader, CommentTransformers } from '@xhub-short/contracts';
2
2
 
3
3
  /**
4
4
  * MockDataAdapter - Development/Testing Data Source
@@ -511,6 +511,48 @@ declare class MockInteractionAdapter implements IInteraction {
511
511
  private maybeThrowError;
512
512
  }
513
513
 
514
+ /**
515
+ * Mock Comment Adapter for testing and development
516
+ *
517
+ * Features:
518
+ * - Simulated network delay
519
+ * - Configurable error simulation
520
+ * - In-memory storage
521
+ */
522
+ declare class MockCommentAdapter implements ICommentAdapter {
523
+ private comments;
524
+ private replies;
525
+ private delay;
526
+ private simulateErrors;
527
+ private errorRate;
528
+ private idCounter;
529
+ constructor(config?: MockCommentAdapterConfig);
530
+ private simulateNetworkDelay;
531
+ private generateId;
532
+ getComments(videoId: string, cursor?: string | null, limit?: number): Promise<CommentListResponse>;
533
+ getReplies(commentId: string, cursor?: string | null, limit?: number): Promise<ReplyListResponse>;
534
+ postComment(payload: PostCommentPayload): Promise<CommentItem>;
535
+ postReply(payload: PostReplyPayload): Promise<ReplyItem>;
536
+ editComment(payload: EditCommentPayload): Promise<CommentItem | ReplyItem>;
537
+ deleteComment(payload: DeleteCommentPayload): Promise<void>;
538
+ likeComment(id: string): Promise<void>;
539
+ unlikeComment(id: string): Promise<void>;
540
+ reportComment(_payload: ReportCommentPayload): Promise<void>;
541
+ private updateLikeState;
542
+ /**
543
+ * Clear all stored data (for testing)
544
+ */
545
+ clearAll(): void;
546
+ /**
547
+ * Get stored comments count (for testing)
548
+ */
549
+ getCommentsCount(videoId: string): number;
550
+ /**
551
+ * Get stored replies count (for testing)
552
+ */
553
+ getRepliesCount(commentId: string): number;
554
+ }
555
+
514
556
  /**
515
557
  * Configuration options for MockAnalyticsAdapter
516
558
  */
@@ -950,13 +992,61 @@ interface RESTEndpointMap {
950
992
  share?: string;
951
993
  };
952
994
  /**
953
- * Analytics endpoints (optional)
954
- * If not provided, analytics adapter will be no-op
995
+ * Analytics endpoints (optional) - BATCH mode
996
+ * If provided, uses RESTAnalyticsAdapter with batch sending
955
997
  */
956
998
  analytics?: {
957
999
  /** POST endpoint for batch events */
958
1000
  batch: string;
959
1001
  };
1002
+ /**
1003
+ * View tracking endpoint (optional) - PER-VIDEO mode
1004
+ * If provided, uses RESTViewTrackingAdapter with per-video tracking
1005
+ *
1006
+ * Priority: viewTracking > analytics (if both provided, viewTracking wins)
1007
+ *
1008
+ * @example
1009
+ * ```typescript
1010
+ * viewTracking: {
1011
+ * notify: '/reels/:id/notify-views',
1012
+ * }
1013
+ * ```
1014
+ */
1015
+ viewTracking?: {
1016
+ /**
1017
+ * POST endpoint for view notification
1018
+ * :id will be replaced with video ID
1019
+ */
1020
+ notify: string;
1021
+ /**
1022
+ * Path param name for video ID
1023
+ * @default 'id'
1024
+ */
1025
+ pathParam?: string;
1026
+ };
1027
+ /**
1028
+ * Comment endpoints (optional)
1029
+ * If not provided, comment adapter will not be created
1030
+ * Note: postReply uses same endpoint as post, with reply_to_comment_id in body
1031
+ */
1032
+ comment?: {
1033
+ /** GET /videos/:videoId/comments */
1034
+ list: string;
1035
+ /** GET /comments/:commentId/replies */
1036
+ replies: string;
1037
+ /** POST /videos/:videoId/comments (also for replies with reply_to_comment_id) */
1038
+ post: string;
1039
+ /** PATCH /comments/:id */
1040
+ edit: string;
1041
+ /** DELETE /comments/:id */
1042
+ delete: string;
1043
+ /** POST /comments/:id/like */
1044
+ like: string;
1045
+ /** DELETE /comments/:id/like */
1046
+ unlike: string;
1047
+ /** POST /comments/:id/report */
1048
+ report: string;
1049
+ };
960
1050
  }
961
1051
  /**
962
1052
  * HTTP request configuration
@@ -1053,6 +1143,225 @@ interface FieldMapConfig {
1053
1143
  hasMore?: string;
1054
1144
  };
1055
1145
  }
1146
+ /**
1147
+ * Event types for batch analytics
1148
+ * Maps SDK internal types to API event types
1149
+ */
1150
+ type BatchAnalyticsEventType = 'view_start' | 'view_end' | 'scroll_pass' | 'like' | 'comment' | 'share' | 'follow_creator' | 'save' | 'report';
1151
+ /**
1152
+ * Device type for analytics context
1153
+ */
1154
+ type BatchAnalyticsDeviceType = 'ios' | 'android' | 'web';
1155
+ /**
1156
+ * Network type for analytics context
1157
+ */
1158
+ type BatchAnalyticsNetworkType = 'wifi' | '4g' | '5g' | 'other';
1159
+ /**
1160
+ * SDK internal analytics event data
1161
+ * Passed to transform function
1162
+ */
1163
+ interface BatchAnalyticsEventData {
1164
+ /** SDK event type (will be mapped to API event type) */
1165
+ sdkEventType: string;
1166
+ /** Video ID */
1167
+ videoId?: string;
1168
+ /** Event timestamp */
1169
+ timestamp: number;
1170
+ /** User ID (from setUser) */
1171
+ userId: string | null;
1172
+ /** User properties (from setUser) */
1173
+ userProperties: Record<string, unknown>;
1174
+ /** Additional event data from SDK */
1175
+ data?: Record<string, unknown>;
1176
+ }
1177
+ /**
1178
+ * Context data for analytics events
1179
+ * Provided by Host App via config
1180
+ */
1181
+ interface BatchAnalyticsContext {
1182
+ /** Get current session ID */
1183
+ getSessionId: () => string | null | Promise<string | null>;
1184
+ /** Get device type */
1185
+ getDeviceType: () => BatchAnalyticsDeviceType;
1186
+ /** Get network type */
1187
+ getNetworkType: () => BatchAnalyticsNetworkType | Promise<BatchAnalyticsNetworkType>;
1188
+ /** Get geo location (optional) */
1189
+ getGeoLocation?: () => string | null | Promise<string | null>;
1190
+ /** Get current video position index in feed */
1191
+ getPositionIndex?: () => number;
1192
+ /** Get scroll speed in ms (optional) */
1193
+ getScrollSpeed?: () => number | null;
1194
+ }
1195
+ /**
1196
+ * Single event in batch request body (after transform)
1197
+ */
1198
+ type BatchAnalyticsRequestEvent = Record<string, unknown>;
1199
+ /**
1200
+ * Full request body for batch analytics
1201
+ */
1202
+ interface BatchAnalyticsRequestBody {
1203
+ events: BatchAnalyticsRequestEvent[];
1204
+ }
1205
+ /**
1206
+ * Transform function to convert SDK event to API event format
1207
+ *
1208
+ * @example
1209
+ * ```typescript
1210
+ * const transform: BatchAnalyticsEventTransformer = async (event, context) => ({
1211
+ * event_id: crypto.randomUUID(),
1212
+ * user_id: event.userId,
1213
+ * video_id: event.videoId,
1214
+ * session_id: await context.getSessionId(),
1215
+ * event_type: mapEventType(event.sdkEventType),
1216
+ * position_index: context.getPositionIndex?.() ?? 0,
1217
+ * impression_timestamp: new Date(event.timestamp).toISOString(),
1218
+ * scroll_speed_ms: context.getScrollSpeed?.() ?? null,
1219
+ * device_type: context.getDeviceType(),
1220
+ * network_type: await context.getNetworkType(),
1221
+ * geo_location: await context.getGeoLocation?.() ?? null,
1222
+ * time_of_day: new Date(event.timestamp).getHours(),
1223
+ * day_of_week: new Date(event.timestamp).getDay(),
1224
+ * });
1225
+ * ```
1226
+ */
1227
+ type BatchAnalyticsEventTransformer = (event: BatchAnalyticsEventData, context: BatchAnalyticsContext) => BatchAnalyticsRequestEvent | Promise<BatchAnalyticsRequestEvent>;
1228
+ /**
1229
+ * Batch analytics configuration for preset
1230
+ *
1231
+ * @example
1232
+ * ```typescript
1233
+ * const batchAnalytics: BatchAnalyticsConfig = {
1234
+ * batchSize: 10,
1235
+ * flushInterval: 30000,
1236
+ * context: {
1237
+ * getSessionId: () => sessionStorage.getItem('session_id'),
1238
+ * getDeviceType: () => 'web',
1239
+ * getNetworkType: () => navigator.connection?.effectiveType === '4g' ? '4g' : 'wifi',
1240
+ * getGeoLocation: () => 'Ho Chi Minh City, Vietnam',
1241
+ * getPositionIndex: () => currentIndex,
1242
+ * },
1243
+ * transform: async (event, ctx) => ({
1244
+ * event_id: crypto.randomUUID(),
1245
+ * user_id: event.userId,
1246
+ * video_id: event.videoId,
1247
+ * session_id: await ctx.getSessionId(),
1248
+ * event_type: event.sdkEventType,
1249
+ * // ... more fields
1250
+ * }),
1251
+ * };
1252
+ * ```
1253
+ */
1254
+ interface BatchAnalyticsConfig {
1255
+ /**
1256
+ * Batch size threshold for auto-flush
1257
+ * @default 10
1258
+ */
1259
+ batchSize?: number;
1260
+ /**
1261
+ * Flush interval in milliseconds
1262
+ * @default 30000 (30 seconds)
1263
+ */
1264
+ flushInterval?: number;
1265
+ /**
1266
+ * Context providers for enriching events
1267
+ * Required if using custom transform
1268
+ */
1269
+ context?: BatchAnalyticsContext;
1270
+ /**
1271
+ * Custom transform function
1272
+ * If not provided, uses default SDK format
1273
+ */
1274
+ transform?: BatchAnalyticsEventTransformer;
1275
+ /**
1276
+ * Map SDK event types to API event types
1277
+ * Used by default transform if no custom transform provided
1278
+ *
1279
+ * @example
1280
+ * ```typescript
1281
+ * eventTypeMap: {
1282
+ * 'video_view': 'view_start',
1283
+ * 'video_complete': 'view_end',
1284
+ * 'scroll': 'scroll_pass',
1285
+ * 'like': 'like',
1286
+ * 'follow': 'follow_creator',
1287
+ * }
1288
+ * ```
1289
+ */
1290
+ eventTypeMap?: Record<string, BatchAnalyticsEventType>;
1291
+ /**
1292
+ * Use sendBeacon API for flushing events
1293
+ *
1294
+ * sendBeacon is more reliable for page unload events but shows as "ping" in Network tab.
1295
+ * Set to false to always use HTTP POST (easier for debugging).
1296
+ *
1297
+ * @default true
1298
+ */
1299
+ useSendBeacon?: boolean;
1300
+ }
1301
+ /**
1302
+ * View event data passed to transformer
1303
+ */
1304
+ interface ViewEventData {
1305
+ /** Video/Reel ID */
1306
+ videoId: string;
1307
+ /** Current playback time in seconds */
1308
+ currentTime: number;
1309
+ /** Total video duration in seconds */
1310
+ duration: number;
1311
+ /** Internal event type (for SDK logic) */
1312
+ internalEventType: 'play' | 'heartbeat' | 'complete' | 'leave';
1313
+ /** Watch time so far (for completion events) */
1314
+ watchTime?: number;
1315
+ /** Loop count (for completion events) */
1316
+ loopCount?: number;
1317
+ /** Timestamp */
1318
+ timestamp: number;
1319
+ }
1320
+ /**
1321
+ * Request body after transformation
1322
+ * Allows custom field names via fieldMap
1323
+ */
1324
+ type ViewEventRequestBody = Record<string, unknown>;
1325
+ /**
1326
+ * Transform function to convert SDK data to API request body
1327
+ */
1328
+ type ViewEventTransformer = (data: ViewEventData) => ViewEventRequestBody;
1329
+ /**
1330
+ * View tracking configuration for preset
1331
+ */
1332
+ interface ViewTrackingConfig {
1333
+ /**
1334
+ * Value for view_event field
1335
+ * @default 'seek'
1336
+ */
1337
+ viewEventValue?: string;
1338
+ /**
1339
+ * Heartbeat interval in milliseconds
1340
+ * @default 10000 (10 seconds)
1341
+ */
1342
+ heartbeatInterval?: number;
1343
+ /**
1344
+ * Minimum watch time (seconds) before sending first event
1345
+ * @default 0
1346
+ */
1347
+ minWatchTime?: number;
1348
+ /**
1349
+ * Track when user scrolls away from video
1350
+ * @default true
1351
+ */
1352
+ trackLeave?: boolean;
1353
+ /**
1354
+ * Custom request body transformer
1355
+ */
1356
+ transform?: ViewEventTransformer;
1357
+ /**
1358
+ * Field mapping for default transformer
1359
+ */
1360
+ fieldMap?: {
1361
+ currentTime?: string;
1362
+ viewEvent?: string;
1363
+ };
1364
+ }
1056
1365
  /**
1057
1366
  * REST Preset Adapter configuration
1058
1367
  */
@@ -1079,6 +1388,35 @@ interface RESTPresetConfig {
1079
1388
  * Request configuration (optional)
1080
1389
  */
1081
1390
  request?: RESTRequestConfig;
1391
+ /**
1392
+ * View tracking configuration (optional)
1393
+ * Only used if endpoints.viewTracking is configured
1394
+ */
1395
+ viewTracking?: ViewTrackingConfig;
1396
+ /**
1397
+ * Batch analytics configuration (optional)
1398
+ * Only used if endpoints.analytics is configured
1399
+ *
1400
+ * @example
1401
+ * ```typescript
1402
+ * batchAnalytics: {
1403
+ * batchSize: 10,
1404
+ * flushInterval: 30000,
1405
+ * context: {
1406
+ * getSessionId: () => sessionStorage.getItem('session_id'),
1407
+ * getDeviceType: () => 'web',
1408
+ * getNetworkType: () => 'wifi',
1409
+ * },
1410
+ * transform: (event, ctx) => ({
1411
+ * event_id: crypto.randomUUID(),
1412
+ * user_id: event.userId,
1413
+ * video_id: event.videoId,
1414
+ * // ... custom format
1415
+ * }),
1416
+ * }
1417
+ * ```
1418
+ */
1419
+ batchAnalytics?: BatchAnalyticsConfig;
1082
1420
  /**
1083
1421
  * Logger adapter for error/warning logging
1084
1422
  * If not provided, uses console in dev, silent in prod
@@ -1092,6 +1430,8 @@ interface PresetAdapters {
1092
1430
  dataSource: IDataSource;
1093
1431
  interaction: IInteraction;
1094
1432
  analytics: IAnalytics;
1433
+ /** Comment adapter (only if comment endpoints are configured) */
1434
+ comment?: ICommentAdapter;
1095
1435
  }
1096
1436
  /**
1097
1437
  * HTTP request options (internal)
@@ -1557,6 +1897,8 @@ interface FullPresetAdapters {
1557
1897
  videoLoader: IVideoLoader;
1558
1898
  /** Poster preloader (Image) */
1559
1899
  posterLoader: IPosterLoader;
1900
+ /** Comment adapter (REST API, only if comment endpoints configured) */
1901
+ comment?: ICommentAdapter;
1560
1902
  /** Logger (passed through) */
1561
1903
  logger?: ILogger;
1562
1904
  }
@@ -1649,6 +1991,10 @@ declare class HttpClient {
1649
1991
  private readonly config;
1650
1992
  private readonly retryConfig;
1651
1993
  private readonly requestConfig;
1994
+ /**
1995
+ * Get base URL (for sendBeacon which needs full URL)
1996
+ */
1997
+ getBaseUrl(): string;
1652
1998
  private isRefreshing;
1653
1999
  private refreshPromise;
1654
2000
  constructor(config: HttpClientConfig);
@@ -1714,6 +2060,127 @@ declare class HttpClient {
1714
2060
  private sleep;
1715
2061
  }
1716
2062
 
2063
+ /**
2064
+ * CircuitBreaker - Prevents cascade failures
2065
+ *
2066
+ * State transitions:
2067
+ * CLOSED → (failures >= threshold) → OPEN
2068
+ * OPEN → (cooldown passes) → HALF_OPEN
2069
+ * HALF_OPEN → (success) → CLOSED
2070
+ * HALF_OPEN → (failure) → OPEN
2071
+ *
2072
+ * @packageDocumentation
2073
+ */
2074
+ /**
2075
+ * Circuit breaker states
2076
+ */
2077
+ type CircuitState = 'closed' | 'open' | 'half-open';
2078
+ /**
2079
+ * Circuit breaker configuration
2080
+ */
2081
+ interface CircuitBreakerConfig {
2082
+ /** Number of failures before opening (default: 5) */
2083
+ failureThreshold?: number;
2084
+ /** Time window for failure counting in ms (default: 30000) */
2085
+ failureWindow?: number;
2086
+ /** Cool-down period before half-open in ms (default: 30000) */
2087
+ cooldownPeriod?: number;
2088
+ /** Number of successes in half-open to close (default: 1) */
2089
+ successThreshold?: number;
2090
+ /** Name for logging */
2091
+ name?: string;
2092
+ }
2093
+ /**
2094
+ * Circuit breaker events
2095
+ */
2096
+ interface CircuitBreakerEvents {
2097
+ stateChange: (state: CircuitState, previousState: CircuitState) => void;
2098
+ failure: (error: Error, failureCount: number) => void;
2099
+ success: () => void;
2100
+ rejected: () => void;
2101
+ }
2102
+ /**
2103
+ * Error thrown when circuit is open
2104
+ */
2105
+ declare class CircuitOpenError extends Error {
2106
+ constructor(message?: string);
2107
+ }
2108
+ /**
2109
+ * CircuitBreaker - Prevents cascade failures
2110
+ *
2111
+ * @example
2112
+ * ```typescript
2113
+ * const breaker = new CircuitBreaker({ failureThreshold: 5 });
2114
+ *
2115
+ * async function fetchData() {
2116
+ * return breaker.execute(async () => {
2117
+ * const response = await fetch('/api/data');
2118
+ * return response.json();
2119
+ * });
2120
+ * }
2121
+ * ```
2122
+ */
2123
+ declare class CircuitBreaker {
2124
+ private state;
2125
+ private failures;
2126
+ private lastFailureTime;
2127
+ private halfOpenSuccesses;
2128
+ private config;
2129
+ private listeners;
2130
+ constructor(config?: CircuitBreakerConfig);
2131
+ /**
2132
+ * Get current circuit state
2133
+ */
2134
+ getState(): CircuitState;
2135
+ /**
2136
+ * Check if circuit allows requests
2137
+ */
2138
+ isAllowed(): boolean;
2139
+ /**
2140
+ * Execute a function through the circuit breaker
2141
+ */
2142
+ execute<T>(fn: () => Promise<T>): Promise<T>;
2143
+ /**
2144
+ * Record a successful call
2145
+ */
2146
+ recordSuccess(): void;
2147
+ /**
2148
+ * Record a failed call
2149
+ */
2150
+ recordFailure(error: Error): void;
2151
+ /**
2152
+ * Manually reset the circuit breaker
2153
+ */
2154
+ reset(): void;
2155
+ /**
2156
+ * Subscribe to events
2157
+ */
2158
+ on<K extends keyof CircuitBreakerEvents>(event: K, listener: CircuitBreakerEvents[K]): () => void;
2159
+ /**
2160
+ * Get failure count
2161
+ */
2162
+ getFailureCount(): number;
2163
+ /**
2164
+ * Get config
2165
+ */
2166
+ getConfig(): Required<CircuitBreakerConfig>;
2167
+ private updateState;
2168
+ private transitionTo;
2169
+ private emit;
2170
+ }
2171
+ /**
2172
+ * Get or create a circuit breaker by name
2173
+ */
2174
+ declare function getCircuitBreaker(name?: string, config?: CircuitBreakerConfig): CircuitBreaker;
2175
+ /**
2176
+ * Reset all circuit breakers
2177
+ */
2178
+ declare function resetAllCircuitBreakers(): void;
2179
+ /**
2180
+ * Get global circuit breaker (default)
2181
+ */
2182
+ declare function getGlobalCircuitBreaker(): CircuitBreaker;
2183
+
1717
2184
  /**
1718
2185
  * Default Transforms - Auto-transform API responses to SDK format
1719
2186
  *
@@ -1882,6 +2349,34 @@ declare class RESTInteractionAdapter implements IInteraction {
1882
2349
  * - Events queued internally
1883
2350
  * - Flush conditions: queue > 10, video change, visibility change
1884
2351
  * - Uses sendBeacon for reliability when available
2352
+ *
2353
+ * Supports custom transform for flexible API formats:
2354
+ * - Default: SDK format { events: [...] }
2355
+ * - Custom: Any format via transform function
2356
+ *
2357
+ * @example
2358
+ * ```typescript
2359
+ * // With custom transform for specific API format
2360
+ * const adapter = new RESTAnalyticsAdapter({
2361
+ * httpClient,
2362
+ * endpoints: { batch: '/api/v1/events/track-batch' },
2363
+ * config: {
2364
+ * context: {
2365
+ * getSessionId: () => sessionStorage.getItem('session_id'),
2366
+ * getDeviceType: () => 'web',
2367
+ * getNetworkType: () => 'wifi',
2368
+ * },
2369
+ * transform: async (event, ctx) => ({
2370
+ * event_id: crypto.randomUUID(),
2371
+ * user_id: event.userId,
2372
+ * video_id: event.videoId,
2373
+ * session_id: await ctx.getSessionId(),
2374
+ * event_type: event.sdkEventType,
2375
+ * // ... more fields
2376
+ * }),
2377
+ * },
2378
+ * });
2379
+ * ```
1885
2380
  */
1886
2381
 
1887
2382
  /**
@@ -1890,22 +2385,35 @@ declare class RESTInteractionAdapter implements IInteraction {
1890
2385
  interface RESTAnalyticsAdapterConfig {
1891
2386
  httpClient: HttpClient;
1892
2387
  endpoints: NonNullable<RESTEndpointMap['analytics']>;
2388
+ /** Batch analytics config with transform and context */
2389
+ config?: BatchAnalyticsConfig;
2390
+ /** @deprecated Use config.batchSize instead */
1893
2391
  batchSize?: number;
2392
+ /** @deprecated Use config.flushInterval instead */
1894
2393
  flushInterval?: number;
1895
2394
  logger?: ILogger;
1896
2395
  }
1897
2396
  /**
1898
2397
  * REST Analytics Adapter
2398
+ *
2399
+ * Supports two modes:
2400
+ * 1. Default mode: Uses SDK format { events: [...] }
2401
+ * 2. Custom mode: Uses transform function for custom API format
1899
2402
  */
1900
2403
  declare class RESTAnalyticsAdapter implements IAnalytics {
1901
2404
  private readonly httpClient;
1902
2405
  private readonly endpoint;
1903
2406
  private readonly batchSize;
1904
2407
  private readonly logger?;
2408
+ private readonly transform?;
2409
+ private readonly context;
2410
+ private readonly eventTypeMap;
2411
+ private readonly useSendBeacon;
1905
2412
  private queue;
1906
2413
  private flushTimer;
1907
2414
  private userId;
1908
2415
  private userProperties;
2416
+ private viewedVideos;
1909
2417
  constructor(config: RESTAnalyticsAdapterConfig);
1910
2418
  /**
1911
2419
  * Track an analytics event
@@ -1917,9 +2425,23 @@ declare class RESTAnalyticsAdapter implements IAnalytics {
1917
2425
  */
1918
2426
  flush(): Promise<void>;
1919
2427
  /**
1920
- * Track video view duration (heartbeat)
2428
+ * Build request body from events
2429
+ * Uses custom transform if provided, otherwise SDK default format
2430
+ */
2431
+ private buildRequestBody;
2432
+ /**
2433
+ * Track video view duration
2434
+ *
2435
+ * Note: PlayerEngine calls this every second (heartbeat), but for batch analytics
2436
+ * we only need ONE view_start per video. This method deduplicates by videoId.
2437
+ *
2438
+ * If you need heartbeat tracking, use RESTViewTrackingAdapter instead.
1921
2439
  */
1922
2440
  trackViewDuration(videoId: string, duration: number, totalDuration: number): void;
2441
+ /**
2442
+ * Clear viewed videos cache (useful for testing or session reset)
2443
+ */
2444
+ clearViewedVideos(): void;
1923
2445
  /**
1924
2446
  * Track video completion
1925
2447
  */
@@ -1938,12 +2460,23 @@ declare class RESTAnalyticsAdapter implements IAnalytics {
1938
2460
  destroy(): void;
1939
2461
  /**
1940
2462
  * Try to send via sendBeacon (for reliability on page unload)
2463
+ * Note: sendBeacon shows as "ping" type in Network tab, not "POST"
2464
+ *
2465
+ * Set useSendBeacon: false in config to always use HTTP POST
1941
2466
  */
1942
2467
  private trySendBeacon;
1943
2468
  /**
1944
2469
  * Build full URL for sendBeacon
1945
2470
  */
1946
2471
  private buildFullUrl;
2472
+ /**
2473
+ * Get mapped API event type from SDK event type
2474
+ */
2475
+ getApiEventType(sdkEventType: string): BatchAnalyticsEventType;
2476
+ /**
2477
+ * Get current context (for debugging/testing)
2478
+ */
2479
+ getContext(): BatchAnalyticsContext;
1947
2480
  }
1948
2481
  /**
1949
2482
  * Create a no-op analytics adapter
@@ -1951,4 +2484,221 @@ declare class RESTAnalyticsAdapter implements IAnalytics {
1951
2484
  */
1952
2485
  declare function createNoOpAnalyticsAdapter(): IAnalytics;
1953
2486
 
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 };
2487
+ /**
2488
+ * REST View Tracking Adapter - Per-video view tracking via REST API
2489
+ *
2490
+ * Implements IAnalytics interface with throttled, fire-and-forget tracking.
2491
+ * Uses shared HttpClient from preset (auth, retry, timeout).
2492
+ *
2493
+ * @example
2494
+ * ```typescript
2495
+ * // Created automatically by createRESTAdapters when viewTracking is configured
2496
+ * const adapters = createRESTAdapters({
2497
+ * baseUrl: 'https://api.myapp.com/v1',
2498
+ * auth: { ... },
2499
+ * endpoints: {
2500
+ * feed: { ... },
2501
+ * interaction: { ... },
2502
+ * viewTracking: {
2503
+ * notify: '/reels/:id/notify-views',
2504
+ * },
2505
+ * },
2506
+ * });
2507
+ * ```
2508
+ */
2509
+
2510
+ /**
2511
+ * REST View Tracking Adapter configuration
2512
+ */
2513
+ interface RESTViewTrackingAdapterConfig {
2514
+ httpClient: HttpClient;
2515
+ endpoint: NonNullable<RESTEndpointMap['viewTracking']>;
2516
+ config?: ViewTrackingConfig;
2517
+ logger?: ILogger;
2518
+ }
2519
+ /**
2520
+ * RESTViewTrackingAdapter - Per-video view tracking via REST API
2521
+ *
2522
+ * Uses shared HttpClient (inherits auth, retry, timeout from preset).
2523
+ * Implements throttled, fire-and-forget tracking.
2524
+ */
2525
+ declare class RESTViewTrackingAdapter implements IAnalytics {
2526
+ private readonly httpClient;
2527
+ private readonly endpoint;
2528
+ private readonly transform;
2529
+ private readonly viewEventValue;
2530
+ private readonly heartbeatInterval;
2531
+ private readonly minWatchTime;
2532
+ private readonly trackLeaveEnabled;
2533
+ private readonly logger?;
2534
+ private lastTrackTime;
2535
+ private lastEventType;
2536
+ private _userId;
2537
+ private _userProperties;
2538
+ /** Get current user ID */
2539
+ get userId(): string | null;
2540
+ /** Get current user properties */
2541
+ get userProperties(): Record<string, unknown>;
2542
+ constructor(config: RESTViewTrackingAdapterConfig);
2543
+ /**
2544
+ * Track generic event
2545
+ * Delegates video events to specific handlers
2546
+ */
2547
+ track(event: AnalyticsEvent): void;
2548
+ /**
2549
+ * Flush - No-op since we send events immediately (fire-and-forget)
2550
+ */
2551
+ flush(): Promise<void>;
2552
+ /**
2553
+ * Track view duration (heartbeat)
2554
+ * Called periodically during video playback
2555
+ */
2556
+ trackViewDuration(videoId: string, duration: number, totalDuration: number): void;
2557
+ /**
2558
+ * Track video completion
2559
+ * Called when video ends or loops
2560
+ */
2561
+ trackCompletion(videoId: string, watchTime: number, loopCount: number): void;
2562
+ /**
2563
+ * Set user context
2564
+ */
2565
+ setUser(userId: string | null, properties?: Record<string, unknown>): void;
2566
+ /**
2567
+ * Get queue size (always 0 - no batching)
2568
+ */
2569
+ getQueueSize(): number;
2570
+ /**
2571
+ * Track when user leaves video
2572
+ * Called when user scrolls to another video
2573
+ */
2574
+ trackLeaveVideo(videoId: string, currentTime: number, duration: number): void;
2575
+ /**
2576
+ * Clear tracking state for a video
2577
+ * Call when video is removed from viewport
2578
+ */
2579
+ clearVideoState(videoId: string): void;
2580
+ /**
2581
+ * Clear all tracking state
2582
+ */
2583
+ reset(): void;
2584
+ /**
2585
+ * Send view event to API (fire-and-forget)
2586
+ */
2587
+ private sendViewEvent;
2588
+ }
2589
+
2590
+ /**
2591
+ * REST Comment Adapter - ICommentAdapter implementation for REST APIs
2592
+ *
2593
+ * Implements all comment operations:
2594
+ * - getComments / getReplies
2595
+ * - postComment / postReply
2596
+ * - editComment / deleteComment
2597
+ * - likeComment / unlikeComment
2598
+ * - reportComment
2599
+ */
2600
+
2601
+ /**
2602
+ * Comment endpoints configuration (required, not optional)
2603
+ */
2604
+ interface CommentEndpointsConfig {
2605
+ /** GET /videos/:videoId/comments */
2606
+ list: string;
2607
+ /** GET /comments/:commentId/replies */
2608
+ replies: string;
2609
+ /** POST /videos/:videoId/comments */
2610
+ post: string;
2611
+ /** PATCH /comments/:id */
2612
+ edit: string;
2613
+ /** DELETE /comments/:id */
2614
+ delete: string;
2615
+ /** POST /comments/:id/like */
2616
+ like: string;
2617
+ /** DELETE /comments/:id/like */
2618
+ unlike: string;
2619
+ /** POST /comments/:id/report */
2620
+ report: string;
2621
+ }
2622
+ /**
2623
+ * REST Comment Adapter configuration
2624
+ */
2625
+ interface RESTCommentAdapterConfig {
2626
+ httpClient: HttpClient;
2627
+ endpoints: CommentEndpointsConfig;
2628
+ transformers?: CommentTransformers;
2629
+ logger?: ILogger;
2630
+ }
2631
+ /**
2632
+ * REST Comment Adapter
2633
+ */
2634
+ declare class RESTCommentAdapter implements ICommentAdapter {
2635
+ private readonly httpClient;
2636
+ private readonly endpoints;
2637
+ private readonly transformers?;
2638
+ private readonly logger?;
2639
+ constructor(config: RESTCommentAdapterConfig);
2640
+ /**
2641
+ * Get comments for a video
2642
+ */
2643
+ getComments(videoId: string, cursor?: string | null, limit?: number): Promise<CommentListResponse>;
2644
+ /**
2645
+ * Get replies for a comment
2646
+ */
2647
+ getReplies(commentId: string, cursor?: string | null, limit?: number): Promise<ReplyListResponse>;
2648
+ /**
2649
+ * Post a new comment
2650
+ */
2651
+ postComment(payload: PostCommentPayload): Promise<CommentItem>;
2652
+ /**
2653
+ * Post a reply to a comment
2654
+ * Uses same endpoint as postComment but with reply_to_comment_id
2655
+ */
2656
+ postReply(payload: PostReplyPayload): Promise<ReplyItem>;
2657
+ /**
2658
+ * Edit a comment or reply
2659
+ */
2660
+ editComment(payload: EditCommentPayload): Promise<CommentItem | ReplyItem>;
2661
+ /**
2662
+ * Delete a comment or reply
2663
+ * DELETE /reels/:id/comments/:commentId
2664
+ */
2665
+ deleteComment(payload: DeleteCommentPayload): Promise<void>;
2666
+ /**
2667
+ * Like a comment or reply
2668
+ */
2669
+ likeComment(id: string): Promise<void>;
2670
+ /**
2671
+ * Unlike a comment or reply
2672
+ */
2673
+ unlikeComment(id: string): Promise<void>;
2674
+ /**
2675
+ * Report a comment or reply
2676
+ */
2677
+ reportComment(payload: ReportCommentPayload): Promise<void>;
2678
+ /**
2679
+ * Transform API response to CommentListResponse
2680
+ */
2681
+ private transformCommentListResponse;
2682
+ /**
2683
+ * Transform API response to ReplyListResponse
2684
+ */
2685
+ private transformReplyListResponse;
2686
+ /**
2687
+ * Transform API response to CommentItem
2688
+ */
2689
+ private transformComment;
2690
+ /**
2691
+ * Transform API response to ReplyItem
2692
+ */
2693
+ private transformReply;
2694
+ /**
2695
+ * Extract author from various API formats
2696
+ */
2697
+ private extractAuthor;
2698
+ /**
2699
+ * Unwrap nested response data
2700
+ */
2701
+ private unwrapResponse;
2702
+ }
2703
+
2704
+ export { type AuthConfig, type AuthError, type BatchAnalyticsConfig, type BatchAnalyticsContext, type BatchAnalyticsDeviceType, type BatchAnalyticsEventData, type BatchAnalyticsEventTransformer, type BatchAnalyticsEventType, type BatchAnalyticsNetworkType, type BatchAnalyticsRequestBody, type BatchAnalyticsRequestEvent, type BrowserAdaptersConfig, BrowserPosterLoader, BrowserVideoLoader, type BrowserVideoLoaderConfig, CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerEvents, CircuitOpenError, type CircuitState, DEFAULT_REQUEST_CONFIG, DEFAULT_RETRY_CONFIG, type FieldMapConfig, type FullPresetAdapters, HttpClient, HttpError, LocalSessionStorageAdapter, LocalStorageAdapter, type LocalStorageConfig, MockAnalyticsAdapter, type MockAnalyticsAdapterOptions, MockCommentAdapter, 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, type RESTAnalyticsAdapterConfig, RESTCommentAdapter, type RESTCommentAdapterConfig, RESTDataAdapter, type RESTEndpointMap, RESTInteractionAdapter, type RESTPresetConfig, type RESTRequestConfig, RESTViewTrackingAdapter, type RESTViewTrackingAdapterConfig, type ResolvedTransforms, type RetryConfig, type TransformConfig, type ViewEventData, type ViewEventRequestBody, type ViewEventTransformer, type ViewTrackingConfig, WebNetworkAdapter, type WebNetworkConfig, createBrowserAdapters, createBrowserPosterLoader, createBrowserVideoLoader, createLocalStorageAdapter, createNoOpAnalyticsAdapter, createRESTAdapters, createSessionStorageAdapter, createTransforms, createWebNetworkAdapter, defaultFeedResponseTransform, defaultVideoItemTransform, getCircuitBreaker, getGlobalCircuitBreaker, resetAllCircuitBreakers };