saltfish 0.2.41 → 0.2.45

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.
package/README.md CHANGED
@@ -1,409 +1,646 @@
1
1
  # Saltfish
2
2
 
3
- An interactive video-guided tour system for web applications. Create engaging onboarding experiences and product walkthroughs with synchronized video playback and interactive overlays.
3
+ Interactive video-guided tours for web applications. Create engaging onboarding experiences with synchronized video playback, interactive overlays, and smart triggering.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/saltfish.svg)](https://www.npmjs.com/package/saltfish)
6
6
  [![License](https://img.shields.io/npm/l/saltfish.svg)](https://github.com/Saltfish-AB/playlist-player/blob/main/LICENSE)
7
7
 
8
8
  ## Features
9
9
 
10
- - 🎥 **Video-Guided Tours** - Synchronize video content with interactive UI elements
11
- - 🎯 **Interactive Overlays** - Highlight and interact with elements during video playback
12
- - 📱 **Responsive Design** - Automatically adapts to different screen sizes
13
- - 🎨 **Customizable UI** - Shadow DOM encapsulation prevents style conflicts
14
- - 📊 **Built-in Analytics** - Track user engagement and progress
10
+ - 🎥 **Video-Guided Tours** - Synchronized video content with interactive UI overlays
11
+ - 🎯 **Smart Triggers** - Automatic playlist triggering based on URL, user behavior, and custom conditions
12
+ - 📱 **Responsive** - Automatically adapts to different screen sizes and devices
13
+ - 🎨 **Shadow DOM** - Complete style isolation prevents CSS conflicts
14
+ - 📊 **Analytics** - Built-in tracking for user engagement and completion rates
15
15
  - 🔄 **State Persistence** - Resume tours where users left off
16
- - 🌐 **Multi-language Support** - Built-in i18n capabilities
16
+ - 🌐 **Multi-language** - User-specific language preferences
17
17
  - ⚡ **Lightweight** - Minimal bundle size with efficient loading
18
18
 
19
19
  ## Installation
20
20
 
21
+ ### CDN (Recommended)
22
+
23
+ ```html
24
+ <script src="https://storage.saltfish.ai/player/player.js"></script>
25
+ ```
26
+
21
27
  ### NPM
22
28
 
23
29
  ```bash
24
30
  npm install saltfish
25
31
  ```
26
32
 
33
+ ## Quick Start
34
+
35
+ ### Basic Setup (CDN)
36
+
37
+ ```html
38
+ <!DOCTYPE html>
39
+ <html>
40
+ <head>
41
+ <title>My App</title>
42
+ </head>
43
+ <body>
44
+ <h1>Welcome to My App</h1>
45
+
46
+ <!-- Load Saltfish -->
47
+ <script src="https://storage.saltfish.ai/player/player.js"></script>
48
+ <script>
49
+ // 1. Initialize with your token
50
+ saltfish.init({
51
+ token: 'YOUR_TOKEN_HERE',
52
+ enableAnalytics: true
53
+ });
54
+
55
+ // 2. Identify the user
56
+ saltfish.identify('user-123', {
57
+ email: 'user@example.com',
58
+ name: 'John Doe',
59
+ language: 'en'
60
+ });
61
+
62
+ // 3. (Optional) Manually start a playlist
63
+ saltfish.startPlaylist('playlist-id');
64
+ </script>
65
+ </body>
66
+ </html>
67
+ ```
68
+
69
+ ### NPM/ES Modules
70
+
27
71
  ```javascript
28
- import { SaltfishPlayer } from 'saltfish';
72
+ import saltfish from 'saltfish';
29
73
 
30
- const player = new SaltfishPlayer({
31
- token: 'your-token-here',
32
- containerId: 'player-container',
33
- apiBaseUrl: 'https://api.saltfish.ai' // optional
74
+ // Initialize
75
+ saltfish.init({
76
+ token: 'YOUR_TOKEN_HERE',
77
+ enableAnalytics: true
34
78
  });
35
79
 
36
- player.loadPlaylist('playlist-id');
80
+ // Identify user
81
+ saltfish.identify('user-123', {
82
+ email: 'user@example.com',
83
+ language: 'en'
84
+ });
85
+
86
+ // Start playlist
87
+ saltfish.startPlaylist('playlist-id');
37
88
  ```
38
89
 
39
- ### CDN
90
+ ## API Reference
40
91
 
41
- ```html
42
- <!-- Include the script -->
43
- <script src="https://storage.saltfish.ai/player/player.js"></script>
92
+ ### `saltfish.init(config)`
44
93
 
45
- <!-- Create a container -->
46
- <div id="player-container"></div>
94
+ Initialize the Saltfish player. Must be called before any other methods.
47
95
 
48
- <script>
49
- const player = new SaltfishPlayer({
50
- token: 'your-token-here',
51
- containerId: 'player-container'
52
- });
96
+ **Parameters:**
97
+ - `config` (Object or String):
98
+ - If string: Your API token
99
+ - If object:
100
+ - `token` (String, required): Your API token from Saltfish dashboard
101
+ - `enableAnalytics` (Boolean, optional): Enable/disable analytics tracking (default: `true`)
53
102
 
54
- player.loadPlaylist('playlist-id');
55
- </script>
103
+ **Returns:** `Promise<void>` (can be called without await)
104
+
105
+ **Examples:**
106
+
107
+ ```javascript
108
+ // Simple initialization with token only
109
+ saltfish.init('YOUR_TOKEN_HERE');
110
+
111
+ // With configuration object
112
+ saltfish.init({
113
+ token: 'YOUR_TOKEN_HERE',
114
+ enableAnalytics: false // Disable analytics for development
115
+ });
116
+
117
+ // Using async/await
118
+ await saltfish.init('YOUR_TOKEN_HERE');
119
+
120
+ // Using events
121
+ saltfish.on('initialized', () => {
122
+ console.log('Saltfish ready!');
123
+ });
124
+ saltfish.init('YOUR_TOKEN_HERE');
56
125
  ```
57
126
 
58
- ## Quick Start
127
+ ### `saltfish.identify(userId, attributes)`
59
128
 
60
- ### Basic Usage
129
+ Identify the current user. This enables playlist triggers, progress tracking, and personalized experiences.
130
+
131
+ **Parameters:**
132
+ - `userId` (String, required): Unique identifier for the user
133
+ - `attributes` (Object, optional):
134
+ - `email` (String): User's email address
135
+ - `name` (String): User's display name
136
+ - `language` (String): Language preference (`'en'`, `'sv'`, `'es'`, `'fr'`, `'de'`, or `'auto'` for browser detection)
137
+ - Any custom attributes you want to track
138
+
139
+ **Examples:**
61
140
 
62
141
  ```javascript
63
- // Initialize the player
64
- const player = new SaltfishPlayer({
65
- token: 'your-api-token',
66
- containerId: 'player-container',
67
- // Optional configuration
68
- onComplete: () => {
69
- console.log('Tour completed!');
70
- },
71
- onError: (error) => {
72
- console.error('Player error:', error);
73
- }
142
+ // Basic identification
143
+ saltfish.identify('user-123');
144
+
145
+ // With email and name
146
+ saltfish.identify('user-123', {
147
+ email: 'user@example.com',
148
+ name: 'John Doe'
74
149
  });
75
150
 
76
- // Load and play a playlist
77
- await player.loadPlaylist('playlist-id');
151
+ // With language preference
152
+ saltfish.identify('user-123', {
153
+ email: 'user@example.com',
154
+ language: 'sv' // Swedish
155
+ });
156
+
157
+ // Auto-detect language from browser
158
+ saltfish.identify('user-123', {
159
+ language: 'auto'
160
+ });
161
+
162
+ // With custom attributes
163
+ saltfish.identify('user-123', {
164
+ email: 'user@example.com',
165
+ plan: 'premium',
166
+ signupDate: '2024-01-15'
167
+ });
78
168
  ```
79
169
 
80
- ### With Custom Configuration
170
+ ### `saltfish.identifyAnonymous(attributes)`
171
+
172
+ Identify anonymous users without backend communication. Uses localStorage for progress tracking and trigger evaluation.
173
+
174
+ **Parameters:**
175
+ - `attributes` (Object, optional): Same as `identify()` method
176
+
177
+ **Examples:**
81
178
 
82
179
  ```javascript
83
- const player = new SaltfishPlayer({
84
- token: 'your-api-token',
85
- containerId: 'player-container',
86
- apiBaseUrl: 'https://api.saltfish.ai',
87
- autoplay: true,
88
- enableAnalytics: true,
89
- startMinimized: false,
90
- onStepChange: (stepIndex) => {
91
- console.log('Current step:', stepIndex);
92
- },
93
- onComplete: () => {
94
- console.log('Playlist completed!');
95
- },
96
- onError: (error) => {
97
- console.error('Error:', error);
98
- }
180
+ // Anonymous user with language preference
181
+ saltfish.identifyAnonymous({
182
+ language: 'en'
99
183
  });
100
184
 
101
- await player.loadPlaylist('playlist-id');
185
+ // Anonymous user with no data
186
+ saltfish.identifyAnonymous();
187
+
188
+ // With custom tracking data
189
+ saltfish.identifyAnonymous({
190
+ language: 'auto',
191
+ theme: 'dark'
192
+ });
102
193
  ```
103
194
 
104
- ## Configuration Options
195
+ ### `saltfish.startPlaylist(playlistId, options)`
105
196
 
106
- | Option | Type | Default | Description |
107
- |--------|------|---------|-------------|
108
- | `token` | `string` | **required** | Your Saltfish API token |
109
- | `containerId` | `string` | **required** | DOM element ID where player will be rendered |
110
- | `apiBaseUrl` | `string` | `'https://api.saltfish.ai'` | API endpoint URL |
111
- | `autoplay` | `boolean` | `true` | Start playing automatically |
112
- | `enableAnalytics` | `boolean` | `true` | Enable analytics tracking |
113
- | `startMinimized` | `boolean` | `false` | Start player in minimized state |
114
- | `onComplete` | `function` | `undefined` | Callback when playlist completes |
115
- | `onError` | `function` | `undefined` | Error handler callback |
116
- | `onStepChange` | `function` | `undefined` | Callback when video step changes |
197
+ Manually start a specific playlist. If a playlist is already running, it will be reset and the new one started.
117
198
 
118
- ## API Reference
199
+ **Parameters:**
200
+ - `playlistId` (String, required): The playlist ID from your Saltfish dashboard
201
+ - `options` (Object, optional):
202
+ - `startNodeId` (String): Start from a specific step instead of the beginning
203
+ - `position` (String): Player position (`'bottom-left'` or `'bottom-right'`)
119
204
 
120
- ### Methods
205
+ **Returns:** `Promise<void>` (can be called without await)
121
206
 
122
- #### `loadPlaylist(playlistId: string): Promise<void>`
123
- Load and initialize a playlist by ID.
207
+ **Examples:**
124
208
 
125
209
  ```javascript
126
- await player.loadPlaylist('playlist-123');
210
+ // Basic usage
211
+ saltfish.startPlaylist('playlist-123');
212
+
213
+ // Start from a specific step
214
+ saltfish.startPlaylist('playlist-123', {
215
+ startNodeId: 'step-5'
216
+ });
217
+
218
+ // With custom position
219
+ saltfish.startPlaylist('playlist-123', {
220
+ position: 'bottom-right'
221
+ });
222
+
223
+ // Using async/await
224
+ await saltfish.startPlaylist('playlist-123');
225
+
226
+ // Using events
227
+ saltfish.on('playlistStarted', (data) => {
228
+ console.log('Started:', data.playlist.id);
229
+ });
230
+ saltfish.startPlaylist('playlist-123');
127
231
  ```
128
232
 
129
- #### `play(): void`
130
- Start or resume video playback.
233
+ ### Event Listeners
234
+
235
+ Listen to player events using `on()` and remove listeners with `off()`.
236
+
237
+ #### `saltfish.on(eventName, handler)`
238
+
239
+ Register an event listener.
240
+
241
+ **Available Events:**
242
+ - `'initialized'` - Player initialized successfully
243
+ - `'playlistStarted'` - Playlist has started playing
244
+ - `'playlistEnded'` - Playlist completed
245
+ - `'playlistDismissed'` - User dismissed/closed the playlist
246
+ - `'stepStarted'` - New step/video started
247
+ - `'stepEnded'` - Step/video completed
248
+ - `'error'` - An error occurred
249
+
250
+ **Examples:**
131
251
 
132
252
  ```javascript
133
- player.play();
253
+ // Listen for playlist completion
254
+ saltfish.on('playlistEnded', (data) => {
255
+ console.log('Playlist completed:', data.playlist.id);
256
+ console.log('Completion rate:', data.completionRate);
257
+ });
258
+
259
+ // Listen for step changes
260
+ saltfish.on('stepStarted', (data) => {
261
+ console.log('Now on step:', data.step.id);
262
+ });
263
+
264
+ // Listen for errors
265
+ saltfish.on('error', (data) => {
266
+ console.error('Player error:', data.message);
267
+ });
268
+
269
+ // Listen for initialization
270
+ saltfish.on('initialized', () => {
271
+ console.log('Player ready!');
272
+ });
134
273
  ```
135
274
 
136
- #### `pause(): void`
137
- Pause video playback.
275
+ #### `saltfish.off(eventName, handler)`
276
+
277
+ Remove an event listener.
278
+
279
+ **Returns:** `boolean` - `true` if listener was removed, `false` if not found
280
+
281
+ **Example:**
138
282
 
139
283
  ```javascript
140
- player.pause();
284
+ function onPlaylistEnd(data) {
285
+ console.log('Playlist ended:', data.playlist.id);
286
+ }
287
+
288
+ // Add listener
289
+ saltfish.on('playlistEnded', onPlaylistEnd);
290
+
291
+ // Remove listener
292
+ saltfish.off('playlistEnded', onPlaylistEnd);
141
293
  ```
142
294
 
143
- #### `minimize(): void`
144
- Minimize the player to corner of screen.
295
+ ### Other Methods
296
+
297
+ #### `saltfish.getSessionId()`
298
+
299
+ Get the current session ID.
300
+
301
+ **Returns:** `String`
145
302
 
146
303
  ```javascript
147
- player.minimize();
304
+ const sessionId = saltfish.getSessionId();
305
+ console.log('Session ID:', sessionId);
148
306
  ```
149
307
 
150
- #### `maximize(): void`
151
- Restore player to full size.
308
+ #### `saltfish.getRunId()`
309
+
310
+ Get the current run ID (unique per playlist execution).
311
+
312
+ **Returns:** `String | null`
152
313
 
153
314
  ```javascript
154
- player.maximize();
315
+ const runId = saltfish.getRunId();
316
+ console.log('Run ID:', runId);
155
317
  ```
156
318
 
157
- #### `destroy(): void`
158
- Clean up and remove the player instance.
319
+ #### `saltfish.resetPlaylist()`
320
+
321
+ Reset the current playlist to its initial state.
159
322
 
160
323
  ```javascript
161
- player.destroy();
324
+ saltfish.resetPlaylist();
162
325
  ```
163
326
 
164
- #### `getState(): PlayerState`
165
- Get current player state.
327
+ #### `saltfish.destroy()`
328
+
329
+ Destroy the player instance and clean up all resources.
166
330
 
167
331
  ```javascript
168
- const state = player.getState();
169
- console.log(state.currentStepIndex, state.isPlaying);
332
+ saltfish.destroy();
170
333
  ```
171
334
 
172
- ### Events
335
+ #### `saltfish.version()`
336
+
337
+ Get the current Saltfish player version.
173
338
 
174
- Listen to player events using the event system:
339
+ **Returns:** `String`
175
340
 
176
341
  ```javascript
177
- player.on('stateChange', (newState) => {
178
- console.log('Player state changed:', newState);
179
- });
342
+ const version = saltfish.version();
343
+ console.log('Saltfish version:', version);
344
+ ```
180
345
 
181
- player.on('videoEnded', () => {
182
- console.log('Current video ended');
183
- });
346
+ ## Playlist Triggers
184
347
 
185
- player.on('error', (error) => {
186
- console.error('Player error:', error);
187
- });
188
- ```
348
+ Playlists can be automatically triggered based on conditions you configure in the Saltfish CMS. No code changes needed!
189
349
 
190
- ## Player States
350
+ ### Trigger Types
191
351
 
192
- The player implements a finite state machine with these states:
352
+ #### URL-Based Triggers
193
353
 
194
- - `idle` - Initial state
195
- - `loading` - Fetching playlist data
196
- - `playing` - Video is playing
197
- - `paused` - Video is paused
198
- - `minimized` - Player minimized to corner
199
- - `completed` - Playlist finished
200
- - `error` - Error occurred
354
+ Automatically show playlists when users visit specific URLs:
201
355
 
202
- ## Advanced Usage
356
+ ```javascript
357
+ // Just initialize and identify - triggers happen automatically
358
+ saltfish.init('YOUR_TOKEN');
359
+ saltfish.identify('user-123');
203
360
 
204
- ### Custom Styling
361
+ // Playlist will auto-trigger when user navigates to configured URLs
362
+ // e.g., "/dashboard", "/pricing", "/features/*"
363
+ ```
205
364
 
206
- The player uses Shadow DOM for style encapsulation. To customize appearance, use CSS custom properties:
365
+ **CMS Configuration Examples:**
366
+ - **Exact match**: `/dashboard` - Only triggers on exactly `/dashboard`
367
+ - **Contains**: `/dashboard` with "contains" mode - Triggers on `/dashboard`, `/dashboard/settings`, etc.
368
+ - **Wildcard**: `/products/*` - Triggers on any product page
369
+ - **Regex**: `/playlist-[0-9]+` - Triggers on `/playlist-1`, `/playlist-42`, etc.
207
370
 
208
- ```css
209
- #player-container {
210
- --saltfish-primary-color: #0066ff;
211
- --saltfish-background: #ffffff;
212
- --saltfish-text-color: #333333;
213
- --saltfish-border-radius: 8px;
214
- }
371
+ #### Element Click Triggers
372
+
373
+ Trigger playlists when users click specific elements:
374
+
375
+ ```html
376
+ <button id="help-button">Help</button>
377
+ <button class="support-btn">Support</button>
378
+ <button data-action="guide">Guide</button>
379
+
380
+ <script>
381
+ saltfish.init('YOUR_TOKEN');
382
+ saltfish.identify('user-123');
383
+
384
+ // Configure in CMS with CSS selector:
385
+ // #help-button, .support-btn, [data-action="guide"]
386
+ </script>
215
387
  ```
216
388
 
217
- ### Analytics Integration
389
+ #### Conditional Triggers
390
+
391
+ Configure complex trigger logic in the CMS:
218
392
 
219
- Track custom events alongside built-in analytics:
393
+ - **Once per user**: Show playlist only once, never again
394
+ - **Seen/Not seen**: Trigger based on whether user has seen other playlists
395
+ - **A/B Testing**: Show different playlists to different user segments
396
+ - **Combine conditions**: URL + click + seen/not seen with AND/OR logic
397
+
398
+ ### Manual Override
399
+
400
+ You can always manually start a playlist even if triggers are configured:
220
401
 
221
402
  ```javascript
222
- player.on('stateChange', (state) => {
223
- // Send to your analytics service
224
- analytics.track('Saltfish Player State', {
225
- state: state.playerState,
226
- step: state.currentStepIndex
227
- });
228
- });
403
+ // Manually start any playlist
404
+ saltfish.startPlaylist('onboarding-tour');
229
405
  ```
230
406
 
231
- ### Session Management
407
+ ## Common Use Cases
232
408
 
233
- The player automatically manages user sessions with 30-minute persistence:
409
+ ### Onboarding Flow
234
410
 
235
411
  ```javascript
236
- // Sessions are tracked automatically
237
- // Resume functionality works out of the box
238
- const player = new SaltfishPlayer({
239
- token: 'your-token',
240
- containerId: 'player-container'
241
- // User progress is automatically saved and restored
412
+ // Initialize on page load
413
+ saltfish.init({
414
+ token: 'YOUR_TOKEN',
415
+ enableAnalytics: true
416
+ });
417
+
418
+ // Identify user after signup
419
+ saltfish.identify(userId, {
420
+ email: userEmail,
421
+ language: 'auto',
422
+ plan: 'free'
242
423
  });
424
+
425
+ // Backend triggers will automatically show onboarding
426
+ // when configured in CMS for new users
243
427
  ```
244
428
 
245
- ## Browser Support
429
+ ### Feature Announcement
246
430
 
247
- - Chrome/Edge 90+
248
- - Firefox 88+
249
- - Safari 14+
250
- - Opera 76+
431
+ ```javascript
432
+ saltfish.init('YOUR_TOKEN');
433
+ saltfish.identify(userId);
251
434
 
252
- ## TypeScript Support
435
+ // Configure in CMS to trigger on /new-feature page
436
+ // Playlist auto-plays when user visits
437
+ ```
253
438
 
254
- Full TypeScript definitions are included:
439
+ ### Help Button
255
440
 
256
- ```typescript
257
- import { SaltfishPlayer, PlayerConfig, PlayerState } from 'saltfish';
441
+ ```html
442
+ <button id="help-btn">Help</button>
258
443
 
259
- const config: PlayerConfig = {
260
- token: 'your-token',
261
- containerId: 'player-container',
262
- autoplay: true
263
- };
444
+ <script>
445
+ saltfish.init('YOUR_TOKEN');
446
+ saltfish.identify(userId);
264
447
 
265
- const player = new SaltfishPlayer(config);
266
- const state: PlayerState = player.getState();
448
+ // Configure in CMS with element selector: #help-btn
449
+ // Playlist auto-triggers when user clicks
450
+ </script>
267
451
  ```
268
452
 
269
- ## Examples
453
+ ### Context-Specific Tours
270
454
 
271
- ### React Integration
455
+ ```javascript
456
+ // Different tours for different pages
457
+ saltfish.init('YOUR_TOKEN');
458
+ saltfish.identify(userId);
272
459
 
273
- ```jsx
274
- import { useEffect, useRef } from 'react';
275
- import { SaltfishPlayer } from 'saltfish';
460
+ // Configure in CMS:
461
+ // - "/dashboard" dashboard-tour
462
+ // - "/settings" settings-tour
463
+ // - "/billing" → billing-tour
276
464
 
277
- function OnboardingTour() {
278
- const containerRef = useRef(null);
279
- const playerRef = useRef(null);
465
+ // Tours trigger automatically based on URL
466
+ ```
467
+
468
+ ## Framework Integration
469
+
470
+ ### React
471
+
472
+ ```jsx
473
+ import { useEffect } from 'react';
474
+ import saltfish from 'saltfish';
280
475
 
476
+ function App() {
281
477
  useEffect(() => {
282
- if (containerRef.current) {
283
- playerRef.current = new SaltfishPlayer({
284
- token: process.env.REACT_APP_SALTFISH_TOKEN,
285
- containerId: 'saltfish-container',
286
- onComplete: () => {
287
- console.log('Onboarding completed!');
288
- }
289
- });
478
+ // Initialize Saltfish
479
+ saltfish.init({
480
+ token: process.env.REACT_APP_SALTFISH_TOKEN,
481
+ enableAnalytics: true
482
+ });
290
483
 
291
- playerRef.current.loadPlaylist('onboarding-tour');
484
+ // Identify user when auth state changes
485
+ if (user) {
486
+ saltfish.identify(user.id, {
487
+ email: user.email,
488
+ name: user.name,
489
+ language: 'auto'
490
+ });
292
491
  }
293
492
 
493
+ // Cleanup on unmount
294
494
  return () => {
295
- playerRef.current?.destroy();
495
+ saltfish.destroy();
296
496
  };
297
- }, []);
497
+ }, [user]);
298
498
 
299
- return <div id="saltfish-container" ref={containerRef} />;
499
+ return <div>Your App</div>;
300
500
  }
301
501
  ```
302
502
 
303
- ### Vue Integration
503
+ ### Vue
304
504
 
305
505
  ```vue
306
506
  <template>
307
- <div id="saltfish-container" ref="container"></div>
507
+ <div>Your App</div>
308
508
  </template>
309
509
 
310
510
  <script>
311
- import { SaltfishPlayer } from 'saltfish';
511
+ import saltfish from 'saltfish';
312
512
 
313
513
  export default {
314
- name: 'OnboardingTour',
315
514
  mounted() {
316
- this.player = new SaltfishPlayer({
515
+ saltfish.init({
317
516
  token: process.env.VUE_APP_SALTFISH_TOKEN,
318
- containerId: 'saltfish-container',
319
- onComplete: () => {
320
- console.log('Tour completed!');
321
- }
517
+ enableAnalytics: true
322
518
  });
323
519
 
324
- this.player.loadPlaylist('onboarding-tour');
520
+ if (this.user) {
521
+ saltfish.identify(this.user.id, {
522
+ email: this.user.email,
523
+ language: 'auto'
524
+ });
525
+ }
325
526
  },
326
527
  beforeUnmount() {
327
- this.player?.destroy();
528
+ saltfish.destroy();
328
529
  }
329
530
  };
330
531
  </script>
331
532
  ```
332
533
 
333
- ### Angular Integration
534
+ ### Angular
334
535
 
335
536
  ```typescript
336
537
  import { Component, OnInit, OnDestroy } from '@angular/core';
337
- import { SaltfishPlayer } from 'saltfish';
538
+ import saltfish from 'saltfish';
338
539
 
339
540
  @Component({
340
- selector: 'app-onboarding',
341
- template: '<div id="saltfish-container"></div>'
541
+ selector: 'app-root',
542
+ template: '<div>Your App</div>'
342
543
  })
343
- export class OnboardingComponent implements OnInit, OnDestroy {
344
- private player: SaltfishPlayer;
345
-
544
+ export class AppComponent implements OnInit, OnDestroy {
346
545
  ngOnInit() {
347
- this.player = new SaltfishPlayer({
546
+ saltfish.init({
348
547
  token: environment.saltfishToken,
349
- containerId: 'saltfish-container',
350
- onComplete: () => {
351
- console.log('Onboarding completed!');
352
- }
548
+ enableAnalytics: true
353
549
  });
354
550
 
355
- this.player.loadPlaylist('onboarding-tour');
551
+ if (this.authService.user) {
552
+ saltfish.identify(this.authService.user.id, {
553
+ email: this.authService.user.email,
554
+ language: 'auto'
555
+ });
556
+ }
356
557
  }
357
558
 
358
559
  ngOnDestroy() {
359
- this.player?.destroy();
560
+ saltfish.destroy();
360
561
  }
361
562
  }
362
563
  ```
363
564
 
364
- ## Troubleshooting
565
+ ### Next.js
365
566
 
366
- ### Player not loading
567
+ ```javascript
568
+ // app/layout.js or pages/_app.js
569
+ 'use client';
367
570
 
368
- - Verify your API token is valid
369
- - Check that the container element exists in the DOM
370
- - Ensure the playlist ID is correct
371
- - Check browser console for errors
571
+ import { useEffect } from 'react';
572
+ import saltfish from 'saltfish';
372
573
 
373
- ### Autoplay issues
574
+ export default function RootLayout({ children }) {
575
+ useEffect(() => {
576
+ if (typeof window !== 'undefined') {
577
+ saltfish.init({
578
+ token: process.env.NEXT_PUBLIC_SALTFISH_TOKEN,
579
+ enableAnalytics: true
580
+ });
374
581
 
375
- Some browsers block autoplay. The player automatically handles this:
582
+ // Identify user if logged in
583
+ // saltfish.identify(userId, { email, language: 'auto' });
584
+ }
376
585
 
377
- ```javascript
378
- const player = new SaltfishPlayer({
379
- token: 'your-token',
380
- containerId: 'player-container',
381
- autoplay: true // Player will show fallback UI if autoplay is blocked
382
- });
586
+ return () => {
587
+ if (typeof window !== 'undefined') {
588
+ saltfish.destroy();
589
+ }
590
+ };
591
+ }, []);
592
+
593
+ return <html><body>{children}</body></html>;
594
+ }
383
595
  ```
384
596
 
385
- ### Style conflicts
597
+ ## TypeScript Support
598
+
599
+ Full TypeScript definitions included:
386
600
 
387
- The player uses Shadow DOM to prevent style conflicts. If you need to customize:
601
+ ```typescript
602
+ import saltfish, { SaltfishAPI } from 'saltfish';
388
603
 
389
- ```css
390
- /* Use CSS custom properties */
391
- #player-container {
392
- --saltfish-z-index: 1000;
393
- }
604
+ // All methods are fully typed
605
+ saltfish.init({
606
+ token: 'YOUR_TOKEN',
607
+ enableAnalytics: true
608
+ });
609
+
610
+ saltfish.identify('user-123', {
611
+ email: 'user@example.com',
612
+ language: 'en'
613
+ });
614
+
615
+ saltfish.startPlaylist('playlist-id', {
616
+ startNodeId: 'step-2'
617
+ });
618
+
619
+ // Event handlers are typed
620
+ saltfish.on('playlistEnded', (data) => {
621
+ console.log(data.playlist.id); // Fully typed
622
+ console.log(data.completionRate);
623
+ });
394
624
  ```
395
625
 
396
- ## Getting a Token
626
+ ## Browser Support
627
+
628
+ - Chrome/Edge 90+
629
+ - Firefox 88+
630
+ - Safari 14+
631
+ - Opera 76+
397
632
 
398
- To use Saltfish, you need an API token. Visit [saltfish.ai](https://saltfish.ai) to:
633
+ ## Getting Started
399
634
 
400
- 1. Create an account
401
- 2. Generate an API token
402
- 3. Create playlists and video tours
635
+ 1. **Sign up** at [saltfish.ai](https://saltfish.ai)
636
+ 2. **Get your token** from the dashboard
637
+ 3. **Create playlists** using the CMS
638
+ 4. **Configure triggers** (optional) for automatic playback
639
+ 5. **Install** Saltfish in your app
403
640
 
404
641
  ## Support
405
642
 
406
- - 📧 Email: support@saltfish.ai
643
+ - 📧 Email: [support@saltfish.ai](mailto:support@saltfish.ai)
407
644
  - 🐛 Issues: [GitHub Issues](https://github.com/Saltfish-AB/playlist-player/issues)
408
645
  - 📚 Documentation: [docs.saltfish.ai](https://docs.saltfish.ai)
409
646
 
@@ -411,10 +648,6 @@ To use Saltfish, you need an API token. Visit [saltfish.ai](https://saltfish.ai)
411
648
 
412
649
  PROPRIETARY - See [LICENSE](./LICENSE) file for details.
413
650
 
414
- ## Contributing
415
-
416
- We welcome contributions! Please see our [contributing guidelines](https://github.com/Saltfish-AB/playlist-player/blob/main/CONTRIBUTING.md) for details.
417
-
418
651
  ---
419
652
 
420
653
  Made with ❤️ by [Saltfish AB](https://saltfish.ai)