@getlimelight/sdk 0.4.6 → 0.5.1

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
@@ -210,6 +210,17 @@ declare enum GraphqlOprtation {
210
210
  SUB = "SUBSCRIPTION"
211
211
  }
212
212
 
213
+ declare enum CommandType {
214
+ CLEAR_RENDERS = "CLEAR_RENDERS",
215
+ ACK = "ACK"
216
+ }
217
+ interface CommandAckEvent {
218
+ phase: CommandType.ACK;
219
+ commandId: string;
220
+ type: CommandType;
221
+ success: boolean;
222
+ }
223
+
213
224
  /**
214
225
  * Render lifecycle phases
215
226
  */
@@ -450,7 +461,7 @@ interface ConnectionEvent {
450
461
  /**
451
462
  * Union type representing all possible Limelight messages.
452
463
  */
453
- type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent;
464
+ type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent | CommandAckEvent;
454
465
 
455
466
  /**
456
467
  * Represents a single frame in a stack trace.
@@ -512,6 +523,7 @@ declare class LimelightClient {
512
523
  private renderInterceptor;
513
524
  private stateInterceptor;
514
525
  private requestBridge;
526
+ private commandHandler;
515
527
  constructor();
516
528
  /**
517
529
  * Configures the Limelight client with the provided settings.
package/dist/index.d.ts CHANGED
@@ -210,6 +210,17 @@ declare enum GraphqlOprtation {
210
210
  SUB = "SUBSCRIPTION"
211
211
  }
212
212
 
213
+ declare enum CommandType {
214
+ CLEAR_RENDERS = "CLEAR_RENDERS",
215
+ ACK = "ACK"
216
+ }
217
+ interface CommandAckEvent {
218
+ phase: CommandType.ACK;
219
+ commandId: string;
220
+ type: CommandType;
221
+ success: boolean;
222
+ }
223
+
213
224
  /**
214
225
  * Render lifecycle phases
215
226
  */
@@ -450,7 +461,7 @@ interface ConnectionEvent {
450
461
  /**
451
462
  * Union type representing all possible Limelight messages.
452
463
  */
453
- type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent;
464
+ type LimelightMessage = NetworkRequest | NetworkResponse | NetworkErrorEvent | ConsoleEvent | ConnectionEvent | RenderSnapshot | TransactionEvent | StateInitEvent | StateUpdateEvent | CommandAckEvent;
454
465
 
455
466
  /**
456
467
  * Represents a single frame in a stack trace.
@@ -512,6 +523,7 @@ declare class LimelightClient {
512
523
  private renderInterceptor;
513
524
  private stateInterceptor;
514
525
  private requestBridge;
526
+ private commandHandler;
515
527
  constructor();
516
528
  /**
517
529
  * Configures the Limelight client with the provided settings.
package/dist/index.js CHANGED
@@ -267,7 +267,7 @@ var SENSITIVE_HEADERS = [
267
267
  var LIMELIGHT_WEB_WSS_URL = "wss://api.getlimelight.io";
268
268
  var LIMELIGHT_DESKTOP_WSS_URL = "ws://localhost:8484";
269
269
  var WS_PATH = "/limelight";
270
- var SDK_VERSION = true ? "0.4.6" : "test-version";
270
+ var SDK_VERSION = true ? "0.5.1" : "test-version";
271
271
  var RENDER_THRESHOLDS = {
272
272
  HOT_VELOCITY: 5,
273
273
  HIGH_RENDER_COUNT: 50,
@@ -1162,12 +1162,10 @@ var RenderInterceptor = class {
1162
1162
  }, RENDER_THRESHOLDS.SNAPSHOT_INTERVAL_MS);
1163
1163
  this.isSetup = true;
1164
1164
  }
1165
- resetProfiles() {
1166
- this.profiles.clear();
1167
- this.pendingUnmounts = [];
1168
- this.currentCommitComponents.clear();
1169
- this.componentIdCounter = 0;
1170
- }
1165
+ /**
1166
+ * Installs or wraps the React DevTools global hook.
1167
+ * Returns true if successful, false otherwise.
1168
+ */
1171
1169
  installHook() {
1172
1170
  const globalObj = typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : null;
1173
1171
  if (!globalObj) return false;
@@ -1180,6 +1178,11 @@ var RenderInterceptor = class {
1180
1178
  }
1181
1179
  return true;
1182
1180
  }
1181
+ /**
1182
+ * Wraps an existing React DevTools hook to intercept render events.
1183
+ * Preserves original functionality.
1184
+ * @param hook - The existing React DevTools hook
1185
+ */
1183
1186
  wrapExistingHook(hook) {
1184
1187
  this.originalHook = hook;
1185
1188
  this.originalOnCommitFiberRoot = hook.onCommitFiberRoot?.bind(hook);
@@ -1193,6 +1196,11 @@ var RenderInterceptor = class {
1193
1196
  this.handleCommitFiberUnmount(rendererID, fiber);
1194
1197
  };
1195
1198
  }
1199
+ /**
1200
+ * Creates a new React DevTools hook to intercept render events.
1201
+ * @param globalObj - The global object (window or global)
1202
+ * @param hookKey - The key for the React DevTools hook
1203
+ */
1196
1204
  createHook(globalObj, hookKey) {
1197
1205
  const renderers = /* @__PURE__ */ new Map();
1198
1206
  let rendererIdCounter = 0;
@@ -1215,6 +1223,8 @@ var RenderInterceptor = class {
1215
1223
  /**
1216
1224
  * Handles a fiber root commit - walks tree and ACCUMULATES into profiles.
1217
1225
  * Two-pass: first count components, then accumulate with distributed cost.
1226
+ * @param rendererID - The renderer ID
1227
+ * @param root - The fiber root
1218
1228
  */
1219
1229
  handleCommitFiberRoot(_rendererID, root) {
1220
1230
  this.currentCommitComponents.clear();
@@ -1230,6 +1240,7 @@ var RenderInterceptor = class {
1230
1240
  }
1231
1241
  /**
1232
1242
  * First pass: count rendered components for cost distribution.
1243
+ * @param fiber - The current fiber node
1233
1244
  */
1234
1245
  countRenderedComponents(fiber) {
1235
1246
  if (!fiber) return;
@@ -1239,6 +1250,11 @@ var RenderInterceptor = class {
1239
1250
  this.countRenderedComponents(fiber.child);
1240
1251
  this.countRenderedComponents(fiber.sibling);
1241
1252
  }
1253
+ /**
1254
+ * Handles a fiber unmount - marks component as unmounted.
1255
+ * @param rendererID - The renderer ID
1256
+ * @param fiber - The fiber being unmounted
1257
+ */
1242
1258
  handleCommitFiberUnmount(_rendererID, fiber) {
1243
1259
  if (!this.isUserComponent(fiber)) return;
1244
1260
  const componentId = this.fiberToComponentId.get(fiber);
@@ -1252,6 +1268,9 @@ var RenderInterceptor = class {
1252
1268
  }
1253
1269
  /**
1254
1270
  * Walks fiber tree and accumulates render stats into profiles.
1271
+ * @param fiber - The current fiber node
1272
+ * @param parentComponentId - The parent component ID
1273
+ * @param depth - The current depth in the tree
1255
1274
  */
1256
1275
  walkFiberTree(fiber, parentComponentId, depth) {
1257
1276
  if (!fiber) return;
@@ -1266,6 +1285,10 @@ var RenderInterceptor = class {
1266
1285
  }
1267
1286
  /**
1268
1287
  * Core accumulation logic - this is where we build up the profile.
1288
+ * @param fiber - The current fiber node
1289
+ * @param componentId - The component ID
1290
+ * @param parentComponentId - The parent component ID
1291
+ * @param depth - The current depth in the tree
1269
1292
  */
1270
1293
  accumulateRender(fiber, componentId, parentComponentId, depth) {
1271
1294
  const now = Date.now();
@@ -1326,7 +1349,9 @@ var RenderInterceptor = class {
1326
1349
  this.updateSuspiciousFlag(profile);
1327
1350
  }
1328
1351
  /**
1329
- * NEW: Accumulate prop change details into the profile.
1352
+ * Accumulate prop change details into the profile.
1353
+ * @param profile - The component profile
1354
+ * @param changes - The list of prop change details
1330
1355
  */
1331
1356
  accumulatePropChanges(profile, changes) {
1332
1357
  const stats = profile.propChangeStats;
@@ -1355,7 +1380,9 @@ var RenderInterceptor = class {
1355
1380
  }
1356
1381
  }
1357
1382
  /**
1358
- * Build prop change snapshot for emission.
1383
+ * Build prop change snapshot for emission.
1384
+ * @param profile - The component profile
1385
+ * @returns The prop change snapshot or undefined
1359
1386
  */
1360
1387
  buildPropChangeSnapshot(profile) {
1361
1388
  const stats = profile.propChangeStats;
@@ -1373,6 +1400,10 @@ var RenderInterceptor = class {
1373
1400
  });
1374
1401
  return { topChangedProps };
1375
1402
  }
1403
+ /**
1404
+ * Updates the suspicious flag based on render velocity and count.
1405
+ * @param profile - The component profile
1406
+ */
1376
1407
  updateSuspiciousFlag(profile) {
1377
1408
  const velocity = this.calculateVelocity(profile);
1378
1409
  if (velocity > RENDER_THRESHOLDS.HOT_VELOCITY) {
@@ -1391,6 +1422,8 @@ var RenderInterceptor = class {
1391
1422
  /**
1392
1423
  * Calculates renders per second from velocity window.
1393
1424
  * Cheap: just count / window duration, no array operations.
1425
+ * @param profile - The component profile
1426
+ * @returns The calculated velocity
1394
1427
  */
1395
1428
  calculateVelocity(profile) {
1396
1429
  const now = Date.now();
@@ -1403,6 +1436,8 @@ var RenderInterceptor = class {
1403
1436
  }
1404
1437
  /**
1405
1438
  * Emits a snapshot of all profiles with deltas.
1439
+ * Only emits profiles that have significant changes since last emit.
1440
+ * Also emits unmounts.
1406
1441
  */
1407
1442
  emitSnapshot() {
1408
1443
  const now = Date.now();
@@ -1486,7 +1521,11 @@ var RenderInterceptor = class {
1486
1521
  this.sendMessage(message);
1487
1522
  }
1488
1523
  /**
1489
- * Now returns prop change details when applicable.
1524
+ * Now returns prop change details when applicable.
1525
+ * Infers the cause of the render by comparing current and previous fiber states.
1526
+ * @param fiber - The current fiber node
1527
+ * @param parentComponentId - The parent component ID
1528
+ * @returns The inferred render cause
1490
1529
  */
1491
1530
  inferRenderCause(fiber, parentComponentId) {
1492
1531
  const alternate = fiber.alternate;
@@ -1535,6 +1574,9 @@ var RenderInterceptor = class {
1535
1574
  /**
1536
1575
  * Diff props to find which keys changed and whether it's reference-only.
1537
1576
  * This is the key insight generator.
1577
+ * @param prevProps - The previous props
1578
+ * @param nextProps - The next props
1579
+ * @returns List of prop change details
1538
1580
  */
1539
1581
  diffProps(prevProps, nextProps) {
1540
1582
  if (!prevProps || !nextProps) {
@@ -1562,8 +1604,11 @@ var RenderInterceptor = class {
1562
1604
  return changes;
1563
1605
  }
1564
1606
  /**
1565
- * Shallow equality check to determine if a prop is reference-only change.
1607
+ * Shallow equality check to determine if a prop is reference-only change.
1566
1608
  * We only go one level deep to keep it fast.
1609
+ * @param a - The first value
1610
+ * @param b - The second value
1611
+ * @returns True if shallow equal, false otherwise
1567
1612
  */
1568
1613
  isShallowEqual(a, b) {
1569
1614
  if (a === b) return true;
@@ -1590,13 +1635,28 @@ var RenderInterceptor = class {
1590
1635
  }
1591
1636
  return a === b;
1592
1637
  }
1638
+ /**
1639
+ * Determines if a fiber represents a user-defined component.
1640
+ * @param fiber - The fiber node
1641
+ * @returns True if user component, false otherwise
1642
+ */
1593
1643
  isUserComponent(fiber) {
1594
1644
  const tag = fiber.tag;
1595
1645
  return tag === 0 /* FunctionComponent */ || tag === 1 /* ClassComponent */ || tag === 11 /* ForwardRef */ || tag === 14 /* MemoComponent */ || tag === 15 /* SimpleMemoComponent */;
1596
1646
  }
1647
+ /**
1648
+ * Determines if a fiber performed work during the commit.
1649
+ * @param fiber - The fiber node
1650
+ * @returns True if performed work, false otherwise
1651
+ */
1597
1652
  didFiberRender(fiber) {
1598
1653
  return (fiber.flags & 1 /* PerformedWork */) !== 0;
1599
1654
  }
1655
+ /**
1656
+ * Gets or creates a unique component ID for a fiber.
1657
+ * @param fiber - The fiber node
1658
+ * @returns The unique component ID
1659
+ */
1600
1660
  getOrCreateComponentId(fiber) {
1601
1661
  let id = this.fiberToComponentId.get(fiber);
1602
1662
  if (id) return id;
@@ -1611,6 +1671,11 @@ var RenderInterceptor = class {
1611
1671
  this.fiberToComponentId.set(fiber, id);
1612
1672
  return id;
1613
1673
  }
1674
+ /**
1675
+ * Gets the display name of a component from a fiber.
1676
+ * @param fiber - The fiber node
1677
+ * @returns The component name
1678
+ */
1614
1679
  getComponentName(fiber) {
1615
1680
  const type = fiber.type;
1616
1681
  if (!type) return "Unknown";
@@ -1628,6 +1693,11 @@ var RenderInterceptor = class {
1628
1693
  }
1629
1694
  return "Unknown";
1630
1695
  }
1696
+ /**
1697
+ * Gets the component type from a fiber.
1698
+ * @param fiber - The fiber node
1699
+ * @returns The component type
1700
+ */
1631
1701
  getComponentType(fiber) {
1632
1702
  switch (fiber.tag) {
1633
1703
  case 0 /* FunctionComponent */:
@@ -1661,6 +1731,9 @@ var RenderInterceptor = class {
1661
1731
  getSuspiciousComponents() {
1662
1732
  return Array.from(this.profiles.values()).filter((p) => p.isSuspicious);
1663
1733
  }
1734
+ /**
1735
+ * Cleans up and restores original hook behavior.
1736
+ */
1664
1737
  cleanup() {
1665
1738
  if (!this.isSetup) return;
1666
1739
  this.emitSnapshot();
@@ -1686,6 +1759,16 @@ var RenderInterceptor = class {
1686
1759
  this.config = null;
1687
1760
  this.isSetup = false;
1688
1761
  }
1762
+ /**
1763
+ * Resets all collected profiles
1764
+ */
1765
+ resetProfiles() {
1766
+ this.profiles.clear();
1767
+ this.fiberToComponentId = /* @__PURE__ */ new WeakMap();
1768
+ this.pendingUnmounts = [];
1769
+ this.currentCommitComponents.clear();
1770
+ this.componentIdCounter = 0;
1771
+ }
1689
1772
  };
1690
1773
 
1691
1774
  // src/limelight/interceptors/StateInterceptor.ts
@@ -1724,7 +1807,7 @@ var StateInterceptor = class {
1724
1807
  );
1725
1808
  return;
1726
1809
  }
1727
- const state = this.getState(store, library);
1810
+ const state = this.getState(store);
1728
1811
  const initEvent = {
1729
1812
  phase: "STATE:INIT" /* INIT */,
1730
1813
  sessionId: this.getSessionId(),
@@ -1741,6 +1824,8 @@ var StateInterceptor = class {
1741
1824
  }
1742
1825
  /**
1743
1826
  * Unregister a store and stop listening to changes.
1827
+ * Can be called manually via Limelight.removeStore().
1828
+ * @param name The name of the store to unregister
1744
1829
  */
1745
1830
  unregisterStore(name) {
1746
1831
  const store = this.stores.get(name);
@@ -1751,6 +1836,7 @@ var StateInterceptor = class {
1751
1836
  }
1752
1837
  /**
1753
1838
  * Emit an event, applying beforeSend hook if configured
1839
+ * @param event The event to emit
1754
1840
  */
1755
1841
  emitEvent(event) {
1756
1842
  if (this.config?.beforeSend) {
@@ -1769,6 +1855,8 @@ var StateInterceptor = class {
1769
1855
  }
1770
1856
  /**
1771
1857
  * Detect whether a store is Zustand or Redux
1858
+ * @param store The store to inspect
1859
+ * @return StateLibrary or null if unknown
1772
1860
  */
1773
1861
  detectLibrary(store) {
1774
1862
  if (!store || typeof store !== "function" && typeof store !== "object") {
@@ -1787,13 +1875,19 @@ var StateInterceptor = class {
1787
1875
  }
1788
1876
  /**
1789
1877
  * Get current state from a store
1878
+ * @param store The store to get state from
1879
+ * @return The current state
1790
1880
  */
1791
- getState(store, library) {
1881
+ getState(store) {
1792
1882
  const storeAny = store;
1793
1883
  return storeAny.getState();
1794
1884
  }
1795
1885
  /**
1796
1886
  * Subscribe to store changes
1887
+ * @param store The store to subscribe to
1888
+ * @param library The detected state library
1889
+ * @param storeName The name of the store
1890
+ * @return Unsubscribe function
1797
1891
  */
1798
1892
  subscribe(store, library, storeName) {
1799
1893
  const storeAny = store;
@@ -1805,6 +1899,9 @@ var StateInterceptor = class {
1805
1899
  }
1806
1900
  /**
1807
1901
  * Subscribe to Zustand store changes
1902
+ * @param store The Zustand store
1903
+ * @param storeName The name of the store
1904
+ * @return Unsubscribe function
1808
1905
  */
1809
1906
  subscribeZustand(store, storeName) {
1810
1907
  return store.subscribe((state, prevState) => {
@@ -1827,6 +1924,9 @@ var StateInterceptor = class {
1827
1924
  }
1828
1925
  /**
1829
1926
  * Subscribe to Redux store changes
1927
+ * @param store The Redux store
1928
+ * @param storeName The name of the store
1929
+ * @return Unsubscribe function
1830
1930
  */
1831
1931
  subscribeRedux(store, storeName) {
1832
1932
  let lastAction = { type: "@@INIT" };
@@ -1862,6 +1962,9 @@ var StateInterceptor = class {
1862
1962
  }
1863
1963
  /**
1864
1964
  * Infer action name from stack trace for Zustand
1965
+ * @param state The new state
1966
+ * @param prevState The previous state
1967
+ * @return Inferred StateAction
1865
1968
  */
1866
1969
  inferZustandAction(state, prevState) {
1867
1970
  const actionType = this.parseActionFromStack(this.captureStackTrace());
@@ -1873,6 +1976,8 @@ var StateInterceptor = class {
1873
1976
  }
1874
1977
  /**
1875
1978
  * Parse function name from stack trace
1979
+ * @param stack The stack trace string
1980
+ * @return The inferred action name
1876
1981
  */
1877
1982
  parseActionFromStack(stack) {
1878
1983
  if (!stack) return "set";
@@ -1903,6 +2008,9 @@ var StateInterceptor = class {
1903
2008
  }
1904
2009
  /**
1905
2010
  * Compute what keys changed between states (shallow)
2011
+ * @param state The new state
2012
+ * @param prevState The previous state
2013
+ * @return Partial state with only changed keys
1906
2014
  */
1907
2015
  computePartialState(state, prevState) {
1908
2016
  if (typeof state !== "object" || state === null || typeof prevState !== "object" || prevState === null) {
@@ -1943,7 +2051,7 @@ var StateInterceptor = class {
1943
2051
  }
1944
2052
  };
1945
2053
 
1946
- // src/limelight/interceptors/RequestBridge.ts
2054
+ // src/limelight/bridges/RequestBridge.ts
1947
2055
  var RequestBridge = class {
1948
2056
  constructor(sendMessage, getSessionId) {
1949
2057
  this.sendMessage = sendMessage;
@@ -2110,6 +2218,42 @@ var RequestBridge = class {
2110
2218
  }
2111
2219
  };
2112
2220
 
2221
+ // src/limelight/handlers/CommandHandler.ts
2222
+ var CommandHandler = class {
2223
+ constructor(interceptors, sendMessage, getConfig) {
2224
+ this.interceptors = interceptors;
2225
+ this.sendMessage = sendMessage;
2226
+ this.getConfig = getConfig;
2227
+ }
2228
+ /**
2229
+ * Handles an incoming command.
2230
+ * @param command - The command to handle
2231
+ */
2232
+ handle(command) {
2233
+ const config = this.getConfig();
2234
+ if (config?.enableInternalLogging) {
2235
+ console.log("[Limelight] Received command:", command.type);
2236
+ }
2237
+ switch (command.type) {
2238
+ case "CLEAR_RENDERS" /* CLEAR_RENDERS */:
2239
+ this.interceptors.render.resetProfiles();
2240
+ break;
2241
+ default:
2242
+ if (config?.enableInternalLogging) {
2243
+ console.warn("[Limelight] Unknown command:", command.type);
2244
+ }
2245
+ }
2246
+ if (command.id) {
2247
+ this.sendMessage({
2248
+ phase: "ACK" /* ACK */,
2249
+ commandId: command.id,
2250
+ type: command.type,
2251
+ success: true
2252
+ });
2253
+ }
2254
+ }
2255
+ };
2256
+
2113
2257
  // src/limelight/LimelightClient.ts
2114
2258
  var LimelightClient = class {
2115
2259
  ws = null;
@@ -2127,6 +2271,7 @@ var LimelightClient = class {
2127
2271
  renderInterceptor;
2128
2272
  stateInterceptor;
2129
2273
  requestBridge;
2274
+ commandHandler = null;
2130
2275
  constructor() {
2131
2276
  this.networkInterceptor = new NetworkInterceptor(
2132
2277
  this.sendMessage.bind(this),
@@ -2152,6 +2297,11 @@ var LimelightClient = class {
2152
2297
  this.sendMessage.bind(this),
2153
2298
  () => this.sessionId
2154
2299
  );
2300
+ this.commandHandler = new CommandHandler(
2301
+ { render: this.renderInterceptor },
2302
+ this.sendMessage.bind(this),
2303
+ () => this.config
2304
+ );
2155
2305
  }
2156
2306
  /**
2157
2307
  * Configures the Limelight client with the provided settings.
@@ -2258,6 +2408,16 @@ var LimelightClient = class {
2258
2408
  this.flushMessageQueue();
2259
2409
  this.sendMessage(message);
2260
2410
  };
2411
+ this.ws.onmessage = (event) => {
2412
+ try {
2413
+ const command = JSON.parse(event.data);
2414
+ this.commandHandler?.handle(command);
2415
+ } catch (error) {
2416
+ if (this.config?.enableInternalLogging) {
2417
+ console.error("[Limelight] Failed to parse command:", error);
2418
+ }
2419
+ }
2420
+ };
2261
2421
  this.ws.onerror = (error) => {
2262
2422
  if (this.config?.enableInternalLogging) {
2263
2423
  console.error("[Limelight] WebSocket error:", error);