@syncular/tauri 0.15.42 → 0.15.43

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/dist/index.d.ts CHANGED
@@ -84,8 +84,16 @@ export declare class TauriSyncClient {
84
84
  __dispatchEvent(event: SyncularEvent): void;
85
85
  securityLifecycle(): Promise<SecurityLifecycle>;
86
86
  beginSecurityPreflight(): Promise<void>;
87
+ /**
88
+ * Complete the security preflight. `headers` optionally carries a fresh
89
+ * FULL transport header set that the native host applies atomically with
90
+ * the activation, before the startup sync it enqueues — so a preflight
91
+ * that outlives the boot token starts its first round with valid
92
+ * credentials (the direct `setHeaders` path stays refused while gated).
93
+ */
87
94
  activateSecurity(options?: {
88
95
  readonly encryption?: EncryptionKeyringConfig;
96
+ readonly headers?: Readonly<Record<string, string>>;
89
97
  }): Promise<void>;
90
98
  onInvalidate(listener: InvalidationListener): () => void;
91
99
  onChange(listener: ClientChangeListener): () => void;
package/dist/index.js CHANGED
@@ -158,6 +158,7 @@ export class TauriSyncClient {
158
158
  #closed = false;
159
159
  #securityLifecycle;
160
160
  #preflightBarrier;
161
+ #diagnosticsEnabled = false;
161
162
  /** @internal — use {@link createTauriSyncClient}. */
162
163
  constructor(tauri, unlisten, securityLifecycle = 'active') {
163
164
  this.#tauri = tauri;
@@ -277,6 +278,13 @@ export class TauriSyncClient {
277
278
  });
278
279
  return barrier;
279
280
  }
281
+ /**
282
+ * Complete the security preflight. `headers` optionally carries a fresh
283
+ * FULL transport header set that the native host applies atomically with
284
+ * the activation, before the startup sync it enqueues — so a preflight
285
+ * that outlives the boot token starts its first round with valid
286
+ * credentials (the direct `setHeaders` path stays refused while gated).
287
+ */
280
288
  async activateSecurity(options = {}) {
281
289
  if (this.#securityLifecycle === 'active') {
282
290
  throw new TauriSyncError('sync.invalid_request', 'activateSecurity requires the client to be in security preflight');
@@ -286,6 +294,7 @@ export class TauriSyncClient {
286
294
  ...(options.encryption !== undefined
287
295
  ? { encryption: encodeEncryption(options.encryption) }
288
296
  : {}),
297
+ ...(options.headers !== undefined ? { headers: options.headers } : {}),
289
298
  });
290
299
  this.#securityLifecycle = 'active';
291
300
  }
@@ -299,8 +308,32 @@ export class TauriSyncClient {
299
308
  }
300
309
  onDiagnostics(listener) {
301
310
  this.#diagnosticsListeners.add(listener);
311
+ this.#enableDiagnosticsPush();
302
312
  return () => this.#diagnosticsListeners.delete(listener);
303
313
  }
314
+ /**
315
+ * Host-local push registration: the native core emits pushed diagnostics
316
+ * snapshots only once a consumer is known (a `diagnosticsSnapshot` pull, or
317
+ * this explicit `enableDiagnostics` signal). Fired once, on the first
318
+ * registered listener, fire-and-forget. The invoke deliberately bypasses
319
+ * the bridge's preflight gate — the command is a pure observability
320
+ * registration handled before dispatch, and the native side stays silent
321
+ * until activation anyway. Errors are tolerated: an older native core
322
+ * rejects the unknown command, and the mount-time snapshot pull path keeps
323
+ * `useDiagnostics` working there.
324
+ */
325
+ #enableDiagnosticsPush() {
326
+ if (this.#diagnosticsEnabled || this.#closed)
327
+ return;
328
+ this.#diagnosticsEnabled = true;
329
+ void this.#tauri
330
+ .invoke(`${PLUGIN}syncular_command`, {
331
+ command: { method: 'enableDiagnostics', params: {} },
332
+ })
333
+ .catch(() => {
334
+ /* fire-and-forget; an error reply is equally ignored above */
335
+ });
336
+ }
304
337
  onPresence(listener) {
305
338
  this.#presenceListeners.add(listener);
306
339
  return () => this.#presenceListeners.delete(listener);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/tauri",
3
- "version": "0.15.42",
3
+ "version": "0.15.43",
4
4
  "description": "Tauri integration for the Syncular client",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -48,12 +48,12 @@
48
48
  "test": "bun test"
49
49
  },
50
50
  "dependencies": {
51
- "@syncular/client": "0.15.42"
51
+ "@syncular/client": "0.15.43"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@tauri-apps/api": ">=2.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@syncular/react": "0.15.42"
57
+ "@syncular/react": "0.15.43"
58
58
  }
59
59
  }
package/src/index.ts CHANGED
@@ -265,6 +265,7 @@ export class TauriSyncClient {
265
265
  #closed = false;
266
266
  #securityLifecycle: SecurityLifecycle;
267
267
  #preflightBarrier: Promise<void> | undefined;
268
+ #diagnosticsEnabled = false;
268
269
 
269
270
  /** @internal — use {@link createTauriSyncClient}. */
270
271
  constructor(
@@ -408,8 +409,18 @@ export class TauriSyncClient {
408
409
  return barrier;
409
410
  }
410
411
 
412
+ /**
413
+ * Complete the security preflight. `headers` optionally carries a fresh
414
+ * FULL transport header set that the native host applies atomically with
415
+ * the activation, before the startup sync it enqueues — so a preflight
416
+ * that outlives the boot token starts its first round with valid
417
+ * credentials (the direct `setHeaders` path stays refused while gated).
418
+ */
411
419
  async activateSecurity(
412
- options: { readonly encryption?: EncryptionKeyringConfig } = {},
420
+ options: {
421
+ readonly encryption?: EncryptionKeyringConfig;
422
+ readonly headers?: Readonly<Record<string, string>>;
423
+ } = {},
413
424
  ): Promise<void> {
414
425
  if (this.#securityLifecycle === 'active') {
415
426
  throw new TauriSyncError(
@@ -422,6 +433,7 @@ export class TauriSyncClient {
422
433
  ...(options.encryption !== undefined
423
434
  ? { encryption: encodeEncryption(options.encryption) }
424
435
  : {}),
436
+ ...(options.headers !== undefined ? { headers: options.headers } : {}),
425
437
  });
426
438
  this.#securityLifecycle = 'active';
427
439
  }
@@ -438,9 +450,33 @@ export class TauriSyncClient {
438
450
 
439
451
  onDiagnostics(listener: ClientDiagnosticsListener): () => void {
440
452
  this.#diagnosticsListeners.add(listener);
453
+ this.#enableDiagnosticsPush();
441
454
  return () => this.#diagnosticsListeners.delete(listener);
442
455
  }
443
456
 
457
+ /**
458
+ * Host-local push registration: the native core emits pushed diagnostics
459
+ * snapshots only once a consumer is known (a `diagnosticsSnapshot` pull, or
460
+ * this explicit `enableDiagnostics` signal). Fired once, on the first
461
+ * registered listener, fire-and-forget. The invoke deliberately bypasses
462
+ * the bridge's preflight gate — the command is a pure observability
463
+ * registration handled before dispatch, and the native side stays silent
464
+ * until activation anyway. Errors are tolerated: an older native core
465
+ * rejects the unknown command, and the mount-time snapshot pull path keeps
466
+ * `useDiagnostics` working there.
467
+ */
468
+ #enableDiagnosticsPush(): void {
469
+ if (this.#diagnosticsEnabled || this.#closed) return;
470
+ this.#diagnosticsEnabled = true;
471
+ void this.#tauri
472
+ .invoke<CommandReply>(`${PLUGIN}syncular_command`, {
473
+ command: { method: 'enableDiagnostics', params: {} },
474
+ })
475
+ .catch(() => {
476
+ /* fire-and-forget; an error reply is equally ignored above */
477
+ });
478
+ }
479
+
444
480
  onPresence(listener: (scopeKey: string) => void): () => void {
445
481
  this.#presenceListeners.add(listener);
446
482
  return () => this.#presenceListeners.delete(listener);