@redseat/api 0.3.12 → 0.3.14

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/client.md CHANGED
@@ -329,6 +329,12 @@ Connects to the server's SSE endpoint for real-time updates.
329
329
  - `maxReconnectAttempts?`: Maximum reconnect attempts (default: unlimited)
330
330
  - `initialReconnectDelay?`: Initial reconnect delay in ms (default: `1000`)
331
331
  - `maxReconnectDelay?`: Maximum reconnect delay in ms (default: `30000`)
332
+ - `reconnectOnPageVisible?`: Reconnect when tab becomes visible after being hidden (default: `true`)
333
+ - `reconnectVisibleAfterMs?`: Minimum hidden duration before reconnect on visible (default: `30000`)
334
+ - `reconnectOnOnline?`: Reconnect when browser goes online (default: `true`)
335
+ - `reconnectOnFocus?`: Reconnect on window focus when connection is unhealthy (default: `true`)
336
+ - `reconnectOnAuthError?`: Retry reconnect after auth (`401`) errors (default: `true`)
337
+ - `maxAuthReconnectAttempts?`: Maximum auth reconnect attempts (default: `5`)
332
338
 
333
339
  **Example:**
334
340
  ```typescript
@@ -345,7 +351,9 @@ await client.connectSSE({
345
351
  autoReconnect: true,
346
352
  maxReconnectAttempts: 10,
347
353
  initialReconnectDelay: 2000,
348
- maxReconnectDelay: 60000
354
+ maxReconnectDelay: 60000,
355
+ reconnectOnPageVisible: true,
356
+ reconnectVisibleAfterMs: 45000
349
357
  });
350
358
  ```
351
359
 
@@ -667,4 +675,3 @@ client.dispose();
667
675
  - [ServerApi Documentation](server.md) - Uses RedseatClient for server operations
668
676
  - [LibraryApi Documentation](libraries.md) - Uses RedseatClient for library operations
669
677
  - [README](README.md) - Package overview
670
-
package/dist/client.d.ts CHANGED
@@ -5,7 +5,7 @@ import { IServer } from './interfaces.js';
5
5
  import { SSEConnectionState, SSEConnectionOptions, SSEConnectionError, SSELibraryEvent, SSELibraryStatusEvent, SSEMediasEvent, SSEUploadProgressEvent, SSEConvertProgressEvent, SSEEpisodesEvent, SSESeriesEvent, SSEMoviesEvent, SSEPeopleEvent, SSETagsEvent, SSEBackupsEvent, SSEBackupFilesEvent, SSEMediaRatingEvent, SSEMediaProgressEvent, SSEPlayersListEvent, SSEWatchedEvent, SSEUnwatchedEvent, SSERequestProcessingEvent } from './sse-types.js';
6
6
  export interface ClientOptions {
7
7
  server: IServer;
8
- getIdToken: () => Promise<string>;
8
+ getIdToken: (forceRefresh?: boolean) => Promise<string>;
9
9
  refreshThreshold?: number;
10
10
  timeout?: number;
11
11
  redseatUrl?: string;
@@ -24,8 +24,15 @@ export declare class RedseatClient {
24
24
  private disposed;
25
25
  private sseAbortController?;
26
26
  private sseReconnectTimeout?;
27
+ private sseActivityTimeout?;
27
28
  private sseReconnectAttempts;
29
+ private sseAuthReconnectAttempts;
28
30
  private sseOptions?;
31
+ private sseShouldReconnect;
32
+ private sseIsConnecting;
33
+ private sseLifecycleRegistered;
34
+ private sseHiddenAt?;
35
+ private readonly SSE_ACTIVITY_TIMEOUT;
29
36
  private readonly _sseConnectionState;
30
37
  private readonly _sseError;
31
38
  private readonly _sseEvents;
@@ -94,6 +101,15 @@ export declare class RedseatClient {
94
101
  * Returns public server info (IServer).
95
102
  */
96
103
  getServer(serverId: string): Promise<IServer>;
104
+ private readonly onDocumentVisibilityChange;
105
+ private readonly onWindowPageShow;
106
+ private readonly onWindowOnline;
107
+ private readonly onWindowFocus;
108
+ private shouldAttemptReconnect;
109
+ private clearSSEReconnectTimeout;
110
+ private registerSSELifecycleListeners;
111
+ private unregisterSSELifecycleListeners;
112
+ private forceReconnectSSE;
97
113
  /**
98
114
  * Connects to the server's SSE endpoint for real-time updates.
99
115
  * Automatically manages authentication and reconnection.
@@ -121,6 +137,16 @@ export declare class RedseatClient {
121
137
  * Builds the SSE endpoint URL with optional library filters
122
138
  */
123
139
  private buildSSEUrl;
140
+ /**
141
+ * Resets the activity timeout timer.
142
+ * Called when data is received from the SSE stream.
143
+ * If no data is received within the timeout period, triggers reconnection.
144
+ */
145
+ private resetActivityTimeout;
146
+ /**
147
+ * Clears the activity timeout timer.
148
+ */
149
+ private clearActivityTimeout;
124
150
  /**
125
151
  * Processes the SSE stream and emits events
126
152
  */
package/dist/client.js CHANGED
@@ -38,6 +38,11 @@ export class RedseatClient {
38
38
  constructor(options) {
39
39
  this.disposed = false;
40
40
  this.sseReconnectAttempts = 0;
41
+ this.sseAuthReconnectAttempts = 0;
42
+ this.sseShouldReconnect = false;
43
+ this.sseIsConnecting = false;
44
+ this.sseLifecycleRegistered = false;
45
+ this.SSE_ACTIVITY_TIMEOUT = 90000; // 90 seconds - reconnect if no data received
41
46
  // RxJS subjects for SSE
42
47
  this._sseConnectionState = new BehaviorSubject('disconnected');
43
48
  this._sseError = new Subject();
@@ -65,6 +70,62 @@ export class RedseatClient {
65
70
  this.watched$ = this.createEventStream('watched');
66
71
  this.unwatched$ = this.createEventStream('unwatched');
67
72
  this.requestProcessing$ = this.createEventStream('request_processing');
73
+ // ==================== SSE Methods ====================
74
+ this.onDocumentVisibilityChange = () => {
75
+ if (!this.shouldAttemptReconnect() || typeof document === 'undefined') {
76
+ return;
77
+ }
78
+ if (document.visibilityState === 'hidden') {
79
+ this.sseHiddenAt = Date.now();
80
+ return;
81
+ }
82
+ if (!(this.sseOptions?.reconnectOnPageVisible ?? true)) {
83
+ return;
84
+ }
85
+ const hiddenAt = this.sseHiddenAt;
86
+ this.sseHiddenAt = undefined;
87
+ if (hiddenAt === undefined) {
88
+ return;
89
+ }
90
+ const hiddenDuration = Date.now() - hiddenAt;
91
+ const minHiddenDuration = this.sseOptions?.reconnectVisibleAfterMs ?? 30000;
92
+ if (hiddenDuration >= minHiddenDuration) {
93
+ this.forceReconnectSSE();
94
+ }
95
+ };
96
+ this.onWindowPageShow = (event) => {
97
+ if (!this.shouldAttemptReconnect()) {
98
+ return;
99
+ }
100
+ if (!(this.sseOptions?.reconnectOnPageVisible ?? true)) {
101
+ return;
102
+ }
103
+ if (event.persisted || this.sseConnectionState !== 'connected') {
104
+ this.forceReconnectSSE();
105
+ }
106
+ };
107
+ this.onWindowOnline = () => {
108
+ if (!this.shouldAttemptReconnect()) {
109
+ return;
110
+ }
111
+ if (!(this.sseOptions?.reconnectOnOnline ?? true)) {
112
+ return;
113
+ }
114
+ if (this.sseConnectionState !== 'connected') {
115
+ this.forceReconnectSSE();
116
+ }
117
+ };
118
+ this.onWindowFocus = () => {
119
+ if (!this.shouldAttemptReconnect()) {
120
+ return;
121
+ }
122
+ if (!(this.sseOptions?.reconnectOnFocus ?? true)) {
123
+ return;
124
+ }
125
+ if (this.sseConnectionState !== 'connected') {
126
+ this.forceReconnectSSE();
127
+ }
128
+ };
68
129
  this.server = options.server;
69
130
  this.redseatUrl = options.redseatUrl;
70
131
  this.getIdToken = options.getIdToken;
@@ -167,7 +228,8 @@ export class RedseatClient {
167
228
  }
168
229
  this.tokenRefreshPromise = (async () => {
169
230
  try {
170
- const idToken = await this.getIdToken();
231
+ // Force refresh the Firebase idToken to ensure it's not stale/cached
232
+ const idToken = await this.getIdToken(true);
171
233
  // Use fetchServerToken which uses the global axios instance
172
234
  // The token endpoint is on the frontend server, not the backend server
173
235
  const newToken = await fetchServerToken(this.serverId, idToken, this.redseatUrl);
@@ -280,7 +342,52 @@ export class RedseatClient {
280
342
  });
281
343
  return response.data;
282
344
  }
283
- // ==================== SSE Methods ====================
345
+ shouldAttemptReconnect() {
346
+ return !this.disposed && this.sseShouldReconnect && (this.sseOptions?.autoReconnect ?? true);
347
+ }
348
+ clearSSEReconnectTimeout() {
349
+ if (this.sseReconnectTimeout) {
350
+ clearTimeout(this.sseReconnectTimeout);
351
+ this.sseReconnectTimeout = undefined;
352
+ }
353
+ }
354
+ registerSSELifecycleListeners() {
355
+ if (this.sseLifecycleRegistered || typeof window === 'undefined' || typeof document === 'undefined') {
356
+ return;
357
+ }
358
+ document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);
359
+ window.addEventListener('pageshow', this.onWindowPageShow);
360
+ window.addEventListener('online', this.onWindowOnline);
361
+ window.addEventListener('focus', this.onWindowFocus);
362
+ this.sseLifecycleRegistered = true;
363
+ if (document.visibilityState === 'hidden') {
364
+ this.sseHiddenAt = Date.now();
365
+ }
366
+ }
367
+ unregisterSSELifecycleListeners() {
368
+ if (!this.sseLifecycleRegistered || typeof window === 'undefined' || typeof document === 'undefined') {
369
+ return;
370
+ }
371
+ document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);
372
+ window.removeEventListener('pageshow', this.onWindowPageShow);
373
+ window.removeEventListener('online', this.onWindowOnline);
374
+ window.removeEventListener('focus', this.onWindowFocus);
375
+ this.sseLifecycleRegistered = false;
376
+ this.sseHiddenAt = undefined;
377
+ }
378
+ forceReconnectSSE() {
379
+ if (!this.shouldAttemptReconnect()) {
380
+ return;
381
+ }
382
+ this.clearSSEReconnectTimeout();
383
+ if (this.sseAbortController) {
384
+ this.sseAbortController.abort();
385
+ this.sseAbortController = undefined;
386
+ }
387
+ this.sseReconnectAttempts = 0;
388
+ this._sseConnectionState.next('reconnecting');
389
+ void this._connectSSE();
390
+ }
284
391
  /**
285
392
  * Connects to the server's SSE endpoint for real-time updates.
286
393
  * Automatically manages authentication and reconnection.
@@ -293,23 +400,34 @@ export class RedseatClient {
293
400
  autoReconnect: true,
294
401
  initialReconnectDelay: 1000,
295
402
  maxReconnectDelay: 30000,
403
+ reconnectOnPageVisible: true,
404
+ reconnectVisibleAfterMs: 30000,
405
+ reconnectOnOnline: true,
406
+ reconnectOnFocus: true,
407
+ reconnectOnAuthError: true,
408
+ maxAuthReconnectAttempts: 5,
296
409
  ...options
297
410
  };
298
411
  this.sseReconnectAttempts = 0;
412
+ this.sseAuthReconnectAttempts = 0;
413
+ this.sseShouldReconnect = true;
414
+ this.registerSSELifecycleListeners();
299
415
  await this._connectSSE();
300
416
  }
301
417
  /**
302
418
  * Disconnects from the SSE endpoint and cleans up resources.
303
419
  */
304
420
  disconnectSSE() {
305
- if (this.sseReconnectTimeout) {
306
- clearTimeout(this.sseReconnectTimeout);
307
- this.sseReconnectTimeout = undefined;
308
- }
421
+ this.sseShouldReconnect = false;
422
+ this.sseIsConnecting = false;
423
+ this.sseAuthReconnectAttempts = 0;
424
+ this.clearSSEReconnectTimeout();
425
+ this.clearActivityTimeout();
309
426
  if (this.sseAbortController) {
310
427
  this.sseAbortController.abort();
311
428
  this.sseAbortController = undefined;
312
429
  }
430
+ this.unregisterSSELifecycleListeners();
313
431
  this._sseConnectionState.next('disconnected');
314
432
  }
315
433
  /**
@@ -335,6 +453,14 @@ export class RedseatClient {
335
453
  * Internal method to establish SSE connection
336
454
  */
337
455
  async _connectSSE() {
456
+ if (this.disposed || !this.sseShouldReconnect || this.sseIsConnecting) {
457
+ return;
458
+ }
459
+ if (this.sseConnectionState === 'connected' && this.sseAbortController) {
460
+ return;
461
+ }
462
+ this.clearSSEReconnectTimeout();
463
+ this.sseIsConnecting = true;
338
464
  this._sseConnectionState.next('connecting');
339
465
  try {
340
466
  // Ensure we have a valid token
@@ -343,28 +469,54 @@ export class RedseatClient {
343
469
  throw new Error('No authentication token available');
344
470
  }
345
471
  const url = this.buildSSEUrl();
346
- this.sseAbortController = new AbortController();
472
+ const abortController = new AbortController();
473
+ this.sseAbortController = abortController;
347
474
  console.log("SSSEEEE URL", url);
348
- const response = await fetch(url, {
475
+ let response = await fetch(url, {
349
476
  method: 'GET',
350
477
  headers: {
351
478
  'Authorization': `Bearer ${this.tokenData.token}`,
352
479
  'Accept': 'text/event-stream',
353
480
  'Cache-Control': 'no-cache'
354
481
  },
355
- signal: this.sseAbortController.signal
482
+ signal: abortController.signal
356
483
  });
357
484
  if (!response.ok) {
358
485
  if (response.status === 401) {
359
- throw Object.assign(new Error('Authentication failed'), { type: 'auth' });
486
+ // Try refreshing token and retry ONCE before giving up
487
+ try {
488
+ console.log('SSE 401 - attempting token refresh and retry');
489
+ await this.refreshToken();
490
+ // Retry the connection with the fresh token
491
+ const retryResponse = await fetch(url, {
492
+ method: 'GET',
493
+ headers: {
494
+ 'Authorization': `Bearer ${this.tokenData.token}`,
495
+ 'Accept': 'text/event-stream',
496
+ 'Cache-Control': 'no-cache'
497
+ },
498
+ signal: this.sseAbortController.signal
499
+ });
500
+ if (!retryResponse.ok) {
501
+ throw Object.assign(new Error('Authentication failed after token refresh'), { type: 'auth' });
502
+ }
503
+ // Use the retry response instead
504
+ response = retryResponse;
505
+ }
506
+ catch (retryError) {
507
+ throw Object.assign(new Error('Authentication failed'), { type: 'auth' });
508
+ }
509
+ }
510
+ else {
511
+ throw Object.assign(new Error(`Server returned ${response.status}`), { type: 'server' });
360
512
  }
361
- throw Object.assign(new Error(`Server returned ${response.status}`), { type: 'server' });
362
513
  }
363
514
  if (!response.body) {
364
515
  throw Object.assign(new Error('No response body'), { type: 'server' });
365
516
  }
366
517
  this._sseConnectionState.next('connected');
367
518
  this.sseReconnectAttempts = 0;
519
+ this.sseAuthReconnectAttempts = 0;
368
520
  // Process the stream in the background (don't await - it runs forever until disconnected)
369
521
  this.processSSEStream(response.body).catch(err => {
370
522
  if (err?.name !== 'AbortError') {
@@ -377,8 +529,12 @@ export class RedseatClient {
377
529
  // Intentionally disconnected
378
530
  return;
379
531
  }
532
+ this.sseAbortController = undefined;
380
533
  this.handleSSEError(error);
381
534
  }
535
+ finally {
536
+ this.sseIsConnecting = false;
537
+ }
382
538
  }
383
539
  /**
384
540
  * Builds the SSE endpoint URL with optional library filters
@@ -392,6 +548,39 @@ export class RedseatClient {
392
548
  }
393
549
  return url;
394
550
  }
551
+ /**
552
+ * Resets the activity timeout timer.
553
+ * Called when data is received from the SSE stream.
554
+ * If no data is received within the timeout period, triggers reconnection.
555
+ */
556
+ resetActivityTimeout() {
557
+ if (this.sseActivityTimeout) {
558
+ clearTimeout(this.sseActivityTimeout);
559
+ }
560
+ this.sseActivityTimeout = setTimeout(() => {
561
+ if (this.disposed) {
562
+ return;
563
+ }
564
+ // No activity within timeout period - connection is likely stale
565
+ console.log('SSE activity timeout - reconnecting');
566
+ this._sseConnectionState.next('disconnected');
567
+ // Abort current connection and schedule reconnect
568
+ if (this.sseAbortController) {
569
+ this.sseAbortController.abort();
570
+ this.sseAbortController = undefined;
571
+ }
572
+ this.scheduleReconnect();
573
+ }, this.SSE_ACTIVITY_TIMEOUT);
574
+ }
575
+ /**
576
+ * Clears the activity timeout timer.
577
+ */
578
+ clearActivityTimeout() {
579
+ if (this.sseActivityTimeout) {
580
+ clearTimeout(this.sseActivityTimeout);
581
+ this.sseActivityTimeout = undefined;
582
+ }
583
+ }
395
584
  /**
396
585
  * Processes the SSE stream and emits events
397
586
  */
@@ -399,15 +588,22 @@ export class RedseatClient {
399
588
  const reader = body.getReader();
400
589
  const decoder = new TextDecoder();
401
590
  let buffer = '';
591
+ // Start activity timeout tracking
592
+ this.resetActivityTimeout();
402
593
  try {
403
594
  while (true) {
404
595
  const { done, value } = await reader.read();
405
596
  if (done) {
406
597
  // Stream ended - server closed connection
598
+ this.clearActivityTimeout();
407
599
  this._sseConnectionState.next('disconnected');
408
- this.scheduleReconnect();
600
+ if (this.shouldAttemptReconnect()) {
601
+ this.scheduleReconnect();
602
+ }
409
603
  break;
410
604
  }
605
+ // Data received - reset activity timeout
606
+ this.resetActivityTimeout();
411
607
  buffer += decoder.decode(value, { stream: true });
412
608
  const { events, remainingBuffer } = this.parseSSEBuffer(buffer);
413
609
  buffer = remainingBuffer;
@@ -418,12 +614,14 @@ export class RedseatClient {
418
614
  }
419
615
  }
420
616
  catch (error) {
617
+ this.clearActivityTimeout();
421
618
  if (error instanceof Error && error.name === 'AbortError') {
422
619
  return;
423
620
  }
424
621
  throw error;
425
622
  }
426
623
  finally {
624
+ this.sseAbortController = undefined;
427
625
  reader.releaseLock();
428
626
  }
429
627
  }
@@ -497,6 +695,9 @@ export class RedseatClient {
497
695
  * Handles SSE errors and triggers reconnection if appropriate
498
696
  */
499
697
  handleSSEError(error) {
698
+ if (this.disposed || !this.sseShouldReconnect) {
699
+ return;
700
+ }
500
701
  const sseError = {
501
702
  type: 'network',
502
703
  message: error instanceof Error ? error.message : 'Unknown error',
@@ -509,40 +710,68 @@ export class RedseatClient {
509
710
  }
510
711
  this._sseConnectionState.next('error');
511
712
  this._sseError.next(sseError);
512
- // Don't reconnect on auth errors
513
713
  if (sseError.type === 'auth') {
714
+ if (!(this.sseOptions?.reconnectOnAuthError ?? true)) {
715
+ return;
716
+ }
717
+ const maxAuthReconnectAttempts = this.sseOptions?.maxAuthReconnectAttempts ?? 5;
718
+ if (this.sseAuthReconnectAttempts >= maxAuthReconnectAttempts) {
719
+ return;
720
+ }
721
+ this.sseAuthReconnectAttempts++;
722
+ this.scheduleReconnect();
514
723
  return;
515
724
  }
725
+ this.sseAuthReconnectAttempts = 0;
516
726
  this.scheduleReconnect();
517
727
  }
518
728
  /**
519
729
  * Schedules a reconnection attempt with exponential backoff
520
730
  */
521
731
  scheduleReconnect() {
522
- if (!this.sseOptions?.autoReconnect) {
732
+ if (!this.shouldAttemptReconnect()) {
733
+ return;
734
+ }
735
+ const options = this.sseOptions;
736
+ if (!options) {
523
737
  return;
524
738
  }
525
- if (this.sseOptions.maxReconnectAttempts !== undefined &&
526
- this.sseReconnectAttempts >= this.sseOptions.maxReconnectAttempts) {
739
+ if (options.maxReconnectAttempts !== undefined &&
740
+ this.sseReconnectAttempts >= options.maxReconnectAttempts) {
527
741
  return;
528
742
  }
529
- const initialDelay = this.sseOptions.initialReconnectDelay ?? 1000;
530
- const maxDelay = this.sseOptions.maxReconnectDelay ?? 30000;
743
+ if (this.sseReconnectTimeout) {
744
+ return;
745
+ }
746
+ const initialDelay = options.initialReconnectDelay ?? 1000;
747
+ const maxDelay = options.maxReconnectDelay ?? 30000;
531
748
  // Exponential backoff with jitter
532
749
  const exponentialDelay = initialDelay * Math.pow(2, this.sseReconnectAttempts);
533
750
  const jitter = Math.random() * 1000;
534
751
  const delay = Math.min(exponentialDelay + jitter, maxDelay);
535
752
  this._sseConnectionState.next('reconnecting');
536
753
  this.sseReconnectAttempts++;
537
- this.sseReconnectTimeout = setTimeout(async () => {
538
- // Refresh token before reconnecting
539
- try {
540
- await this.refreshToken();
541
- }
542
- catch {
543
- // Token refresh failed, will try again on next reconnect
754
+ this.sseReconnectTimeout = setTimeout(() => {
755
+ this.sseReconnectTimeout = undefined;
756
+ if (!this.shouldAttemptReconnect()) {
757
+ return;
544
758
  }
545
- this._connectSSE();
759
+ void (async () => {
760
+ try {
761
+ await this.refreshToken();
762
+ }
763
+ catch (error) {
764
+ console.log('SSE reconnect: token refresh failed, scheduling another retry', error);
765
+ this._sseError.next({
766
+ type: 'auth',
767
+ message: `Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
768
+ timestamp: Date.now()
769
+ });
770
+ this.scheduleReconnect();
771
+ return;
772
+ }
773
+ await this._connectSSE();
774
+ })();
546
775
  }, delay);
547
776
  }
548
777
  }
@@ -130,6 +130,16 @@ export declare enum LibraryRole {
130
130
  share = "share",
131
131
  admin = "admin"
132
132
  }
133
+ export interface UserMapping {
134
+ [key: string]: unknown;
135
+ }
136
+ export interface ServerLibrarySettings {
137
+ faceThreshold?: number;
138
+ ignoreGroups?: boolean;
139
+ preductionModel?: string;
140
+ mapProgress?: UserMapping[];
141
+ dataPath?: string;
142
+ }
133
143
  export interface ILibrary {
134
144
  id?: string;
135
145
  name: string;
@@ -137,6 +147,7 @@ export interface ILibrary {
137
147
  source?: LibrarySources;
138
148
  roles?: LibraryRole[];
139
149
  crypt?: boolean;
150
+ settings: ServerLibrarySettings;
140
151
  hidden?: boolean;
141
152
  status?: string;
142
153
  }
package/dist/server.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare class ServerApi {
11
11
  timeout?: number;
12
12
  }): Promise<any>;
13
13
  addLibrary(library: Partial<ILibrary>): Promise<ILibrary>;
14
- getSetting(key: string): Promise<string>;
14
+ updateLibrary(libraryId: string, library: Partial<ILibrary>): Promise<ILibrary>;
15
15
  getPlugins(): Promise<IPlugin[]>;
16
16
  getCredentials(): Promise<ICredential[]>;
17
17
  saveCredential(credential: ICredential): Promise<ICredential>;
package/dist/server.js CHANGED
@@ -18,9 +18,9 @@ export class ServerApi {
18
18
  const res = await this.client.post('/libraries', library);
19
19
  return res.data;
20
20
  }
21
- async getSetting(key) {
22
- const res = await this.client.get(`/settings/${key}`);
23
- return res.data.value;
21
+ async updateLibrary(libraryId, library) {
22
+ const res = await this.client.patch(`/libraries/${libraryId}`, library);
23
+ return res.data;
24
24
  }
25
25
  async getPlugins() {
26
26
  const res = await this.client.get('/plugins');
@@ -12,6 +12,18 @@ export interface SSEConnectionOptions {
12
12
  initialReconnectDelay?: number;
13
13
  /** Maximum reconnect delay in ms (default: 30000) */
14
14
  maxReconnectDelay?: number;
15
+ /** Reconnect when tab becomes visible after being hidden for a while (default: true) */
16
+ reconnectOnPageVisible?: boolean;
17
+ /** Minimum hidden time before reconnecting on visible, in ms (default: 30000) */
18
+ reconnectVisibleAfterMs?: number;
19
+ /** Reconnect when browser comes back online (default: true) */
20
+ reconnectOnOnline?: boolean;
21
+ /** Reconnect on window focus when disconnected/error (default: true) */
22
+ reconnectOnFocus?: boolean;
23
+ /** Retry reconnection on auth (401) errors (default: true) */
24
+ reconnectOnAuthError?: boolean;
25
+ /** Maximum auth-error reconnect attempts before giving up (default: 5) */
26
+ maxAuthReconnectAttempts?: number;
15
27
  }
16
28
  export interface SSEConnectionError {
17
29
  type: 'network' | 'auth' | 'server' | 'parse';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redseat/api",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "TypeScript API client library for interacting with Redseat servers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/server.md CHANGED
@@ -6,6 +6,7 @@ The `ServerApi` class provides server-level operations for managing libraries, s
6
6
 
7
7
  `ServerApi` handles operations that are not specific to a single library, such as:
8
8
  - Listing and creating libraries
9
+ - Updating libraries
9
10
  - Getting current user information
10
11
  - Managing server settings
11
12
  - Listing plugins and credentials
@@ -66,6 +67,12 @@ Creates a new library.
66
67
  - `type`: Library type - `'photos'`, `'shows'`, `'movies'`, or `'iptv'` (required)
67
68
  - `source`: Optional source type
68
69
  - `crypt`: Optional boolean to enable encryption
70
+ - `settings`: Optional library settings object:
71
+ - `faceThreshold?: number`
72
+ - `ignoreGroups?: boolean`
73
+ - `preductionModel?: string` prediction model to use for tagging
74
+ - `mapProgress?: Record<string, any>[]` allow to map view progress from a user to another user
75
+ - `dataPath?: string` custom path to store library running data like thumnails, cache, portraits...
69
76
  - `hidden`: Optional boolean to hide library
70
77
 
71
78
  **Returns:** Promise resolving to the created `ILibrary` object with generated `id`
@@ -75,24 +82,33 @@ Creates a new library.
75
82
  const newLibrary = await serverApi.addLibrary({
76
83
  name: 'My Photos',
77
84
  type: 'photos',
78
- crypt: true // Enable encryption
85
+ crypt: true, // Enable encryption
86
+ settings: {
87
+ faceThreshold: 0.7
88
+ }
79
89
  });
80
90
  console.log(`Created library with ID: ${newLibrary.id}`);
81
91
  ```
82
92
 
83
- ### `getSetting(key: string): Promise<string>`
93
+ ### `updateLibrary(libraryId: string, library: Partial<ILibrary>): Promise<ILibrary>`
84
94
 
85
- Retrieves a server setting value by key.
95
+ Updates an existing library.
86
96
 
87
97
  **Parameters:**
88
- - `key`: The setting key to retrieve
98
+ - `libraryId`: The library ID to update
99
+ - `library`: Partial library object containing fields to update (for example `name`, `source`, or `settings`)
89
100
 
90
- **Returns:** Promise resolving to the setting value as a string
101
+ **Returns:** Promise resolving to the updated `ILibrary` object
91
102
 
92
103
  **Example:**
93
104
  ```typescript
94
- const maxUploadSize = await serverApi.getSetting('max_upload_size');
95
- console.log(`Max upload size: ${maxUploadSize}`);
105
+ const updatedLibrary = await serverApi.updateLibrary('library-123', {
106
+ name: 'Renamed Library',
107
+ settings: {
108
+ faceThreshold: 0.8
109
+ }
110
+ });
111
+ console.log(`Updated library: ${updatedLibrary.name}`);
96
112
  ```
97
113
 
98
114
  ### `getPlugins(): Promise<any[]>`
@@ -509,6 +525,11 @@ const newLibrary = await serverApi.addLibrary({
509
525
  crypt: true
510
526
  });
511
527
 
528
+ // Update the library settings
529
+ const updatedLibrary = await serverApi.updateLibrary(newLibrary.id!, {
530
+ name: 'Encrypted Family Photos'
531
+ });
532
+
512
533
  // Get server settings
513
534
  const maxUpload = await serverApi.getSetting('max_upload_size');
514
535
  console.log(`Max upload size: ${maxUpload}`);
@@ -535,4 +556,3 @@ try {
535
556
  - [RedseatClient Documentation](client.md) - HTTP client used by ServerApi
536
557
  - [LibraryApi Documentation](libraries.md) - Library-specific operations
537
558
  - [README](README.md) - Package overview
538
-