@getlimelight/sdk 0.4.6 → 0.5.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.
package/dist/index.d.mts CHANGED
@@ -7,7 +7,8 @@ declare enum NetworkPhase {
7
7
  CONNECT = "CONNECT",
8
8
  REQUEST = "REQUEST",
9
9
  RESPONSE = "RESPONSE",
10
- ERROR = "ERROR"
10
+ ERROR = "ERROR",
11
+ ABORT = "ABORT"
11
12
  }
12
13
  declare enum BodyFormat {
13
14
  TEXT = "TEXT",
@@ -92,7 +93,7 @@ interface NetworkResponse extends BaseNetworkEvent {
92
93
  * NETWORK ERROR (3rd possible outcome)
93
94
  */
94
95
  interface NetworkErrorEvent extends BaseNetworkEvent {
95
- phase: NetworkPhase.ERROR;
96
+ phase: NetworkPhase.ERROR | NetworkPhase.ABORT;
96
97
  errorMessage: string;
97
98
  stack?: string;
98
99
  }
@@ -210,6 +211,25 @@ declare enum GraphqlOprtation {
210
211
  SUB = "SUBSCRIPTION"
211
212
  }
212
213
 
214
+ declare enum CommandType {
215
+ CLEAR_RENDERS = "CLEAR_RENDERS",
216
+ ACK = "ACK"
217
+ }
218
+ interface BaseCommand {
219
+ type: CommandType;
220
+ id?: string;
221
+ }
222
+ interface ClearRendersCommand extends BaseCommand {
223
+ type: CommandType.CLEAR_RENDERS;
224
+ }
225
+ type Command = ClearRendersCommand;
226
+ interface CommandAckEvent {
227
+ phase: CommandType.ACK;
228
+ commandId: string;
229
+ type: CommandType;
230
+ success: boolean;
231
+ }
232
+
213
233
  /**
214
234
  * Render lifecycle phases
215
235
  */
@@ -450,7 +470,7 @@ interface ConnectionEvent {
450
470
  /**
451
471
  * Union type representing all possible Limelight messages.
452
472
  */
453
- type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent;
473
+ type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent | CommandAckEvent;
454
474
 
455
475
  /**
456
476
  * Represents a single frame in a stack trace.
@@ -512,6 +532,7 @@ declare class LimelightClient {
512
532
  private renderInterceptor;
513
533
  private stateInterceptor;
514
534
  private requestBridge;
535
+ private commandHandler;
515
536
  constructor();
516
537
  /**
517
538
  * Configures the Limelight client with the provided settings.
@@ -612,4 +633,4 @@ declare global {
612
633
  }
613
634
  }
614
635
 
615
- export { type BaseNetworkEvent, BodyFormat, type ConnectEvent, type ConnectionEvent, type ConsoleEvent, ConsoleLevel, ConsoleSource, ConsoleType, EventType, type GraphQLRequest, type GraphQLResponse, GraphqlOprtation, HttpMethod, HttpStatusClass, Limelight, type LimelightConfig, type LimelightEvent, type LimelightMessage, type NetworkErrorEvent, type NetworkEvent, NetworkPhase, type NetworkRequest, type NetworkRequestWithResponse, type NetworkResponse, NetworkType, type ParsedStackTrace, type RequestBridgeConfig, type ResponseBridgeConfig, type SerializedBody, type Session, type StackFrame };
636
+ export { type BaseCommand, type BaseNetworkEvent, BodyFormat, type ClearRendersCommand, type Command, type CommandAckEvent, CommandType, type ConnectEvent, type ConnectionEvent, type ConsoleEvent, ConsoleLevel, ConsoleSource, ConsoleType, EventType, type GraphQLRequest, type GraphQLResponse, GraphqlOprtation, HttpMethod, HttpStatusClass, Limelight, type LimelightConfig, type LimelightEvent, type LimelightMessage, type NetworkErrorEvent, type NetworkEvent, NetworkPhase, type NetworkRequest, type NetworkRequestWithResponse, type NetworkResponse, NetworkType, type ParsedStackTrace, type RequestBridgeConfig, type ResponseBridgeConfig, type SerializedBody, type Session, type StackFrame };
package/dist/index.d.ts CHANGED
@@ -7,7 +7,8 @@ declare enum NetworkPhase {
7
7
  CONNECT = "CONNECT",
8
8
  REQUEST = "REQUEST",
9
9
  RESPONSE = "RESPONSE",
10
- ERROR = "ERROR"
10
+ ERROR = "ERROR",
11
+ ABORT = "ABORT"
11
12
  }
12
13
  declare enum BodyFormat {
13
14
  TEXT = "TEXT",
@@ -92,7 +93,7 @@ interface NetworkResponse extends BaseNetworkEvent {
92
93
  * NETWORK ERROR (3rd possible outcome)
93
94
  */
94
95
  interface NetworkErrorEvent extends BaseNetworkEvent {
95
- phase: NetworkPhase.ERROR;
96
+ phase: NetworkPhase.ERROR | NetworkPhase.ABORT;
96
97
  errorMessage: string;
97
98
  stack?: string;
98
99
  }
@@ -210,6 +211,25 @@ declare enum GraphqlOprtation {
210
211
  SUB = "SUBSCRIPTION"
211
212
  }
212
213
 
214
+ declare enum CommandType {
215
+ CLEAR_RENDERS = "CLEAR_RENDERS",
216
+ ACK = "ACK"
217
+ }
218
+ interface BaseCommand {
219
+ type: CommandType;
220
+ id?: string;
221
+ }
222
+ interface ClearRendersCommand extends BaseCommand {
223
+ type: CommandType.CLEAR_RENDERS;
224
+ }
225
+ type Command = ClearRendersCommand;
226
+ interface CommandAckEvent {
227
+ phase: CommandType.ACK;
228
+ commandId: string;
229
+ type: CommandType;
230
+ success: boolean;
231
+ }
232
+
213
233
  /**
214
234
  * Render lifecycle phases
215
235
  */
@@ -450,7 +470,7 @@ interface ConnectionEvent {
450
470
  /**
451
471
  * Union type representing all possible Limelight messages.
452
472
  */
453
- type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent;
473
+ type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent | CommandAckEvent;
454
474
 
455
475
  /**
456
476
  * Represents a single frame in a stack trace.
@@ -512,6 +532,7 @@ declare class LimelightClient {
512
532
  private renderInterceptor;
513
533
  private stateInterceptor;
514
534
  private requestBridge;
535
+ private commandHandler;
515
536
  constructor();
516
537
  /**
517
538
  * Configures the Limelight client with the provided settings.
@@ -612,4 +633,4 @@ declare global {
612
633
  }
613
634
  }
614
635
 
615
- export { type BaseNetworkEvent, BodyFormat, type ConnectEvent, type ConnectionEvent, type ConsoleEvent, ConsoleLevel, ConsoleSource, ConsoleType, EventType, type GraphQLRequest, type GraphQLResponse, GraphqlOprtation, HttpMethod, HttpStatusClass, Limelight, type LimelightConfig, type LimelightEvent, type LimelightMessage, type NetworkErrorEvent, type NetworkEvent, NetworkPhase, type NetworkRequest, type NetworkRequestWithResponse, type NetworkResponse, NetworkType, type ParsedStackTrace, type RequestBridgeConfig, type ResponseBridgeConfig, type SerializedBody, type Session, type StackFrame };
636
+ export { type BaseCommand, type BaseNetworkEvent, BodyFormat, type ClearRendersCommand, type Command, type CommandAckEvent, CommandType, type ConnectEvent, type ConnectionEvent, type ConsoleEvent, ConsoleLevel, ConsoleSource, ConsoleType, EventType, type GraphQLRequest, type GraphQLResponse, GraphqlOprtation, HttpMethod, HttpStatusClass, Limelight, type LimelightConfig, type LimelightEvent, type LimelightMessage, type NetworkErrorEvent, type NetworkEvent, NetworkPhase, type NetworkRequest, type NetworkRequestWithResponse, type NetworkResponse, NetworkType, type ParsedStackTrace, type RequestBridgeConfig, type ResponseBridgeConfig, type SerializedBody, type Session, type StackFrame };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  BodyFormat: () => BodyFormat,
24
+ CommandType: () => CommandType,
24
25
  ConsoleLevel: () => ConsoleLevel,
25
26
  ConsoleSource: () => ConsoleSource,
26
27
  ConsoleType: () => ConsoleType,
@@ -72,6 +73,7 @@ var NetworkPhase = /* @__PURE__ */ ((NetworkPhase2) => {
72
73
  NetworkPhase2["REQUEST"] = "REQUEST";
73
74
  NetworkPhase2["RESPONSE"] = "RESPONSE";
74
75
  NetworkPhase2["ERROR"] = "ERROR";
76
+ NetworkPhase2["ABORT"] = "ABORT";
75
77
  return NetworkPhase2;
76
78
  })(NetworkPhase || {});
77
79
  var BodyFormat = /* @__PURE__ */ ((BodyFormat2) => {
@@ -118,6 +120,13 @@ var GraphqlOprtation = /* @__PURE__ */ ((GraphqlOprtation2) => {
118
120
  return GraphqlOprtation2;
119
121
  })(GraphqlOprtation || {});
120
122
 
123
+ // src/types/commands.ts
124
+ var CommandType = /* @__PURE__ */ ((CommandType2) => {
125
+ CommandType2["CLEAR_RENDERS"] = "CLEAR_RENDERS";
126
+ CommandType2["ACK"] = "ACK";
127
+ return CommandType2;
128
+ })(CommandType || {});
129
+
121
130
  // src/helpers/detection/detectConsoleType.ts
122
131
  var detectConsoleType = (level, args) => {
123
132
  const messageStr = args.map((arg) => {
@@ -267,7 +276,7 @@ var SENSITIVE_HEADERS = [
267
276
  var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
268
277
  var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
269
278
  var WS_PATH = "/limelight";
270
- var SDK_VERSION = true ? "0.4.6" : "test-version";
279
+ var SDK_VERSION = true ? "0.5.2" : "test-version";
271
280
  var RENDER_THRESHOLDS = {
272
281
  HOT_VELOCITY: 5,
273
282
  HIGH_RENDER_COUNT: 50,
@@ -838,13 +847,14 @@ var NetworkInterceptor = class {
838
847
  } catch (err) {
839
848
  const errorMessage = err instanceof Error ? err.message : String(err);
840
849
  const errorStack = err instanceof Error ? err.stack : void 0;
850
+ const isAbort = err instanceof DOMException && err.name === "AbortError";
841
851
  let errorEvent = {
842
852
  id: requestId,
843
853
  sessionId: self.getSessionId(),
844
854
  timestamp: Date.now(),
845
- phase: "ERROR" /* ERROR */,
855
+ phase: isAbort ? "ABORT" /* ABORT */ : "ERROR" /* ERROR */,
846
856
  networkType: "fetch" /* FETCH */,
847
- errorMessage,
857
+ errorMessage: isAbort ? "Request aborted" : errorMessage,
848
858
  stack: errorStack
849
859
  };
850
860
  if (self.config?.beforeSend) {
@@ -1028,14 +1038,14 @@ var XHRInterceptor = class {
1028
1038
  self.sendMessage(responseEvent);
1029
1039
  cleanup.call(this);
1030
1040
  };
1031
- const sendError = (errorMessage) => {
1041
+ const sendError = (errorMessage, phase = "ERROR" /* ERROR */) => {
1032
1042
  if (responseSent) return;
1033
1043
  responseSent = true;
1034
1044
  let errorEvent = {
1035
1045
  id: data.id,
1036
1046
  sessionId: self.getSessionId(),
1037
1047
  timestamp: Date.now(),
1038
- phase: "ERROR" /* ERROR */,
1048
+ phase,
1039
1049
  networkType: "xhr" /* XHR */,
1040
1050
  errorMessage
1041
1051
  };
@@ -1061,7 +1071,7 @@ var XHRInterceptor = class {
1061
1071
  cleanup.call(this);
1062
1072
  };
1063
1073
  const abortHandler = function() {
1064
- sendError("Request aborted");
1074
+ sendError("Request aborted", "ABORT" /* ABORT */);
1065
1075
  cleanup.call(this);
1066
1076
  };
1067
1077
  const timeoutHandler = function() {
@@ -1162,12 +1172,10 @@ var RenderInterceptor = class {
1162
1172
  }, RENDER_THRESHOLDS.SNAPSHOT_INTERVAL_MS);
1163
1173
  this.isSetup = true;
1164
1174
  }
1165
- resetProfiles() {
1166
- this.profiles.clear();
1167
- this.pendingUnmounts = [];
1168
- this.currentCommitComponents.clear();
1169
- this.componentIdCounter = 0;
1170
- }
1175
+ /**
1176
+ * Installs or wraps the React DevTools global hook.
1177
+ * Returns true if successful, false otherwise.
1178
+ */
1171
1179
  installHook() {
1172
1180
  const globalObj = typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : null;
1173
1181
  if (!globalObj) return false;
@@ -1180,6 +1188,11 @@ var RenderInterceptor = class {
1180
1188
  }
1181
1189
  return true;
1182
1190
  }
1191
+ /**
1192
+ * Wraps an existing React DevTools hook to intercept render events.
1193
+ * Preserves original functionality.
1194
+ * @param hook - The existing React DevTools hook
1195
+ */
1183
1196
  wrapExistingHook(hook) {
1184
1197
  this.originalHook = hook;
1185
1198
  this.originalOnCommitFiberRoot = hook.onCommitFiberRoot?.bind(hook);
@@ -1193,6 +1206,11 @@ var RenderInterceptor = class {
1193
1206
  this.handleCommitFiberUnmount(rendererID, fiber);
1194
1207
  };
1195
1208
  }
1209
+ /**
1210
+ * Creates a new React DevTools hook to intercept render events.
1211
+ * @param globalObj - The global object (window or global)
1212
+ * @param hookKey - The key for the React DevTools hook
1213
+ */
1196
1214
  createHook(globalObj, hookKey) {
1197
1215
  const renderers = /* @__PURE__ */ new Map();
1198
1216
  let rendererIdCounter = 0;
@@ -1215,6 +1233,8 @@ var RenderInterceptor = class {
1215
1233
  /**
1216
1234
  * Handles a fiber root commit - walks tree and ACCUMULATES into profiles.
1217
1235
  * Two-pass: first count components, then accumulate with distributed cost.
1236
+ * @param rendererID - The renderer ID
1237
+ * @param root - The fiber root
1218
1238
  */
1219
1239
  handleCommitFiberRoot(_rendererID, root) {
1220
1240
  this.currentCommitComponents.clear();
@@ -1230,6 +1250,7 @@ var RenderInterceptor = class {
1230
1250
  }
1231
1251
  /**
1232
1252
  * First pass: count rendered components for cost distribution.
1253
+ * @param fiber - The current fiber node
1233
1254
  */
1234
1255
  countRenderedComponents(fiber) {
1235
1256
  if (!fiber) return;
@@ -1239,6 +1260,11 @@ var RenderInterceptor = class {
1239
1260
  this.countRenderedComponents(fiber.child);
1240
1261
  this.countRenderedComponents(fiber.sibling);
1241
1262
  }
1263
+ /**
1264
+ * Handles a fiber unmount - marks component as unmounted.
1265
+ * @param rendererID - The renderer ID
1266
+ * @param fiber - The fiber being unmounted
1267
+ */
1242
1268
  handleCommitFiberUnmount(_rendererID, fiber) {
1243
1269
  if (!this.isUserComponent(fiber)) return;
1244
1270
  const componentId = this.fiberToComponentId.get(fiber);
@@ -1252,20 +1278,28 @@ var RenderInterceptor = class {
1252
1278
  }
1253
1279
  /**
1254
1280
  * Walks fiber tree and accumulates render stats into profiles.
1281
+ * @param fiber - The current fiber node
1282
+ * @param parentComponentId - The parent component ID
1283
+ * @param depth - The current depth in the tree
1255
1284
  */
1256
1285
  walkFiberTree(fiber, parentComponentId, depth) {
1257
1286
  if (!fiber) return;
1287
+ let currentParentId = parentComponentId;
1258
1288
  if (this.isUserComponent(fiber) && this.didFiberRender(fiber)) {
1259
1289
  const componentId = this.getOrCreateComponentId(fiber);
1260
1290
  this.accumulateRender(fiber, componentId, parentComponentId, depth);
1261
1291
  this.currentCommitComponents.add(componentId);
1262
- parentComponentId = componentId;
1292
+ currentParentId = componentId;
1263
1293
  }
1264
- this.walkFiberTree(fiber.child, parentComponentId, depth + 1);
1294
+ this.walkFiberTree(fiber.child, currentParentId, depth + 1);
1265
1295
  this.walkFiberTree(fiber.sibling, parentComponentId, depth);
1266
1296
  }
1267
1297
  /**
1268
1298
  * Core accumulation logic - this is where we build up the profile.
1299
+ * @param fiber - The current fiber node
1300
+ * @param componentId - The component ID
1301
+ * @param parentComponentId - The parent component ID
1302
+ * @param depth - The current depth in the tree
1269
1303
  */
1270
1304
  accumulateRender(fiber, componentId, parentComponentId, depth) {
1271
1305
  const now = Date.now();
@@ -1326,7 +1360,9 @@ var RenderInterceptor = class {
1326
1360
  this.updateSuspiciousFlag(profile);
1327
1361
  }
1328
1362
  /**
1329
- * NEW: Accumulate prop change details into the profile.
1363
+ * Accumulate prop change details into the profile.
1364
+ * @param profile - The component profile
1365
+ * @param changes - The list of prop change details
1330
1366
  */
1331
1367
  accumulatePropChanges(profile, changes) {
1332
1368
  const stats = profile.propChangeStats;
@@ -1355,7 +1391,9 @@ var RenderInterceptor = class {
1355
1391
  }
1356
1392
  }
1357
1393
  /**
1358
- * Build prop change snapshot for emission.
1394
+ * Build prop change snapshot for emission.
1395
+ * @param profile - The component profile
1396
+ * @returns The prop change snapshot or undefined
1359
1397
  */
1360
1398
  buildPropChangeSnapshot(profile) {
1361
1399
  const stats = profile.propChangeStats;
@@ -1373,6 +1411,10 @@ var RenderInterceptor = class {
1373
1411
  });
1374
1412
  return { topChangedProps };
1375
1413
  }
1414
+ /**
1415
+ * Updates the suspicious flag based on render velocity and count.
1416
+ * @param profile - The component profile
1417
+ */
1376
1418
  updateSuspiciousFlag(profile) {
1377
1419
  const velocity = this.calculateVelocity(profile);
1378
1420
  if (velocity > RENDER_THRESHOLDS.HOT_VELOCITY) {
@@ -1391,6 +1433,8 @@ var RenderInterceptor = class {
1391
1433
  /**
1392
1434
  * Calculates renders per second from velocity window.
1393
1435
  * Cheap: just count / window duration, no array operations.
1436
+ * @param profile - The component profile
1437
+ * @returns The calculated velocity
1394
1438
  */
1395
1439
  calculateVelocity(profile) {
1396
1440
  const now = Date.now();
@@ -1403,6 +1447,8 @@ var RenderInterceptor = class {
1403
1447
  }
1404
1448
  /**
1405
1449
  * Emits a snapshot of all profiles with deltas.
1450
+ * Only emits profiles that have significant changes since last emit.
1451
+ * Also emits unmounts.
1406
1452
  */
1407
1453
  emitSnapshot() {
1408
1454
  const now = Date.now();
@@ -1486,7 +1532,11 @@ var RenderInterceptor = class {
1486
1532
  this.sendMessage(message);
1487
1533
  }
1488
1534
  /**
1489
- * Now returns prop change details when applicable.
1535
+ * Now returns prop change details when applicable.
1536
+ * Infers the cause of the render by comparing current and previous fiber states.
1537
+ * @param fiber - The current fiber node
1538
+ * @param parentComponentId - The parent component ID
1539
+ * @returns The inferred render cause
1490
1540
  */
1491
1541
  inferRenderCause(fiber, parentComponentId) {
1492
1542
  const alternate = fiber.alternate;
@@ -1535,6 +1585,9 @@ var RenderInterceptor = class {
1535
1585
  /**
1536
1586
  * Diff props to find which keys changed and whether it's reference-only.
1537
1587
  * This is the key insight generator.
1588
+ * @param prevProps - The previous props
1589
+ * @param nextProps - The next props
1590
+ * @returns List of prop change details
1538
1591
  */
1539
1592
  diffProps(prevProps, nextProps) {
1540
1593
  if (!prevProps || !nextProps) {
@@ -1562,8 +1615,11 @@ var RenderInterceptor = class {
1562
1615
  return changes;
1563
1616
  }
1564
1617
  /**
1565
- * Shallow equality check to determine if a prop is reference-only change.
1618
+ * Shallow equality check to determine if a prop is reference-only change.
1566
1619
  * We only go one level deep to keep it fast.
1620
+ * @param a - The first value
1621
+ * @param b - The second value
1622
+ * @returns True if shallow equal, false otherwise
1567
1623
  */
1568
1624
  isShallowEqual(a, b) {
1569
1625
  if (a === b) return true;
@@ -1590,13 +1646,28 @@ var RenderInterceptor = class {
1590
1646
  }
1591
1647
  return a === b;
1592
1648
  }
1649
+ /**
1650
+ * Determines if a fiber represents a user-defined component.
1651
+ * @param fiber - The fiber node
1652
+ * @returns True if user component, false otherwise
1653
+ */
1593
1654
  isUserComponent(fiber) {
1594
1655
  const tag = fiber.tag;
1595
1656
  return tag === 0 /* FunctionComponent */ || tag === 1 /* ClassComponent */ || tag === 11 /* ForwardRef */ || tag === 14 /* MemoComponent */ || tag === 15 /* SimpleMemoComponent */;
1596
1657
  }
1658
+ /**
1659
+ * Determines if a fiber performed work during the commit.
1660
+ * @param fiber - The fiber node
1661
+ * @returns True if performed work, false otherwise
1662
+ */
1597
1663
  didFiberRender(fiber) {
1598
1664
  return (fiber.flags & 1 /* PerformedWork */) !== 0;
1599
1665
  }
1666
+ /**
1667
+ * Gets or creates a unique component ID for a fiber.
1668
+ * @param fiber - The fiber node
1669
+ * @returns The unique component ID
1670
+ */
1600
1671
  getOrCreateComponentId(fiber) {
1601
1672
  let id = this.fiberToComponentId.get(fiber);
1602
1673
  if (id) return id;
@@ -1611,6 +1682,11 @@ var RenderInterceptor = class {
1611
1682
  this.fiberToComponentId.set(fiber, id);
1612
1683
  return id;
1613
1684
  }
1685
+ /**
1686
+ * Gets the display name of a component from a fiber.
1687
+ * @param fiber - The fiber node
1688
+ * @returns The component name
1689
+ */
1614
1690
  getComponentName(fiber) {
1615
1691
  const type = fiber.type;
1616
1692
  if (!type) return "Unknown";
@@ -1628,6 +1704,11 @@ var RenderInterceptor = class {
1628
1704
  }
1629
1705
  return "Unknown";
1630
1706
  }
1707
+ /**
1708
+ * Gets the component type from a fiber.
1709
+ * @param fiber - The fiber node
1710
+ * @returns The component type
1711
+ */
1631
1712
  getComponentType(fiber) {
1632
1713
  switch (fiber.tag) {
1633
1714
  case 0 /* FunctionComponent */:
@@ -1661,6 +1742,9 @@ var RenderInterceptor = class {
1661
1742
  getSuspiciousComponents() {
1662
1743
  return Array.from(this.profiles.values()).filter((p) => p.isSuspicious);
1663
1744
  }
1745
+ /**
1746
+ * Cleans up and restores original hook behavior.
1747
+ */
1664
1748
  cleanup() {
1665
1749
  if (!this.isSetup) return;
1666
1750
  this.emitSnapshot();
@@ -1686,6 +1770,16 @@ var RenderInterceptor = class {
1686
1770
  this.config = null;
1687
1771
  this.isSetup = false;
1688
1772
  }
1773
+ /**
1774
+ * Resets all collected profiles
1775
+ */
1776
+ resetProfiles() {
1777
+ this.profiles.clear();
1778
+ this.fiberToComponentId = /* @__PURE__ */ new WeakMap();
1779
+ this.pendingUnmounts = [];
1780
+ this.currentCommitComponents.clear();
1781
+ this.componentIdCounter = 0;
1782
+ }
1689
1783
  };
1690
1784
 
1691
1785
  // src/limelight/interceptors/StateInterceptor.ts
@@ -1724,7 +1818,7 @@ var StateInterceptor = class {
1724
1818
  );
1725
1819
  return;
1726
1820
  }
1727
- const state = this.getState(store, library);
1821
+ const state = this.getState(store);
1728
1822
  const initEvent = {
1729
1823
  phase: "STATE:INIT" /* INIT */,
1730
1824
  sessionId: this.getSessionId(),
@@ -1741,6 +1835,8 @@ var StateInterceptor = class {
1741
1835
  }
1742
1836
  /**
1743
1837
  * Unregister a store and stop listening to changes.
1838
+ * Can be called manually via Limelight.removeStore().
1839
+ * @param name The name of the store to unregister
1744
1840
  */
1745
1841
  unregisterStore(name) {
1746
1842
  const store = this.stores.get(name);
@@ -1751,6 +1847,7 @@ var StateInterceptor = class {
1751
1847
  }
1752
1848
  /**
1753
1849
  * Emit an event, applying beforeSend hook if configured
1850
+ * @param event The event to emit
1754
1851
  */
1755
1852
  emitEvent(event) {
1756
1853
  if (this.config?.beforeSend) {
@@ -1769,6 +1866,8 @@ var StateInterceptor = class {
1769
1866
  }
1770
1867
  /**
1771
1868
  * Detect whether a store is Zustand or Redux
1869
+ * @param store The store to inspect
1870
+ * @return StateLibrary or null if unknown
1772
1871
  */
1773
1872
  detectLibrary(store) {
1774
1873
  if (!store || typeof store !== "function" && typeof store !== "object") {
@@ -1787,13 +1886,19 @@ var StateInterceptor = class {
1787
1886
  }
1788
1887
  /**
1789
1888
  * Get current state from a store
1889
+ * @param store The store to get state from
1890
+ * @return The current state
1790
1891
  */
1791
- getState(store, library) {
1892
+ getState(store) {
1792
1893
  const storeAny = store;
1793
1894
  return storeAny.getState();
1794
1895
  }
1795
1896
  /**
1796
1897
  * Subscribe to store changes
1898
+ * @param store The store to subscribe to
1899
+ * @param library The detected state library
1900
+ * @param storeName The name of the store
1901
+ * @return Unsubscribe function
1797
1902
  */
1798
1903
  subscribe(store, library, storeName) {
1799
1904
  const storeAny = store;
@@ -1805,6 +1910,9 @@ var StateInterceptor = class {
1805
1910
  }
1806
1911
  /**
1807
1912
  * Subscribe to Zustand store changes
1913
+ * @param store The Zustand store
1914
+ * @param storeName The name of the store
1915
+ * @return Unsubscribe function
1808
1916
  */
1809
1917
  subscribeZustand(store, storeName) {
1810
1918
  return store.subscribe((state, prevState) => {
@@ -1827,6 +1935,9 @@ var StateInterceptor = class {
1827
1935
  }
1828
1936
  /**
1829
1937
  * Subscribe to Redux store changes
1938
+ * @param store The Redux store
1939
+ * @param storeName The name of the store
1940
+ * @return Unsubscribe function
1830
1941
  */
1831
1942
  subscribeRedux(store, storeName) {
1832
1943
  let lastAction = { type: "@@INIT" };
@@ -1862,6 +1973,9 @@ var StateInterceptor = class {
1862
1973
  }
1863
1974
  /**
1864
1975
  * Infer action name from stack trace for Zustand
1976
+ * @param state The new state
1977
+ * @param prevState The previous state
1978
+ * @return Inferred StateAction
1865
1979
  */
1866
1980
  inferZustandAction(state, prevState) {
1867
1981
  const actionType = this.parseActionFromStack(this.captureStackTrace());
@@ -1873,6 +1987,8 @@ var StateInterceptor = class {
1873
1987
  }
1874
1988
  /**
1875
1989
  * Parse function name from stack trace
1990
+ * @param stack The stack trace string
1991
+ * @return The inferred action name
1876
1992
  */
1877
1993
  parseActionFromStack(stack) {
1878
1994
  if (!stack) return "set";
@@ -1903,6 +2019,9 @@ var StateInterceptor = class {
1903
2019
  }
1904
2020
  /**
1905
2021
  * Compute what keys changed between states (shallow)
2022
+ * @param state The new state
2023
+ * @param prevState The previous state
2024
+ * @return Partial state with only changed keys
1906
2025
  */
1907
2026
  computePartialState(state, prevState) {
1908
2027
  if (typeof state !== "object" || state === null || typeof prevState !== "object" || prevState === null) {
@@ -1943,7 +2062,7 @@ var StateInterceptor = class {
1943
2062
  }
1944
2063
  };
1945
2064
 
1946
- // src/limelight/interceptors/RequestBridge.ts
2065
+ // src/limelight/bridges/RequestBridge.ts
1947
2066
  var RequestBridge = class {
1948
2067
  constructor(sendMessage, getSessionId) {
1949
2068
  this.sendMessage = sendMessage;
@@ -2110,6 +2229,42 @@ var RequestBridge = class {
2110
2229
  }
2111
2230
  };
2112
2231
 
2232
+ // src/limelight/handlers/CommandHandler.ts
2233
+ var CommandHandler = class {
2234
+ constructor(interceptors, sendMessage, getConfig) {
2235
+ this.interceptors = interceptors;
2236
+ this.sendMessage = sendMessage;
2237
+ this.getConfig = getConfig;
2238
+ }
2239
+ /**
2240
+ * Handles an incoming command.
2241
+ * @param command - The command to handle
2242
+ */
2243
+ handle(command) {
2244
+ const config = this.getConfig();
2245
+ if (config?.enableInternalLogging) {
2246
+ console.log("[Limelight] Received command:", command.type);
2247
+ }
2248
+ switch (command.type) {
2249
+ case "CLEAR_RENDERS" /* CLEAR_RENDERS */:
2250
+ this.interceptors.render.resetProfiles();
2251
+ break;
2252
+ default:
2253
+ if (config?.enableInternalLogging) {
2254
+ console.warn("[Limelight] Unknown command:", command.type);
2255
+ }
2256
+ }
2257
+ if (command.id) {
2258
+ this.sendMessage({
2259
+ phase: "ACK" /* ACK */,
2260
+ commandId: command.id,
2261
+ type: command.type,
2262
+ success: true
2263
+ });
2264
+ }
2265
+ }
2266
+ };
2267
+
2113
2268
  // src/limelight/LimelightClient.ts
2114
2269
  var LimelightClient = class {
2115
2270
  ws = null;
@@ -2127,6 +2282,7 @@ var LimelightClient = class {
2127
2282
  renderInterceptor;
2128
2283
  stateInterceptor;
2129
2284
  requestBridge;
2285
+ commandHandler = null;
2130
2286
  constructor() {
2131
2287
  this.networkInterceptor = new NetworkInterceptor(
2132
2288
  this.sendMessage.bind(this),
@@ -2152,6 +2308,11 @@ var LimelightClient = class {
2152
2308
  this.sendMessage.bind(this),
2153
2309
  () => this.sessionId
2154
2310
  );
2311
+ this.commandHandler = new CommandHandler(
2312
+ { render: this.renderInterceptor },
2313
+ this.sendMessage.bind(this),
2314
+ () => this.config
2315
+ );
2155
2316
  }
2156
2317
  /**
2157
2318
  * Configures the Limelight client with the provided settings.
@@ -2258,6 +2419,16 @@ var LimelightClient = class {
2258
2419
  this.flushMessageQueue();
2259
2420
  this.sendMessage(message);
2260
2421
  };
2422
+ this.ws.onmessage = (event) => {
2423
+ try {
2424
+ const command = JSON.parse(event.data);
2425
+ this.commandHandler?.handle(command);
2426
+ } catch (error) {
2427
+ if (this.config?.enableInternalLogging) {
2428
+ console.error("[Limelight] Failed to parse command:", error);
2429
+ }
2430
+ }
2431
+ };
2261
2432
  this.ws.onerror = (error) => {
2262
2433
  if (this.config?.enableInternalLogging) {
2263
2434
  console.error("[Limelight] WebSocket error:", error);
@@ -2437,6 +2608,7 @@ var Limelight = new LimelightClient();
2437
2608
  // Annotate the CommonJS export names for ESM import in node:
2438
2609
  0 && (module.exports = {
2439
2610
  BodyFormat,
2611
+ CommandType,
2440
2612
  ConsoleLevel,
2441
2613
  ConsoleSource,
2442
2614
  ConsoleType,