@getlimelight/sdk 0.4.5 → 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.5" : "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
@@ -1712,7 +1795,9 @@ var StateInterceptor = class {
1712
1795
  */
1713
1796
  registerStore(name, store) {
1714
1797
  if (this.stores.has(name)) {
1715
- console.warn(`[Limelight] Store "${name}" already registered`);
1798
+ if (this.config?.enableInternalLogging) {
1799
+ console.warn(`[Limelight] Store "${name}" already registered`);
1800
+ }
1716
1801
  return;
1717
1802
  }
1718
1803
  const library = this.detectLibrary(store);
@@ -1722,7 +1807,7 @@ var StateInterceptor = class {
1722
1807
  );
1723
1808
  return;
1724
1809
  }
1725
- const state = this.getState(store, library);
1810
+ const state = this.getState(store);
1726
1811
  const initEvent = {
1727
1812
  phase: "STATE:INIT" /* INIT */,
1728
1813
  sessionId: this.getSessionId(),
@@ -1739,6 +1824,8 @@ var StateInterceptor = class {
1739
1824
  }
1740
1825
  /**
1741
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
1742
1829
  */
1743
1830
  unregisterStore(name) {
1744
1831
  const store = this.stores.get(name);
@@ -1749,6 +1836,7 @@ var StateInterceptor = class {
1749
1836
  }
1750
1837
  /**
1751
1838
  * Emit an event, applying beforeSend hook if configured
1839
+ * @param event The event to emit
1752
1840
  */
1753
1841
  emitEvent(event) {
1754
1842
  if (this.config?.beforeSend) {
@@ -1767,6 +1855,8 @@ var StateInterceptor = class {
1767
1855
  }
1768
1856
  /**
1769
1857
  * Detect whether a store is Zustand or Redux
1858
+ * @param store The store to inspect
1859
+ * @return StateLibrary or null if unknown
1770
1860
  */
1771
1861
  detectLibrary(store) {
1772
1862
  if (!store || typeof store !== "function" && typeof store !== "object") {
@@ -1785,13 +1875,19 @@ var StateInterceptor = class {
1785
1875
  }
1786
1876
  /**
1787
1877
  * Get current state from a store
1878
+ * @param store The store to get state from
1879
+ * @return The current state
1788
1880
  */
1789
- getState(store, library) {
1881
+ getState(store) {
1790
1882
  const storeAny = store;
1791
1883
  return storeAny.getState();
1792
1884
  }
1793
1885
  /**
1794
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
1795
1891
  */
1796
1892
  subscribe(store, library, storeName) {
1797
1893
  const storeAny = store;
@@ -1803,6 +1899,9 @@ var StateInterceptor = class {
1803
1899
  }
1804
1900
  /**
1805
1901
  * Subscribe to Zustand store changes
1902
+ * @param store The Zustand store
1903
+ * @param storeName The name of the store
1904
+ * @return Unsubscribe function
1806
1905
  */
1807
1906
  subscribeZustand(store, storeName) {
1808
1907
  return store.subscribe((state, prevState) => {
@@ -1825,6 +1924,9 @@ var StateInterceptor = class {
1825
1924
  }
1826
1925
  /**
1827
1926
  * Subscribe to Redux store changes
1927
+ * @param store The Redux store
1928
+ * @param storeName The name of the store
1929
+ * @return Unsubscribe function
1828
1930
  */
1829
1931
  subscribeRedux(store, storeName) {
1830
1932
  let lastAction = { type: "@@INIT" };
@@ -1860,6 +1962,9 @@ var StateInterceptor = class {
1860
1962
  }
1861
1963
  /**
1862
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
1863
1968
  */
1864
1969
  inferZustandAction(state, prevState) {
1865
1970
  const actionType = this.parseActionFromStack(this.captureStackTrace());
@@ -1871,6 +1976,8 @@ var StateInterceptor = class {
1871
1976
  }
1872
1977
  /**
1873
1978
  * Parse function name from stack trace
1979
+ * @param stack The stack trace string
1980
+ * @return The inferred action name
1874
1981
  */
1875
1982
  parseActionFromStack(stack) {
1876
1983
  if (!stack) return "set";
@@ -1901,6 +2008,9 @@ var StateInterceptor = class {
1901
2008
  }
1902
2009
  /**
1903
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
1904
2014
  */
1905
2015
  computePartialState(state, prevState) {
1906
2016
  if (typeof state !== "object" || state === null || typeof prevState !== "object" || prevState === null) {
@@ -1941,7 +2051,7 @@ var StateInterceptor = class {
1941
2051
  }
1942
2052
  };
1943
2053
 
1944
- // src/limelight/interceptors/RequestBridge.ts
2054
+ // src/limelight/bridges/RequestBridge.ts
1945
2055
  var RequestBridge = class {
1946
2056
  constructor(sendMessage, getSessionId) {
1947
2057
  this.sendMessage = sendMessage;
@@ -2108,6 +2218,42 @@ var RequestBridge = class {
2108
2218
  }
2109
2219
  };
2110
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
+
2111
2257
  // src/limelight/LimelightClient.ts
2112
2258
  var LimelightClient = class {
2113
2259
  ws = null;
@@ -2125,6 +2271,7 @@ var LimelightClient = class {
2125
2271
  renderInterceptor;
2126
2272
  stateInterceptor;
2127
2273
  requestBridge;
2274
+ commandHandler = null;
2128
2275
  constructor() {
2129
2276
  this.networkInterceptor = new NetworkInterceptor(
2130
2277
  this.sendMessage.bind(this),
@@ -2150,6 +2297,11 @@ var LimelightClient = class {
2150
2297
  this.sendMessage.bind(this),
2151
2298
  () => this.sessionId
2152
2299
  );
2300
+ this.commandHandler = new CommandHandler(
2301
+ { render: this.renderInterceptor },
2302
+ this.sendMessage.bind(this),
2303
+ () => this.config
2304
+ );
2153
2305
  }
2154
2306
  /**
2155
2307
  * Configures the Limelight client with the provided settings.
@@ -2256,6 +2408,16 @@ var LimelightClient = class {
2256
2408
  this.flushMessageQueue();
2257
2409
  this.sendMessage(message);
2258
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
+ };
2259
2421
  this.ws.onerror = (error) => {
2260
2422
  if (this.config?.enableInternalLogging) {
2261
2423
  console.error("[Limelight] WebSocket error:", error);