@wavecraft/core 0.7.0

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.js ADDED
@@ -0,0 +1,782 @@
1
+ import { useState, useCallback, useEffect, useMemo } from "react";
2
+ import { dbToLinear, linearToDb } from "./meters.js";
3
+ function isWebViewEnvironment() {
4
+ return globalThis.__WAVECRAFT_IPC__ !== void 0;
5
+ }
6
+ function isBrowserEnvironment() {
7
+ return !isWebViewEnvironment();
8
+ }
9
+ const ERROR_PARSE = -32700;
10
+ const ERROR_INVALID_REQUEST = -32600;
11
+ const ERROR_METHOD_NOT_FOUND = -32601;
12
+ const ERROR_INVALID_PARAMS = -32602;
13
+ const ERROR_INTERNAL = -32603;
14
+ const ERROR_PARAM_NOT_FOUND = -32e3;
15
+ const ERROR_PARAM_OUT_OF_RANGE = -32001;
16
+ const METHOD_GET_PARAMETER = "getParameter";
17
+ const METHOD_SET_PARAMETER = "setParameter";
18
+ const METHOD_GET_ALL_PARAMETERS = "getAllParameters";
19
+ const NOTIFICATION_PARAMETER_CHANGED = "parameterChanged";
20
+ function isIpcResponse(obj) {
21
+ return typeof obj === "object" && obj !== null && "jsonrpc" in obj && "id" in obj && ("result" in obj || "error" in obj);
22
+ }
23
+ function isIpcNotification(obj) {
24
+ return typeof obj === "object" && obj !== null && "jsonrpc" in obj && "method" in obj && !("id" in obj);
25
+ }
26
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
27
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
28
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
29
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
30
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
31
+ return LogLevel2;
32
+ })(LogLevel || {});
33
+ class Logger {
34
+ constructor(options = {}) {
35
+ this.minLevel = options.minLevel ?? 0;
36
+ }
37
+ /**
38
+ * Set the minimum log level at runtime.
39
+ */
40
+ setMinLevel(level) {
41
+ this.minLevel = level;
42
+ }
43
+ /**
44
+ * Log debug message (verbose tracing).
45
+ */
46
+ debug(message, context) {
47
+ if (this.minLevel <= 0) {
48
+ console.debug(`[DEBUG] ${message}`, context ?? {});
49
+ }
50
+ }
51
+ /**
52
+ * Log informational message.
53
+ */
54
+ info(message, context) {
55
+ if (this.minLevel <= 1) {
56
+ console.info(`[INFO] ${message}`, context ?? {});
57
+ }
58
+ }
59
+ /**
60
+ * Log warning message.
61
+ */
62
+ warn(message, context) {
63
+ if (this.minLevel <= 2) {
64
+ console.warn(`[WARN] ${message}`, context ?? {});
65
+ }
66
+ }
67
+ /**
68
+ * Log error message.
69
+ */
70
+ error(message, context) {
71
+ if (this.minLevel <= 3) {
72
+ console.error(`[ERROR] ${message}`, context ?? {});
73
+ }
74
+ }
75
+ }
76
+ const logger = new Logger({
77
+ minLevel: 1
78
+ /* INFO */
79
+ });
80
+ class NativeTransport {
81
+ constructor() {
82
+ this.pendingRequests = /* @__PURE__ */ new Map();
83
+ this.notificationCallbacks = /* @__PURE__ */ new Set();
84
+ this.primitives = globalThis.__WAVECRAFT_IPC__;
85
+ if (!this.primitives) {
86
+ throw new Error(
87
+ "NativeTransport: __WAVECRAFT_IPC__ primitives not found. Ensure this runs in a WKWebView with injected IPC."
88
+ );
89
+ }
90
+ this.primitives.setReceiveCallback((message) => {
91
+ this.handleIncomingMessage(message);
92
+ });
93
+ if (this.primitives.onParamUpdate) {
94
+ this.primitives.onParamUpdate((notification) => {
95
+ if (isIpcNotification(notification)) {
96
+ this.handleNotification(notification);
97
+ }
98
+ });
99
+ }
100
+ }
101
+ /**
102
+ * Send a JSON-RPC request and wait for response
103
+ */
104
+ async send(request) {
105
+ if (!this.primitives) {
106
+ throw new Error("NativeTransport: Primitives not available");
107
+ }
108
+ const parsedRequest = JSON.parse(request);
109
+ const id = parsedRequest.id;
110
+ if (id === void 0 || id === null) {
111
+ throw new Error("NativeTransport: Request must have an id");
112
+ }
113
+ const responsePromise = new Promise((resolve, reject) => {
114
+ const timeoutId = setTimeout(() => {
115
+ this.pendingRequests.delete(id);
116
+ reject(new Error(`Request timeout: ${parsedRequest.method}`));
117
+ }, 5e3);
118
+ this.pendingRequests.set(id, { resolve, reject, timeoutId });
119
+ });
120
+ this.primitives.postMessage(request);
121
+ return responsePromise;
122
+ }
123
+ /**
124
+ * Register a callback for incoming notifications
125
+ */
126
+ onNotification(callback) {
127
+ this.notificationCallbacks.add(callback);
128
+ return () => {
129
+ this.notificationCallbacks.delete(callback);
130
+ };
131
+ }
132
+ /**
133
+ * Check if transport is connected (native is always connected)
134
+ */
135
+ isConnected() {
136
+ return true;
137
+ }
138
+ /**
139
+ * Clean up resources
140
+ */
141
+ dispose() {
142
+ for (const [id, { reject, timeoutId }] of this.pendingRequests.entries()) {
143
+ clearTimeout(timeoutId);
144
+ reject(new Error("Transport disposed"));
145
+ this.pendingRequests.delete(id);
146
+ }
147
+ this.notificationCallbacks.clear();
148
+ }
149
+ /**
150
+ * Handle incoming message (response or notification)
151
+ */
152
+ handleIncomingMessage(message) {
153
+ try {
154
+ const parsed = JSON.parse(message);
155
+ if (isIpcResponse(parsed)) {
156
+ this.handleResponse(parsed);
157
+ } else if (isIpcNotification(parsed)) {
158
+ this.handleNotification(parsed);
159
+ }
160
+ } catch (error) {
161
+ logger.error("Failed to parse incoming message", { error });
162
+ }
163
+ }
164
+ /**
165
+ * Handle JSON-RPC response
166
+ */
167
+ handleResponse(response) {
168
+ const pending = this.pendingRequests.get(response.id);
169
+ if (pending) {
170
+ clearTimeout(pending.timeoutId);
171
+ this.pendingRequests.delete(response.id);
172
+ pending.resolve(JSON.stringify(response));
173
+ }
174
+ }
175
+ /**
176
+ * Handle notification and dispatch to listeners
177
+ */
178
+ handleNotification(notification) {
179
+ const notificationJson = JSON.stringify(notification);
180
+ for (const callback of this.notificationCallbacks) {
181
+ try {
182
+ callback(notificationJson);
183
+ } catch (error) {
184
+ logger.error("Error in notification callback", { error });
185
+ }
186
+ }
187
+ }
188
+ }
189
+ class WebSocketTransport {
190
+ constructor(options) {
191
+ this.ws = null;
192
+ this.isConnecting = false;
193
+ this.reconnectAttempts = 0;
194
+ this.reconnectTimeoutId = null;
195
+ this.isDisposed = false;
196
+ this.maxAttemptsReached = false;
197
+ this.pendingRequests = /* @__PURE__ */ new Map();
198
+ this.notificationCallbacks = /* @__PURE__ */ new Set();
199
+ this.url = options.url;
200
+ this.reconnectDelayMs = options.reconnectDelayMs ?? 1e3;
201
+ this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;
202
+ this.connect();
203
+ }
204
+ /**
205
+ * Send a JSON-RPC request and wait for response
206
+ */
207
+ async send(request) {
208
+ if (!this.isConnected()) {
209
+ throw new Error("WebSocketTransport: Not connected");
210
+ }
211
+ const parsedRequest = JSON.parse(request);
212
+ const id = parsedRequest.id;
213
+ if (id === void 0 || id === null) {
214
+ throw new Error("WebSocketTransport: Request must have an id");
215
+ }
216
+ const responsePromise = new Promise((resolve, reject) => {
217
+ const timeoutId = setTimeout(() => {
218
+ this.pendingRequests.delete(id);
219
+ reject(new Error(`Request timeout: ${parsedRequest.method}`));
220
+ }, 5e3);
221
+ this.pendingRequests.set(id, { resolve, reject, timeoutId });
222
+ });
223
+ if (!this.ws) {
224
+ throw new Error("WebSocketTransport: Connection lost");
225
+ }
226
+ this.ws.send(request);
227
+ return responsePromise;
228
+ }
229
+ /**
230
+ * Register a callback for incoming notifications
231
+ */
232
+ onNotification(callback) {
233
+ this.notificationCallbacks.add(callback);
234
+ return () => {
235
+ this.notificationCallbacks.delete(callback);
236
+ };
237
+ }
238
+ /**
239
+ * Check if transport is connected
240
+ */
241
+ isConnected() {
242
+ return this.ws !== null && this.ws.readyState === WebSocket.OPEN;
243
+ }
244
+ /**
245
+ * Clean up resources and close connection
246
+ */
247
+ dispose() {
248
+ this.isDisposed = true;
249
+ if (this.reconnectTimeoutId) {
250
+ clearTimeout(this.reconnectTimeoutId);
251
+ this.reconnectTimeoutId = null;
252
+ }
253
+ if (this.ws) {
254
+ this.ws.close();
255
+ this.ws = null;
256
+ }
257
+ for (const [id, { reject, timeoutId }] of this.pendingRequests.entries()) {
258
+ clearTimeout(timeoutId);
259
+ reject(new Error("Transport disposed"));
260
+ this.pendingRequests.delete(id);
261
+ }
262
+ this.notificationCallbacks.clear();
263
+ }
264
+ /**
265
+ * Attempt to connect to WebSocket server
266
+ */
267
+ connect() {
268
+ if (this.isDisposed || this.isConnecting || this.isConnected()) {
269
+ return;
270
+ }
271
+ this.isConnecting = true;
272
+ try {
273
+ this.ws = new WebSocket(this.url);
274
+ this.ws.onopen = () => {
275
+ this.isConnecting = false;
276
+ this.reconnectAttempts = 0;
277
+ logger.info("WebSocketTransport connected", { url: this.url });
278
+ };
279
+ this.ws.onmessage = (event) => {
280
+ this.handleIncomingMessage(event.data);
281
+ };
282
+ this.ws.onerror = (error) => {
283
+ logger.error("WebSocketTransport connection error", { error });
284
+ };
285
+ this.ws.onclose = () => {
286
+ this.isConnecting = false;
287
+ this.ws = null;
288
+ if (!this.isDisposed && !this.maxAttemptsReached) {
289
+ this.scheduleReconnect();
290
+ }
291
+ };
292
+ } catch (error) {
293
+ this.isConnecting = false;
294
+ logger.error("WebSocketTransport failed to create WebSocket", { error, url: this.url });
295
+ this.scheduleReconnect();
296
+ }
297
+ }
298
+ /**
299
+ * Schedule reconnection attempt with exponential backoff
300
+ */
301
+ scheduleReconnect() {
302
+ if (this.isDisposed || this.maxAttemptsReached) {
303
+ return;
304
+ }
305
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
306
+ this.maxAttemptsReached = true;
307
+ logger.error("WebSocketTransport max reconnect attempts reached", {
308
+ maxAttempts: this.maxReconnectAttempts
309
+ });
310
+ if (this.ws) {
311
+ this.ws.close();
312
+ this.ws = null;
313
+ }
314
+ return;
315
+ }
316
+ this.reconnectAttempts++;
317
+ const delay = this.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1);
318
+ logger.debug("WebSocketTransport reconnecting", {
319
+ delayMs: delay,
320
+ attempt: this.reconnectAttempts,
321
+ maxAttempts: this.maxReconnectAttempts
322
+ });
323
+ this.reconnectTimeoutId = setTimeout(() => {
324
+ this.reconnectTimeoutId = null;
325
+ this.connect();
326
+ }, delay);
327
+ }
328
+ /**
329
+ * Handle incoming message (response or notification)
330
+ */
331
+ handleIncomingMessage(message) {
332
+ try {
333
+ const parsed = JSON.parse(message);
334
+ if (isIpcResponse(parsed)) {
335
+ this.handleResponse(parsed);
336
+ } else if (isIpcNotification(parsed)) {
337
+ this.handleNotification(parsed);
338
+ }
339
+ } catch (error) {
340
+ logger.error("WebSocketTransport failed to parse incoming message", { error, message });
341
+ }
342
+ }
343
+ /**
344
+ * Handle JSON-RPC response
345
+ */
346
+ handleResponse(response) {
347
+ const pending = this.pendingRequests.get(response.id);
348
+ if (pending) {
349
+ clearTimeout(pending.timeoutId);
350
+ this.pendingRequests.delete(response.id);
351
+ pending.resolve(JSON.stringify(response));
352
+ }
353
+ }
354
+ /**
355
+ * Handle notification and dispatch to listeners
356
+ */
357
+ handleNotification(notification) {
358
+ const notificationJson = JSON.stringify(notification);
359
+ for (const callback of this.notificationCallbacks) {
360
+ try {
361
+ callback(notificationJson);
362
+ } catch (error) {
363
+ logger.error("WebSocketTransport notification callback error", {
364
+ error,
365
+ method: notification.method
366
+ });
367
+ }
368
+ }
369
+ }
370
+ }
371
+ const IS_WEBVIEW = isWebViewEnvironment();
372
+ let transportInstance = null;
373
+ function getTransport() {
374
+ if (transportInstance) {
375
+ return transportInstance;
376
+ }
377
+ if (IS_WEBVIEW) {
378
+ transportInstance = new NativeTransport();
379
+ } else {
380
+ const wsUrl = "ws://127.0.0.1:9000";
381
+ transportInstance = new WebSocketTransport({ url: wsUrl });
382
+ }
383
+ return transportInstance;
384
+ }
385
+ const _IpcBridge = class _IpcBridge {
386
+ // Log warning max once per 5s
387
+ constructor() {
388
+ this.nextId = 1;
389
+ this.eventListeners = /* @__PURE__ */ new Map();
390
+ this.transport = null;
391
+ this.isInitialized = false;
392
+ this.lastDisconnectWarning = 0;
393
+ this.DISCONNECT_WARNING_INTERVAL_MS = 5e3;
394
+ }
395
+ /**
396
+ * Initialize the IPC bridge (lazy)
397
+ */
398
+ initialize() {
399
+ if (this.isInitialized) {
400
+ return;
401
+ }
402
+ this.transport = getTransport();
403
+ this.transport.onNotification((notificationJson) => {
404
+ try {
405
+ const parsed = JSON.parse(notificationJson);
406
+ if (isIpcNotification(parsed)) {
407
+ this.handleNotification(parsed);
408
+ }
409
+ } catch (error) {
410
+ logger.error("Failed to parse notification", { error });
411
+ }
412
+ });
413
+ this.isInitialized = true;
414
+ }
415
+ /**
416
+ * Get singleton instance
417
+ */
418
+ static getInstance() {
419
+ _IpcBridge.instance ?? (_IpcBridge.instance = new _IpcBridge());
420
+ return _IpcBridge.instance;
421
+ }
422
+ /**
423
+ * Check if the bridge is connected
424
+ */
425
+ isConnected() {
426
+ var _a;
427
+ this.initialize();
428
+ return ((_a = this.transport) == null ? void 0 : _a.isConnected()) ?? false;
429
+ }
430
+ /**
431
+ * Invoke a method and wait for response
432
+ */
433
+ async invoke(method, params) {
434
+ var _a;
435
+ this.initialize();
436
+ if (!((_a = this.transport) == null ? void 0 : _a.isConnected())) {
437
+ const now = Date.now();
438
+ if (now - this.lastDisconnectWarning > this.DISCONNECT_WARNING_INTERVAL_MS) {
439
+ logger.warn("Transport not connected, call will fail. Waiting for reconnection...");
440
+ this.lastDisconnectWarning = now;
441
+ }
442
+ throw new Error("IpcBridge: Transport not connected");
443
+ }
444
+ const id = this.nextId++;
445
+ const request = {
446
+ jsonrpc: "2.0",
447
+ id,
448
+ method,
449
+ params
450
+ };
451
+ const requestJson = JSON.stringify(request);
452
+ const responseJson = await this.transport.send(requestJson);
453
+ const response = JSON.parse(responseJson);
454
+ if (response.error) {
455
+ throw new Error(`IPC Error ${response.error.code}: ${response.error.message}`);
456
+ }
457
+ return response.result;
458
+ }
459
+ /**
460
+ * Subscribe to notification events
461
+ */
462
+ on(event, callback) {
463
+ this.initialize();
464
+ if (!this.eventListeners.has(event)) {
465
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
466
+ }
467
+ const listeners = this.eventListeners.get(event);
468
+ if (!listeners) {
469
+ throw new Error(`Event listener set not found for event: ${event}`);
470
+ }
471
+ listeners.add(callback);
472
+ return () => {
473
+ listeners.delete(callback);
474
+ };
475
+ }
476
+ /**
477
+ * Handle notification and dispatch to listeners
478
+ */
479
+ handleNotification(notification) {
480
+ const listeners = this.eventListeners.get(notification.method);
481
+ if (!listeners || listeners.size === 0) {
482
+ return;
483
+ }
484
+ for (const listener of listeners) {
485
+ try {
486
+ listener(notification.params);
487
+ } catch (error) {
488
+ logger.error("Error in event listener", { event: notification.method, error });
489
+ }
490
+ }
491
+ }
492
+ };
493
+ _IpcBridge.instance = null;
494
+ let IpcBridge = _IpcBridge;
495
+ const _ParameterClient = class _ParameterClient {
496
+ constructor() {
497
+ this.bridge = IpcBridge.getInstance();
498
+ }
499
+ /**
500
+ * Get singleton instance
501
+ */
502
+ static getInstance() {
503
+ if (!_ParameterClient.instance) {
504
+ _ParameterClient.instance = new _ParameterClient();
505
+ }
506
+ return _ParameterClient.instance;
507
+ }
508
+ /**
509
+ * Get a single parameter's current value and metadata
510
+ */
511
+ async getParameter(id) {
512
+ return this.bridge.invoke(METHOD_GET_PARAMETER, { id });
513
+ }
514
+ /**
515
+ * Set a parameter's value
516
+ * @param id Parameter ID
517
+ * @param value Normalized value [0.0, 1.0]
518
+ */
519
+ async setParameter(id, value) {
520
+ await this.bridge.invoke(METHOD_SET_PARAMETER, {
521
+ id,
522
+ value
523
+ });
524
+ }
525
+ /**
526
+ * Get all parameters with their current values and metadata
527
+ */
528
+ async getAllParameters() {
529
+ const result = await this.bridge.invoke(METHOD_GET_ALL_PARAMETERS);
530
+ return result.parameters;
531
+ }
532
+ /**
533
+ * Test connectivity with Rust backend
534
+ * @returns Roundtrip time in milliseconds
535
+ */
536
+ async ping() {
537
+ const start = performance.now();
538
+ await this.bridge.invoke("ping");
539
+ const end = performance.now();
540
+ return end - start;
541
+ }
542
+ /**
543
+ * Subscribe to parameter change notifications
544
+ * @returns Unsubscribe function
545
+ */
546
+ onParameterChanged(callback) {
547
+ return this.bridge.on(NOTIFICATION_PARAMETER_CHANGED, (data) => {
548
+ if (data && typeof data === "object" && "id" in data && "value" in data) {
549
+ callback(data.id, data.value);
550
+ }
551
+ });
552
+ }
553
+ };
554
+ _ParameterClient.instance = null;
555
+ let ParameterClient = _ParameterClient;
556
+ let client = null;
557
+ function getClient() {
558
+ client ?? (client = ParameterClient.getInstance());
559
+ return client;
560
+ }
561
+ function useParameter(id) {
562
+ const [param, setParam] = useState(null);
563
+ const [isLoading, setIsLoading] = useState(true);
564
+ const [error, setError] = useState(null);
565
+ useEffect(() => {
566
+ let isMounted = true;
567
+ async function loadParameter() {
568
+ try {
569
+ setIsLoading(true);
570
+ setError(null);
571
+ const allParams = await getClient().getAllParameters();
572
+ const foundParam = allParams.find((p) => p.id === id);
573
+ if (isMounted) {
574
+ if (foundParam) {
575
+ setParam(foundParam);
576
+ } else {
577
+ setError(new Error(`Parameter not found: ${id}`));
578
+ }
579
+ }
580
+ } catch (err) {
581
+ if (isMounted) {
582
+ setError(err instanceof Error ? err : new Error(String(err)));
583
+ }
584
+ } finally {
585
+ if (isMounted) {
586
+ setIsLoading(false);
587
+ }
588
+ }
589
+ }
590
+ loadParameter();
591
+ return () => {
592
+ isMounted = false;
593
+ };
594
+ }, [id]);
595
+ useEffect(() => {
596
+ const unsubscribe = getClient().onParameterChanged((changedId, value) => {
597
+ if (changedId === id) {
598
+ setParam((prev) => prev ? { ...prev, value } : null);
599
+ }
600
+ });
601
+ return unsubscribe;
602
+ }, [id]);
603
+ const setValue = useCallback(
604
+ async (value) => {
605
+ try {
606
+ await getClient().setParameter(id, value);
607
+ setParam((prev) => prev ? { ...prev, value } : null);
608
+ } catch (err) {
609
+ setError(err instanceof Error ? err : new Error(String(err)));
610
+ throw err;
611
+ }
612
+ },
613
+ [id]
614
+ );
615
+ return { param, setValue, isLoading, error };
616
+ }
617
+ function useAllParameters() {
618
+ const [params, setParams] = useState([]);
619
+ const [isLoading, setIsLoading] = useState(true);
620
+ const [error, setError] = useState(null);
621
+ const reload = useCallback(async () => {
622
+ try {
623
+ setIsLoading(true);
624
+ setError(null);
625
+ const allParams = await getClient().getAllParameters();
626
+ setParams(allParams);
627
+ } catch (err) {
628
+ setError(err instanceof Error ? err : new Error(String(err)));
629
+ } finally {
630
+ setIsLoading(false);
631
+ }
632
+ }, []);
633
+ useEffect(() => {
634
+ reload();
635
+ }, [reload]);
636
+ useEffect(() => {
637
+ const handleParamChange = (changedId, value) => {
638
+ setParams((prev) => prev.map((p) => p.id === changedId ? { ...p, value } : p));
639
+ };
640
+ const unsubscribe = getClient().onParameterChanged(handleParamChange);
641
+ return unsubscribe;
642
+ }, []);
643
+ return { params, isLoading, error, reload };
644
+ }
645
+ function useLatencyMonitor(intervalMs = 1e3) {
646
+ const [latency, setLatency] = useState(null);
647
+ const [measurements, setMeasurements] = useState([]);
648
+ const bridge = IpcBridge.getInstance();
649
+ useEffect(() => {
650
+ let isMounted = true;
651
+ async function measure() {
652
+ if (!bridge.isConnected()) {
653
+ return;
654
+ }
655
+ try {
656
+ const ms = await getClient().ping();
657
+ if (isMounted) {
658
+ setLatency(ms);
659
+ setMeasurements((prev) => [...prev.slice(-99), ms]);
660
+ }
661
+ } catch (err) {
662
+ logger.debug("Ping failed", { error: err });
663
+ }
664
+ }
665
+ measure();
666
+ const intervalId = setInterval(measure, intervalMs);
667
+ return () => {
668
+ isMounted = false;
669
+ clearInterval(intervalId);
670
+ };
671
+ }, [intervalMs, bridge]);
672
+ const avg = measurements.length > 0 ? measurements.reduce((sum, val) => sum + val, 0) / measurements.length : 0;
673
+ const max = measurements.length > 0 ? Math.max(...measurements) : 0;
674
+ return {
675
+ latency,
676
+ avg,
677
+ max,
678
+ count: measurements.length
679
+ };
680
+ }
681
+ function useParameterGroups(parameters) {
682
+ return useMemo(() => {
683
+ const grouped = /* @__PURE__ */ new Map();
684
+ for (const param of parameters) {
685
+ const groupName = param.group ?? "Parameters";
686
+ const existing = grouped.get(groupName) ?? [];
687
+ existing.push(param);
688
+ grouped.set(groupName, existing);
689
+ }
690
+ const groups = Array.from(grouped.entries()).map(([name, parameters2]) => ({ name, parameters: parameters2 })).sort((a, b) => {
691
+ if (a.name === "Parameters") return -1;
692
+ if (b.name === "Parameters") return 1;
693
+ return a.name.localeCompare(b.name);
694
+ });
695
+ return groups;
696
+ }, [parameters]);
697
+ }
698
+ function useConnectionStatus() {
699
+ const [status, setStatus] = useState(() => {
700
+ const bridge = IpcBridge.getInstance();
701
+ const connected = bridge.isConnected();
702
+ let transport;
703
+ if (isWebViewEnvironment()) {
704
+ transport = "native";
705
+ } else if (connected) {
706
+ transport = "websocket";
707
+ } else {
708
+ transport = "none";
709
+ }
710
+ return { connected, transport };
711
+ });
712
+ useEffect(() => {
713
+ const bridge = IpcBridge.getInstance();
714
+ const intervalId = setInterval(() => {
715
+ const connected = bridge.isConnected();
716
+ let transport;
717
+ if (isWebViewEnvironment()) {
718
+ transport = "native";
719
+ } else if (connected) {
720
+ transport = "websocket";
721
+ } else {
722
+ transport = "none";
723
+ }
724
+ setStatus((prevStatus) => {
725
+ if (prevStatus.connected !== connected || prevStatus.transport !== transport) {
726
+ return { connected, transport };
727
+ }
728
+ return prevStatus;
729
+ });
730
+ }, 1e3);
731
+ return () => {
732
+ clearInterval(intervalId);
733
+ };
734
+ }, []);
735
+ return status;
736
+ }
737
+ async function requestResize(width, height) {
738
+ const bridge = IpcBridge.getInstance();
739
+ const result = await bridge.invoke("requestResize", { width, height });
740
+ return result.accepted;
741
+ }
742
+ function useRequestResize() {
743
+ return requestResize;
744
+ }
745
+ async function getMeterFrame() {
746
+ const bridge = IpcBridge.getInstance();
747
+ const result = await bridge.invoke("getMeterFrame");
748
+ return result.frame;
749
+ }
750
+ export {
751
+ ERROR_INTERNAL,
752
+ ERROR_INVALID_PARAMS,
753
+ ERROR_INVALID_REQUEST,
754
+ ERROR_METHOD_NOT_FOUND,
755
+ ERROR_PARAM_NOT_FOUND,
756
+ ERROR_PARAM_OUT_OF_RANGE,
757
+ ERROR_PARSE,
758
+ IpcBridge,
759
+ LogLevel,
760
+ Logger,
761
+ METHOD_GET_ALL_PARAMETERS,
762
+ METHOD_GET_PARAMETER,
763
+ METHOD_SET_PARAMETER,
764
+ NOTIFICATION_PARAMETER_CHANGED,
765
+ NativeTransport,
766
+ ParameterClient,
767
+ WebSocketTransport,
768
+ dbToLinear,
769
+ getMeterFrame,
770
+ isBrowserEnvironment,
771
+ isWebViewEnvironment,
772
+ linearToDb,
773
+ logger,
774
+ requestResize,
775
+ useAllParameters,
776
+ useConnectionStatus,
777
+ useLatencyMonitor,
778
+ useParameter,
779
+ useParameterGroups,
780
+ useRequestResize
781
+ };
782
+ //# sourceMappingURL=index.js.map