@rixl/media-core 0.0.0-stage → 1.0.2

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 (170) hide show
  1. package/LICENSE.md +1 -0
  2. package/README.md +118 -2
  3. package/dist/api/analytics.d.ts +16 -0
  4. package/dist/api/analytics.js +227 -0
  5. package/dist/api/feed.js +52 -0
  6. package/dist/api/heatmap.d.ts +6 -0
  7. package/dist/api/heatmap.js +28 -0
  8. package/dist/api/hot-segments.d.ts +7 -0
  9. package/dist/api/hot-segments.js +20 -0
  10. package/dist/api/image.js +11 -0
  11. package/dist/api/rixl-client.d.ts +3 -0
  12. package/dist/api/rixl-client.js +14 -0
  13. package/dist/api/upload.d.ts +76 -0
  14. package/dist/api/upload.js +150 -0
  15. package/dist/api/video.js +11 -0
  16. package/dist/index.d.ts +82 -0
  17. package/dist/index.js +73 -0
  18. package/dist/lib/analytics-builders.d.ts +59 -0
  19. package/dist/lib/analytics-builders.js +88 -0
  20. package/dist/lib/analytics-initialize.d.ts +1 -0
  21. package/dist/lib/analytics-initialize.js +59 -0
  22. package/dist/lib/analytics-utils.d.ts +43 -0
  23. package/dist/lib/analytics-utils.js +85 -0
  24. package/dist/lib/analytics-wire.js +153 -0
  25. package/dist/lib/buffer-progress.d.ts +20 -0
  26. package/dist/lib/buffer-progress.js +37 -0
  27. package/dist/lib/caption-styles.d.ts +20 -0
  28. package/dist/lib/caption-styles.js +56 -0
  29. package/dist/lib/caption-utils.d.ts +80 -0
  30. package/dist/lib/caption-utils.js +111 -0
  31. package/dist/lib/chapter-utils.d.ts +102 -0
  32. package/dist/lib/chapter-utils.js +147 -0
  33. package/dist/lib/click-disambiguation.d.ts +44 -0
  34. package/dist/lib/click-disambiguation.js +80 -0
  35. package/dist/lib/click-outside.d.ts +68 -0
  36. package/dist/lib/click-outside.js +64 -0
  37. package/dist/lib/colors.d.ts +6 -0
  38. package/dist/lib/colors.js +16 -0
  39. package/dist/lib/content-analytics.d.ts +27 -0
  40. package/dist/lib/content-analytics.js +38 -0
  41. package/dist/lib/environment-effects.d.ts +89 -0
  42. package/dist/lib/environment-effects.js +139 -0
  43. package/dist/lib/feed-autoscroll.d.ts +10 -0
  44. package/dist/lib/feed-autoscroll.js +13 -0
  45. package/dist/lib/feed-container-height.d.ts +7 -0
  46. package/dist/lib/feed-container-height.js +11 -0
  47. package/dist/lib/feed-description-overflow.d.ts +1 -0
  48. package/dist/lib/feed-description-overflow.js +9 -0
  49. package/dist/lib/feed-dwell.d.ts +2 -0
  50. package/dist/lib/feed-dwell.js +8 -0
  51. package/dist/lib/feed-loop.d.ts +10 -0
  52. package/dist/lib/feed-loop.js +21 -0
  53. package/dist/lib/feed-scroll.d.ts +11 -0
  54. package/dist/lib/feed-scroll.js +19 -0
  55. package/dist/lib/feed-transforms.d.ts +5 -0
  56. package/dist/lib/feed-transforms.js +30 -0
  57. package/dist/lib/filter-undefined.js +11 -0
  58. package/dist/lib/fullscreen-utils.d.ts +1 -0
  59. package/dist/lib/fullscreen-utils.js +7 -0
  60. package/dist/lib/hls-buffer-flush.js +46 -0
  61. package/dist/lib/hls-error-recovery.d.ts +35 -0
  62. package/dist/lib/hls-error-recovery.js +75 -0
  63. package/dist/lib/hls-event-handlers.js +61 -0
  64. package/dist/lib/hls-loader.js +23 -0
  65. package/dist/lib/hls-setup.d.ts +72 -0
  66. package/dist/lib/hls-setup.js +144 -0
  67. package/dist/lib/hls-utils.d.ts +31 -0
  68. package/dist/lib/hls-utils.js +34 -0
  69. package/dist/lib/image-render.d.ts +29 -0
  70. package/dist/lib/image-render.js +63 -0
  71. package/dist/lib/keyboard-config.d.ts +66 -0
  72. package/dist/lib/keyboard-config.js +127 -0
  73. package/dist/lib/keyboard-utils.d.ts +4 -0
  74. package/dist/lib/keyboard-utils.js +11 -0
  75. package/dist/lib/lifecycle-effects.d.ts +67 -0
  76. package/dist/lib/lifecycle-effects.js +78 -0
  77. package/dist/lib/loading-debounce.d.ts +26 -0
  78. package/dist/lib/loading-debounce.js +38 -0
  79. package/dist/lib/numbers.d.ts +6 -0
  80. package/dist/lib/numbers.js +36 -0
  81. package/dist/lib/pip-utils.d.ts +16 -0
  82. package/dist/lib/pip-utils.js +32 -0
  83. package/dist/lib/playback-handlers.d.ts +33 -0
  84. package/dist/lib/playback-handlers.js +51 -0
  85. package/dist/lib/playback-slider.d.ts +23 -0
  86. package/dist/lib/playback-slider.js +24 -0
  87. package/dist/lib/player-labels.d.ts +23 -0
  88. package/dist/lib/player-labels.js +132 -0
  89. package/dist/lib/player-menu-labels.d.ts +80 -0
  90. package/dist/lib/player-menu-labels.js +829 -0
  91. package/dist/lib/player-ui-actions.d.ts +10 -0
  92. package/dist/lib/player-ui-actions.js +58 -0
  93. package/dist/lib/progress-fraction.d.ts +11 -0
  94. package/dist/lib/progress-fraction.js +20 -0
  95. package/dist/lib/progress-persist.d.ts +28 -0
  96. package/dist/lib/progress-persist.js +54 -0
  97. package/dist/lib/segment-analytics.d.ts +20 -0
  98. package/dist/lib/segment-analytics.js +29 -0
  99. package/dist/lib/subtitle-logic.d.ts +31 -0
  100. package/dist/lib/subtitle-logic.js +75 -0
  101. package/dist/lib/subtitle-utils.d.ts +16 -0
  102. package/dist/lib/subtitle-utils.js +37 -0
  103. package/dist/lib/time.d.ts +1 -0
  104. package/dist/lib/time.js +17 -0
  105. package/dist/lib/track-persistence.d.ts +4 -0
  106. package/dist/lib/track-persistence.js +35 -0
  107. package/dist/lib/video-preload-config.d.ts +34 -0
  108. package/dist/lib/video-preload-config.js +41 -0
  109. package/dist/lib/video-spinner.d.ts +25 -0
  110. package/dist/lib/video-spinner.js +35 -0
  111. package/dist/lib/video-stats.d.ts +28 -0
  112. package/dist/lib/video-stats.js +44 -0
  113. package/dist/lib/volume-utils.d.ts +59 -0
  114. package/dist/lib/volume-utils.js +55 -0
  115. package/dist/progressbar.d.ts +10 -0
  116. package/dist/progressbar.js +125 -0
  117. package/dist/store/analytics-context-store.d.ts +4 -0
  118. package/dist/store/analytics-context-store.js +13 -0
  119. package/dist/store/analytics-device-store.d.ts +8 -0
  120. package/dist/store/analytics-device-store.js +44 -0
  121. package/dist/store/analytics-session-store.d.ts +8 -0
  122. package/dist/store/analytics-session-store.js +40 -0
  123. package/dist/store/analytics-store.d.ts +30 -0
  124. package/dist/store/analytics-store.js +88 -0
  125. package/dist/store/controls-store.d.ts +16 -0
  126. package/dist/store/controls-store.js +53 -0
  127. package/dist/store/feed-cache-store.d.ts +24 -0
  128. package/dist/store/feed-cache-store.js +165 -0
  129. package/dist/store/feed-interaction-store.d.ts +17 -0
  130. package/dist/store/feed-interaction-store.js +32 -0
  131. package/dist/store/feed-store.d.ts +16 -0
  132. package/dist/store/feed-store.js +38 -0
  133. package/dist/store/generic-store.js +24 -0
  134. package/dist/store/image-store.d.ts +2 -0
  135. package/dist/store/image-store.js +8 -0
  136. package/dist/store/player-container-element-store.d.ts +10 -0
  137. package/dist/store/player-container-element-store.js +38 -0
  138. package/dist/store/player-global-actions.js +53 -0
  139. package/dist/store/player-media-element-store.d.ts +10 -0
  140. package/dist/store/player-media-element-store.js +38 -0
  141. package/dist/store/player-store-actions.js +197 -0
  142. package/dist/store/player-store.d.ts +94 -0
  143. package/dist/store/player-store.js +127 -0
  144. package/dist/store/player-track-actions.js +44 -0
  145. package/dist/store/postStore.d.ts +20 -0
  146. package/dist/store/postStore.js +112 -0
  147. package/dist/store/settings/constants.d.ts +15 -0
  148. package/dist/store/settings/constants.js +47 -0
  149. package/dist/store/settings/defaults.d.ts +2 -0
  150. package/dist/store/settings/defaults.js +24 -0
  151. package/dist/store/settings/storage.d.ts +3 -0
  152. package/dist/store/settings/storage.js +28 -0
  153. package/dist/store/settings/store.d.ts +32 -0
  154. package/dist/store/settings/store.js +59 -0
  155. package/dist/store/settings/types.d.ts +27 -0
  156. package/dist/store/settings/validation.d.ts +7 -0
  157. package/dist/store/settings/validation.js +55 -0
  158. package/dist/store/settings-menu-store.d.ts +19 -0
  159. package/dist/store/settings-menu-store.js +30 -0
  160. package/dist/store/video-store.d.ts +2 -0
  161. package/dist/store/video-store.js +8 -0
  162. package/dist/types/analytics.d.ts +113 -0
  163. package/dist/types/feed.d.ts +21 -0
  164. package/dist/types/file.d.ts +12 -0
  165. package/dist/types/heatmap.d.ts +5 -0
  166. package/dist/types/hot-segments.d.ts +9 -0
  167. package/dist/types/image.d.ts +9 -0
  168. package/dist/types/uuid.d.ts +1 -0
  169. package/dist/types/video.d.ts +22 -0
  170. package/package.json +41 -4
package/LICENSE.md ADDED
@@ -0,0 +1 @@
1
+ Use of this SDK is subject to our [Terms of Use](https://rixl.com/en/mediasdk-terms/).
package/README.md CHANGED
@@ -1,3 +1,119 @@
1
- # Temporary Holding Version
1
+ # RIXL Media SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The [RIXL Media SDK](https://rixl.com/en/docs/mediasdk) enables you to build custom media experiences with RIXL's core technology through a highly optimized TypeScript library.
4
+
5
+ The library provides a framework-agnostic foundation for media playback experiences: fetching, caching, player state stores, analytics, and pure helpers for progress, buffering, chapters, captions, and more. It powers the UI components in `@rixl/media-react`, `@rixl/media-vue`, `@rixl/media-svelte`, `@rixl/media-angular`, `@rixl/media-astro`, and `@rixl/media-lit`, and can be used directly from any JavaScript/TypeScript project (vanilla, custom web components, Node.js).
6
+
7
+ Use of this SDK is subject to our [Terms of Use](https://rixl.com/en/mediasdk-terms/).
8
+
9
+ ## Installation
10
+
11
+ For headless usage, install the core package. For UI components, install only your framework package (`@rixl/media-react`, `@rixl/media-vue`, `@rixl/media-svelte`, `@rixl/media-angular`, `@rixl/media-astro`, or `@rixl/media-lit`); it installs core automatically and exposes the same APIs through its `/core` subpath.
12
+
13
+ ```bash
14
+ npm install @rixl/media-core
15
+ # or
16
+ pnpm add @rixl/media-core
17
+ # or
18
+ bun add @rixl/media-core
19
+ ```
20
+
21
+ ## Media Fetching
22
+
23
+ | Function | Description |
24
+ | --------------------------------------- | ----------------------------------- |
25
+ | `getImage(imageId)` | Fetches image data by ID |
26
+ | `getVideo(videoId)` | Fetches video data by ID |
27
+ | `getFeedPost(feedId, postId)` | Fetches a single post from a feed |
28
+ | `getFeedPosts(feedId, offset?, limit?)` | Fetches paginated posts from a feed |
29
+
30
+ ### Usage:
31
+
32
+ ```typescript
33
+ // Fetch image data
34
+ const imageData = await getImage("image-id");
35
+
36
+ // Fetch video data
37
+ const videoData = await getVideo("video-id");
38
+
39
+ // Fetch a single post from a feed
40
+ const post = await getFeedPost("feed-id", "post-id");
41
+
42
+ // Fetch paginated posts from a feed
43
+ const posts = await getFeedPosts("feed-id", 0, 10);
44
+ ```
45
+
46
+ ### TypeScript Types
47
+
48
+ The library exports the following TypeScript types:
49
+
50
+ | Type | Description |
51
+ | ------------------- | ---------------------------------------------- |
52
+ | `ImageData` | Image metadata and URLs |
53
+ | `VideoData` | Video metadata, URLs, and playback information |
54
+ | `FeedPost` | Feed post content and metadata |
55
+ | `FeedPostsResponse` | Paginated response for feed posts |
56
+ | `FileStatus` | Status of a media file |
57
+ | `FileData` | Generic file metadata |
58
+
59
+ ## Player State
60
+
61
+ Reactive [nanostores](https://github.com/nanostores/nanostores) for media playback state. All stores are framework-agnostic; consume them in React via `@nanostores/react`, in Vue via `@nanostores/vue`, etc.
62
+
63
+ | Store | Description |
64
+ | ----------------------------- | ----------------------------------------------------------------------- |
65
+ | `playerStateStore` | Per-player playback state (mute, volume, rate, tracks, fullscreen, PiP) |
66
+ | `controlsStore` | UI controls visibility/state |
67
+ | `settingsStore` | User settings (caption styling, language preferences, playback speed) |
68
+ | `postStore` | Active post/content metadata |
69
+ | `analyticsStore` | Watched-segments tracking for analytics payloads |
70
+ | `feedStore` | Feed interaction state (transitions, initialization) |
71
+ | `feedCacheStore` | Cached feed pages |
72
+ | `playerContainerElementStore` | Reference to the active player container element |
73
+ | `playerMediaElementStore` | Reference to the active media element |
74
+
75
+ ## Progress Bar & Player UI Actions
76
+
77
+ | Function | Description |
78
+ | ----------------------------------------------- | --------------------------------------------------- |
79
+ | `initProgressBar({video, ...})` | Wires drag/seek interactions to a video element |
80
+ | `showPlayerControls()` / `hidePlayerControls()` | Toggle controls visibility |
81
+ | `togglePlayerFullscreen()` | Enter/exit fullscreen for the active player |
82
+ | `togglePlayerPictureInPicture()` | Enter/exit picture-in-picture for the active player |
83
+
84
+ ## Pure Helpers
85
+
86
+ Selected utilities (fully tree-shakeable):
87
+
88
+ - **Progress / buffer:** `computeProgressFraction`, `computeBufferRanges`, `computeBufferedAhead`
89
+ - **Chapters & heatmap:** `calculateChaptersWithEnd`, `calculateHeatmapSlices`, `sliceHeatmapData`, `validateChapters`
90
+ - **Subtitles & captions:** `extractSubtitleTracks`, `processCues`, `findNextCue`, `calculateFontSize`, `constrainPercentagePosition`
91
+ - **Volume / fullscreen / PiP:** `calculateVolumeToSet`, `shouldBeMuted`, `isEffectivelyMuted`, `isDocumentFullscreen`, `togglePictureInPicture`
92
+ - **Persistence:** `getPersistedProgress`, `persistProgress`, `getPersistedAudioTrack`, `getPersistedSubtitleTrack`, `loadSettings`, `saveSettings`
93
+ - **HLS / quality:** `sortLevelsByQuality`, `generateLevelName`, `getQualityLabel`, `isHlsStream`
94
+ - **Analytics:** `postContentEvent`, `buildVideoAnalyticsPayload`, `buildContentViewEvent`, `calculateWatchDuration`
95
+ - **Formatting:** `formatTime`, `formatNumber`, `formatNumberSm`, `clamp`, `hexToRGB`, `hexToRGBAString`
96
+
97
+ ## API Reference
98
+
99
+ All data is fetched from `https://api.rixl.com`.
100
+
101
+ ### REST Endpoints
102
+
103
+ | Endpoint | Description |
104
+ | --------------------------------------------------------------- | --------------------------------------------- |
105
+ | `GET /images/{imageId}` | Fetch image metadata and URLs |
106
+ | `GET /videos/{videoId}` | Fetch video metadata and playback information |
107
+ | `GET /feeds/{feedId}/posts?offset=...&limit=...` | Fetch paginated feed posts |
108
+ | `GET /feeds/{feedId}/posts/{postId}` | Fetch a specific feed post |
109
+ | `GET /feeds/{feedId}/creators/{creatorId}?offset=...&limit=...` | Fetch posts by a specific creator |
110
+
111
+ ## Documentation
112
+
113
+ For complete documentation, tutorials, and examples, visit our **[SDK Documentation](https://docs.rixl.com/en/sdk/getting-started)**.
114
+
115
+ ## Need Help?
116
+
117
+ - [Developer Support](https://support.rixl.com)
118
+ - [Developer Forum](https://forum.rixl.com)
119
+ - [Premier Developer Support](https://rixl.com/pricing) (priority support)
@@ -0,0 +1,16 @@
1
+ import { BuildEngagementParams, BuildErrorParams, BuildInteractionParams } from "../lib/analytics-builders.js";
2
+ import { AnalyticsEvent } from "../types/analytics.js";
3
+ export declare function postAnalytics(events: AnalyticsEvent[]): Promise<void>;
4
+ export declare function postEngagement(params: BuildEngagementParams): void;
5
+ export declare function postInteraction(params: BuildInteractionParams): void;
6
+ export declare function postError(params: BuildErrorParams): void;
7
+ /**
8
+ * Resolves once the events outstanding at call time have been sent, rather than
9
+ * once a flush has merely been started. Two paths used to return early: a call
10
+ * during an active flush reported a drain that had not happened, and a call
11
+ * while the only outstanding batch was already in flight (so the queue looked
12
+ * empty) returned before that request finished. Delegating to flushQueue makes
13
+ * it wait for whichever drain covers the outstanding work — starting one now,
14
+ * bypassing the timer, when nothing is running yet.
15
+ */
16
+ export declare function flushAnalytics(): Promise<void>;
@@ -0,0 +1,227 @@
1
+ import { analyticsContextStore } from "../store/analytics-context-store.js";
2
+ import { getOrCreateDeviceId } from "../store/analytics-device-store.js";
3
+ import { getOrCreateSessionId, hasSessionStarted, markSessionStarted } from "../store/analytics-session-store.js";
4
+ import { buildEngagementEvent, buildErrorEvent, buildInteractionEvent, buildSessionStartEvent } from "../lib/analytics-builders.js";
5
+ import { filterUndefined } from "../lib/filter-undefined.js";
6
+ import { toWireEvent } from "../lib/analytics-wire.js";
7
+ import { analyticsV1EventsServiceTrackEvents } from "@rixl/sdk";
8
+
9
+ // src/api/analytics.ts
10
+ const MAX_RETRIES = 3;
11
+ /**
12
+ * Statuses worth sending again. 5xx and these are transient by definition;
13
+ * every other 4xx means the endpoint understood the payload and rejected it,
14
+ * so repeating it only wastes attempts and holds up the queue behind a batch
15
+ * that can never drain.
16
+ */
17
+ const SERVER_ERROR_MIN_STATUS = 500;
18
+ const RETRYABLE_CLIENT_STATUSES = /* @__PURE__ */ new Set([
19
+ 408,
20
+ 425,
21
+ 429
22
+ ]);
23
+ function isRetryableStatus(status) {
24
+ return status >= SERVER_ERROR_MIN_STATUS || RETRYABLE_CLIENT_STATUSES.has(status);
25
+ }
26
+ function deliveryError(message, retryable) {
27
+ return Object.assign(new Error(message), { retryable });
28
+ }
29
+ /**
30
+ * Anything that is not a classified delivery failure — a thrown network error,
31
+ * an aborted request — has no response behind it and is assumed transient, so
32
+ * events are kept rather than discarded on an unknown fault.
33
+ */
34
+ function isRetryableError(error) {
35
+ return error instanceof Error && "retryable" in error ? error.retryable : true;
36
+ }
37
+ let queue = [];
38
+ let isFlushing = false;
39
+ let flushTimer = null;
40
+ /**
41
+ * The drain in progress, or null when none is running. flushAnalytics awaits
42
+ * it so an explicit flush resolves only once the outstanding request finishes,
43
+ * not the moment the queue looks empty because its batch is already in flight.
44
+ */
45
+ let flushing = null;
46
+ /**
47
+ * Callers waiting on the events currently queued but not yet handed to a
48
+ * request. One deferred covers the whole queue: a batch is taken and its
49
+ * waiters settled together, and anything enqueued afterwards starts a new one.
50
+ */
51
+ let pending = null;
52
+ function createDeferred() {
53
+ let resolve = () => {};
54
+ return {
55
+ promise: new Promise((res) => {
56
+ resolve = res;
57
+ }),
58
+ resolve
59
+ };
60
+ }
61
+ /** Settles and clears the current deferred, if any. */
62
+ function settlePending() {
63
+ const waiting = pending;
64
+ pending = null;
65
+ waiting?.resolve();
66
+ }
67
+ /**
68
+ * No timer is armed while a flush is running. The running flush drains what
69
+ * arrives during its own request, so arming one here would let it fire mid
70
+ * request, find `isFlushing` set, and return having abandoned its waiters.
71
+ */
72
+ function scheduleFlush() {
73
+ pending ??= createDeferred();
74
+ if (flushTimer === null && !isFlushing) flushTimer = setTimeout(() => {
75
+ flushTimer = null;
76
+ flushQueue();
77
+ }, 0);
78
+ return pending.promise;
79
+ }
80
+ /**
81
+ * Starts a drain unless one is already running, and hands back a promise that
82
+ * settles when the whole cycle finishes — the request in flight included, not
83
+ * just the events still waiting in the queue. Nudge callers `void` it; only
84
+ * flushAnalytics awaits it, so an explicit flush cannot resolve early while a
85
+ * request it should cover is still outstanding.
86
+ */
87
+ function flushQueue() {
88
+ if (isFlushing) return flushing ?? Promise.resolve();
89
+ if (queue.length === 0) return Promise.resolve();
90
+ const run = drainQueue().finally(() => {
91
+ if (flushing === run) flushing = null;
92
+ });
93
+ flushing = run;
94
+ return run;
95
+ }
96
+ async function drainQueue() {
97
+ isFlushing = true;
98
+ if (flushTimer) {
99
+ clearTimeout(flushTimer);
100
+ flushTimer = null;
101
+ }
102
+ try {
103
+ while (queue.length > 0) {
104
+ const batch = queue;
105
+ queue = [];
106
+ const waiters = pending;
107
+ pending = null;
108
+ try {
109
+ await performFlush(batch);
110
+ } catch (error) {
111
+ handleFailedBatch(batch, error);
112
+ waiters?.resolve();
113
+ settlePending();
114
+ return;
115
+ }
116
+ waiters?.resolve();
117
+ }
118
+ } finally {
119
+ isFlushing = false;
120
+ }
121
+ }
122
+ /**
123
+ * Transient failures keep their events for a later flush; a rejected payload is
124
+ * dropped, since requeueing it would block everything behind a batch the
125
+ * endpoint will refuse again.
126
+ */
127
+ function handleFailedBatch(batch, error) {
128
+ console.error("[Analytics] Failed to flush queue:", error);
129
+ if (isRetryableError(error)) queue.unshift(...batch);
130
+ }
131
+ async function performFlush(events) {
132
+ const options = {
133
+ context: analyticsContextStore.get(),
134
+ deviceId: getOrCreateDeviceId(),
135
+ sessionId: getOrCreateSessionId()
136
+ };
137
+ const carriesSessionStart = !hasSessionStarted();
138
+ await sendWithRetry(buildTrackRequest(buildPayloadEvents(events, options.context, carriesSessionStart), options));
139
+ if (carriesSessionStart) markSessionStarted();
140
+ }
141
+ function buildPayloadEvents(events, context, withSessionStart) {
142
+ const payloadEvents = [];
143
+ if (withSessionStart) payloadEvents.push(buildSessionStartEventFromContext(context));
144
+ payloadEvents.push(...events);
145
+ return payloadEvents;
146
+ }
147
+ function buildTrackRequest(events, options) {
148
+ const metadata = filterUndefined({
149
+ user_id: options.context.user_id,
150
+ country: options.context.country,
151
+ city: options.context.city,
152
+ region: options.context.region,
153
+ device: options.context.device,
154
+ os: options.context.os,
155
+ os_version: options.context.os_version,
156
+ language: options.context.language,
157
+ browser: options.context.browser
158
+ });
159
+ return {
160
+ events: events.map((event) => toWireEvent(event, options)),
161
+ ...metadata
162
+ };
163
+ }
164
+ async function sendWithRetry(request) {
165
+ let lastError;
166
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) try {
167
+ await sendEvents(request);
168
+ return;
169
+ } catch (error) {
170
+ lastError = error;
171
+ if (!isRetryableError(error)) break;
172
+ }
173
+ throw lastError;
174
+ }
175
+ async function sendEvents(request) {
176
+ const { response } = await analyticsV1EventsServiceTrackEvents({ body: request });
177
+ if (response && !response.ok) {
178
+ console.error(`[Analytics] Failed to post events: ${response.status} ${response.statusText}`);
179
+ throw deliveryError(`Analytics request failed with ${response.status}`, isRetryableStatus(response.status));
180
+ }
181
+ }
182
+ function buildSessionStartEventFromContext(context) {
183
+ return buildSessionStartEvent({
184
+ browser: context.browser,
185
+ language: context.language,
186
+ screenResolution: context.screen_resolution,
187
+ platform: context.platform,
188
+ tgPlatform: context.tg_platform,
189
+ tgVersion: context.tg_version,
190
+ timezone: context.timezone,
191
+ timezoneOffset: context.timezone_offset,
192
+ colorDepth: context.color_depth,
193
+ touchSupport: context.touch_support,
194
+ country: context.country,
195
+ userId: context.user_id,
196
+ utmSource: context.utm_source,
197
+ utmCampaign: context.utm_campaign
198
+ });
199
+ }
200
+ async function postAnalytics(events) {
201
+ if (events.length === 0) return;
202
+ queue.push(...events);
203
+ return scheduleFlush();
204
+ }
205
+ function postEngagement(params) {
206
+ postAnalytics([buildEngagementEvent(params)]);
207
+ }
208
+ function postInteraction(params) {
209
+ postAnalytics([buildInteractionEvent(params)]);
210
+ }
211
+ function postError(params) {
212
+ postAnalytics([buildErrorEvent(params)]);
213
+ }
214
+ /**
215
+ * Resolves once the events outstanding at call time have been sent, rather than
216
+ * once a flush has merely been started. Two paths used to return early: a call
217
+ * during an active flush reported a drain that had not happened, and a call
218
+ * while the only outstanding batch was already in flight (so the queue looked
219
+ * empty) returned before that request finished. Delegating to flushQueue makes
220
+ * it wait for whichever drain covers the outstanding work — starting one now,
221
+ * bypassing the timer, when nothing is running yet.
222
+ */
223
+ async function flushAnalytics() {
224
+ return flushQueue();
225
+ }
226
+
227
+ export { flushAnalytics, postAnalytics, postEngagement, postError, postInteraction };
@@ -0,0 +1,52 @@
1
+ import { postsV1PostServiceGetPost3, postsV1PostServiceListPosts3 } from "@rixl/sdk";
2
+
3
+ // src/api/feed.ts
4
+ function toType(type) {
5
+ if (type === "MEDIA_TYPE_IMAGE") return "image";
6
+ if (type === "MEDIA_TYPE_VIDEO") return "video";
7
+ return "image";
8
+ }
9
+ function toFeedPost(post) {
10
+ const withMedia = post;
11
+ return {
12
+ id: post.id ?? "",
13
+ feedId: post.feed_id ?? "",
14
+ creatorId: post.creator_id ?? "",
15
+ type: toType(post.type),
16
+ description: post.description ?? "",
17
+ createdAt: post.created_at ?? "",
18
+ image: withMedia.image,
19
+ video: withMedia.video
20
+ };
21
+ }
22
+ function toFeedPostsResponse(data, offset, limit) {
23
+ const rawTotal = data.total ?? 0;
24
+ return {
25
+ posts: (data.posts ?? []).map(toFeedPost),
26
+ total: typeof rawTotal === "number" ? rawTotal : Number(rawTotal),
27
+ offset: data.offset ?? offset,
28
+ limit: data.limit ?? limit
29
+ };
30
+ }
31
+ const fetchFeedPosts = async (feedId, offset = 0, limit = 10) => {
32
+ const { data, error, response } = await postsV1PostServiceListPosts3({
33
+ path: { feed_id: feedId },
34
+ query: {
35
+ "pagination.offset": offset,
36
+ "pagination.limit": limit
37
+ }
38
+ });
39
+ if (error || !data) throw new Error(`HTTP error! status: ${response?.status ?? 500}`);
40
+ return toFeedPostsResponse(data, offset, limit);
41
+ };
42
+ const fetchFeedPost = async (feedId, postId) => {
43
+ const { data, error, response } = await postsV1PostServiceGetPost3({ path: {
44
+ feed_id: feedId,
45
+ post_id: postId
46
+ } });
47
+ if (error) throw new Error(`HTTP error! status: ${response?.status ?? 500}`);
48
+ if (!data?.post) throw new Error(`HTTP error! status: 404`);
49
+ return toFeedPost(data.post);
50
+ };
51
+
52
+ export { fetchFeedPost, fetchFeedPosts };
@@ -0,0 +1,6 @@
1
+ import { VideoHeatmap } from "../types/heatmap.js";
2
+ export interface GetVideoHeatmapOptions {
3
+ videoId: string;
4
+ buckets?: number;
5
+ }
6
+ export declare function getVideoHeatmap({ videoId, buckets }: GetVideoHeatmapOptions): Promise<VideoHeatmap | null>;
@@ -0,0 +1,28 @@
1
+ import { analyticsV1HeatmapServiceGetVideoHeatmap } from "@rixl/sdk";
2
+
3
+ // src/api/heatmap.ts
4
+ function toNumber(value) {
5
+ if (value === void 0) return 0;
6
+ return typeof value === "number" ? value : Number(value);
7
+ }
8
+ function toDomainHeatmap(data) {
9
+ return {
10
+ video_id: data.video_id ?? "",
11
+ total_duration_ms: toNumber(data.total_duration_ms),
12
+ data: data.data ?? []
13
+ };
14
+ }
15
+ async function getVideoHeatmap({ videoId, buckets = 101 }) {
16
+ try {
17
+ const { data, error } = await analyticsV1HeatmapServiceGetVideoHeatmap({
18
+ path: { video_id: videoId },
19
+ query: { buckets }
20
+ });
21
+ if (error || !data) return null;
22
+ return toDomainHeatmap(data);
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+
28
+ export { getVideoHeatmap };
@@ -0,0 +1,7 @@
1
+ import { HotSegment } from "../types/hot-segments.js";
2
+ export interface GetHotSegmentsOptions {
3
+ videoId: string;
4
+ startDate?: string;
5
+ endDate?: string;
6
+ }
7
+ export declare function getHotSegments({ videoId, startDate, endDate }: GetHotSegmentsOptions): Promise<HotSegment[]>;
@@ -0,0 +1,20 @@
1
+ import { analyticsV1HeatmapServiceGetHotSegments } from "@rixl/sdk";
2
+
3
+ // src/api/hot-segments.ts
4
+ async function getHotSegments({ videoId, startDate, endDate }) {
5
+ try {
6
+ const query = {};
7
+ if (startDate) query["range.start"] = startDate;
8
+ if (endDate) query["range.end"] = endDate;
9
+ const { data, error } = await analyticsV1HeatmapServiceGetHotSegments({
10
+ path: { video_id: videoId },
11
+ query: Object.keys(query).length > 0 ? query : void 0
12
+ });
13
+ if (error) return [];
14
+ return data?.segments ?? [];
15
+ } catch {
16
+ return [];
17
+ }
18
+ }
19
+
20
+ export { getHotSegments };
@@ -0,0 +1,11 @@
1
+ import { imagesV1ImageServiceGetImage } from "@rixl/sdk";
2
+
3
+ // src/api/image.ts
4
+ const fetchImage = async (imageId) => {
5
+ const { data, error, response } = await imagesV1ImageServiceGetImage({ path: { image_id: imageId } });
6
+ if (error) throw new Error(`HTTP error! status: ${response?.status ?? 500}`);
7
+ if (!data?.image) throw new Error(`HTTP error! status: 404`);
8
+ return data.image;
9
+ };
10
+
11
+ export { fetchImage };
@@ -0,0 +1,3 @@
1
+ import { AuthConfig, ConnectConfig, client } from "@rixl/sdk";
2
+ declare function connect$1(config: ConnectConfig): Promise<string | undefined>;
3
+ export { type AuthConfig, type ConnectConfig, connect$1 as connect };
@@ -0,0 +1,14 @@
1
+ import { initializeAnalyticsContext } from "../lib/analytics-initialize.js";
2
+ import { client, connect } from "@rixl/sdk";
3
+
4
+ // src/api/rixl-client.ts
5
+ function getRixlClient() {
6
+ return client;
7
+ }
8
+ async function connect$1(config) {
9
+ const result = await connect(config);
10
+ initializeAnalyticsContext();
11
+ return result;
12
+ }
13
+
14
+ export { connect$1 as connect, getRixlClient };
@@ -0,0 +1,76 @@
1
+ export interface CreateImageUploadOptions {
2
+ projectId: string;
3
+ orgId?: string;
4
+ name: string;
5
+ }
6
+ export interface ImageUploadResult {
7
+ imageId: string;
8
+ uploadUrl: string;
9
+ }
10
+ export declare function createImageUpload(options: CreateImageUploadOptions): Promise<ImageUploadResult>;
11
+ export declare function uploadImage(file: Blob | Uint8Array, options: CreateImageUploadOptions): Promise<ImageUploadResult>;
12
+ export interface CreateVideoUploadOptions {
13
+ projectId: string;
14
+ orgId?: string;
15
+ name: string;
16
+ videoQuality?: string;
17
+ }
18
+ export interface VideoUploadResult {
19
+ videoId: string;
20
+ posterId: string;
21
+ videoUploadUrl: string;
22
+ posterUploadUrl: string;
23
+ }
24
+ export declare function createVideoUpload(options: CreateVideoUploadOptions): Promise<VideoUploadResult>;
25
+ export declare function uploadVideo(videoFile: Blob | Uint8Array, posterFile: Blob | Uint8Array, options: CreateVideoUploadOptions): Promise<VideoUploadResult>;
26
+ export interface AudioTrackUploadItem {
27
+ languageCode: string;
28
+ label?: string;
29
+ fileName: string;
30
+ format: string;
31
+ size?: number;
32
+ }
33
+ export interface AudioTrackUploadTarget {
34
+ id: string;
35
+ languageCode?: string;
36
+ uploadUrl?: string;
37
+ }
38
+ export interface AudioTrackUploadResult {
39
+ targets: AudioTrackUploadTarget[];
40
+ }
41
+ export declare function createAudioTrackUpload(projectId: string, videoId: string, items: AudioTrackUploadItem[]): Promise<AudioTrackUploadResult>;
42
+ export interface SubtitleUploadItem {
43
+ languageCode: string;
44
+ label?: string;
45
+ fileName: string;
46
+ format: string;
47
+ size?: number;
48
+ }
49
+ export interface SubtitleUploadTarget {
50
+ id: string;
51
+ languageCode?: string;
52
+ uploadUrl?: string;
53
+ }
54
+ export interface SubtitleUploadResult {
55
+ targets: SubtitleUploadTarget[];
56
+ }
57
+ export declare function createSubtitleUpload(projectId: string, videoId: string, items: SubtitleUploadItem[]): Promise<SubtitleUploadResult>;
58
+ export interface CreatePostUploadOptions {
59
+ projectId: string;
60
+ feedId: string;
61
+ orgId?: string;
62
+ creatorId: string;
63
+ description?: string;
64
+ contentType: string;
65
+ fileName: string;
66
+ videoQuality?: string;
67
+ }
68
+ export interface PostUploadResult {
69
+ postId: string;
70
+ contentId: string;
71
+ posterId: string;
72
+ contentUploadUrl: string;
73
+ posterUploadUrl: string;
74
+ }
75
+ export declare function uploadPost(contentFile: Blob | Uint8Array, posterFile: Blob | Uint8Array, options: CreatePostUploadOptions): Promise<PostUploadResult>;
76
+ export declare function createPostUpload(options: CreatePostUploadOptions): Promise<PostUploadResult>;