Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
@storybook/instrumenter
Advanced tools
The @storybook/instrumenter package is part of the Storybook ecosystem, designed to help developers instrument their code for interaction testing and debugging within the Storybook UI. It provides utilities to track and manipulate function calls, enabling more interactive and dynamic story creation.
Code Instrumentation
This feature allows developers to wrap their functions with instrumentation logic, which enables tracking and controlling the execution of these functions within Storybook's UI. The code sample shows how to instrument a simple function to log its execution.
import { instrument } from '@storybook/instrumenter';
const originalFunction = () => { console.log('Original function execution'); };
const instrumentedFunction = instrument(originalFunction, 'uniqueKey');
instrumentedFunction();
Interaction Tracking
Enables the tracking of function calls, which can be viewed and manipulated in Storybook's interaction panel. This is useful for debugging and understanding component behaviors during development.
import { track } from '@storybook/instrumenter';
const trackedFunction = track(() => { console.log('Function called'); }, 'trackedFunction');
trackedFunction();
Sinon is a standalone test spies, stubs, and mocks for JavaScript. It offers functionalities similar to @storybook/instrumenter in terms of spying and mocking functions, but it is more focused on testing scenarios rather than integrating with a UI tool like Storybook.
Part of the Jest testing framework, jest-mock provides mocking features that allow developers to replace parts of their system under test with mock objects and to make assertions about how they have been used. Jest-mock is similar to @storybook/instrumenter in that it manipulates function behavior, but it is tailored specifically for Jest-based testing environments.
The Storybook Instrumenter is used to patch a (3rd party) module to track and intercept function invocations for step-through debugging using the Interactions addon. In essence, the Instrumenter traverses a given object, recursively monkey-patching any functions to make them "tracked".
During normal operation, tracked functions simply call through to their original function, forwarding the return value. As a side-effect, they also emit a call
event whenever they are invoked.
Through options
, functions can be marked "interceptable", which give them another mode of operation. In this "intercept" mode, the original function is not invoked, instead the interceptable function returns a Promise
which only resolves when receiving an event to do so. This enables step-through debugging, directly in the browser. A consequence of this design is that all interceptable functions must be await
-ed, even if their original function is not asynchronous (i.e. it normally does not return a Promise).
The primary way to use the Storybook Instrumenter is through the instrument
function:
instrument<TObj extends Record<string, any>>(obj: TObj, options: Options): TObj
instrument
takes a plain JS object or imported ES module, and optionally an options
object. It traverses the input object, recursively iterating over object properties and arrays. Any values with typeof function
are tracked (through monkey-patching). Finally, a shallow copy of the original object is returned (with functions replaced). If the mutate: true
option is set, the original object is mutated instead of returning a shallow copy.
intercept
Control which instrumented functions are interceptable.mutate
Mutate and return the input object rather than returning a shallow copy.path
Virtual object path to prepend to the actual input object function paths.retain
Retain calls across renders and when switching stories.intercept
boolean | ((method: string, path: Array<string | CallRef>) => boolean)
Depending on the library and functions to be instrumented, you may want to configure some or all functions to be interceptable. Interceptable calls are debuggable, meaning they can be paused on. When paused, an interceptable function will not invoke it's original function, but rather return a pending Promise. This promise is only resolved when stepping over the call in the debugger. Only interceptable calls will appear as rows in the Interactions addon panel. Non-interceptable calls may appear as inline arguments to an interceptable function.
intercept
can take either a boolean (default false
) or a function which returns a boolean. This enables you to only make specific library functions interceptable. This function receives a method
and path
, referring to the name of the function and the path to that function in the object tree. Some functions may return an object which is then instrumented as well, in which case the path
will contain a "call ref", which is a plain object containing a __callId__
property referencing the originating call.
Here's an example intercept
function (from @storybook/testing-library
):
(method, path) => path[0] === 'fireEvent' || method.startsWith('findBy') || method.startsWith('waitFor'),
This means all methods under fireEvent
(an object) are instrumentable, as well as any methods which name starts with findBy
or waitFor
.
mutate
boolean
By default, instrument
creates a shallow clone of the input object, replacing functions with their tracked counterparts without affecting the original input object. This is usually the safest and most predictable behavior, but in some situations you may have to rely on mutation. By setting mutate: true
you can enable this behavior. Be careful though: mutating a Node module can lead to hard-to-debug issues.
path
Array<string | CallRef>
Storybook Interactions will automatically generate a pseudo-code representation of tracked function calls based on their metadata. For example, this call:
{ path: ['userEvent'], method: 'keyboard', args: ['hello'], ... }
Will print as userEvent.keyboard("hello")
.
By default, the call path
is determined by the hierarchy of the input object. To get the above result, your input object would have to look something like this:
{ userEvent: { keyboard: function(text) { ... } } }
The path
config option allows you to prepend elements to the normal path. So if your input object does not have a userEvent
property, but directly contains keyboard
, then you can set path: ['userEvent']
to correct for this.
retain
boolean
On rare occasions, you may have an instrumented function that's invoked outside the context of a story render or play function. One example can be found in argsEnhancers
which run when Storybook is initialized, but aren't rerun when you switch or rerun stories. Normally, the Storybook Instrumenter clears its internal record of calls when switching between stories, losing track of those function calls which happened on initialization. Set retain: true
to keep these calls on record while switching or rerunning stories.
Note that retained functions should not be interceptable.
The Storybook Instrumenter uses the Storybook Channel API to send and receive events.
The instrumenter emits two types of events for tracking function invocations ("calls"):
storybook/instrumenter/call
Provides call metadata whenever a tracked function is invoked.storybook/instrumenter/sync
Provides a call log after one or more tracked functions are invoked.storybook/instrumenter/call
This event is emitted whenever a tracked function is invoked (a "call").
The event payload consists of all metadata about the function invocation, including a unique id
, any arguments, the method name and object path. However, the order of events is not guaranteed and you may receive the same call multiple times while debugging. Moreover, this event is emitted for all tracked calls, not just interceptable ones.
storybook/instrumenter/sync
This event is emitted whenever a tracked function is invoked, but the event is debounced until the next "tick", so multiple consecutive synchronous calls will trigger a single sync
event.
The event payload object contains an array of logItems
and a controlStates
object. The logItems
array represent a "normalized" log of interceptable calls. The order of calls in this log is guaranteed and step-through debugging will not append to the log but rather update it to set the proper status
for each call. The log does not contain full call metadata but only a callId
property, so this must be mapped onto received storybook/instrumenter/call
events. For the value of controlStates
, see Control states.
An example sync
payload may look like this:
{
controlStates: {
start: false,
back: false,
goto: true,
next: true,
end: true,
},
logItems: [
{ callId: 'tooltip--hovered [0] hover', status: 'waiting' }
]
}
The instrumenter listens for these control events:
storybook/instrumenter/start
- Remount the story and start the debugger at the first interceptable callstorybook/instrumenter/back
- Remount the story and start the debugger at the previous interceptable callstorybook/instrumenter/goto
- Fast-forwards to - or remounts and starts debugging at - the given interceptable callstorybook/instrumenter/next
- Resolves the Promise for the currently intercepted call, letting execution continue to the next callstorybook/instrumenter/end
- Resolves all Promises for intercepted calls, letting execution continue to the endRemounting is achieved through emitting Storybook's forceRemount
event. In some situations, this will trigger a full page refresh (of the preview) in order to flush pending promises (e.g. long-running interactions).
Besides patching functions, the instrumenter keeps track of "control states". These indicate whether the debugger is available, and which control events are available for use:
start: boolean
- Whether emitting storybook/instrumenter/start
would workback: boolean
- Whether emitting storybook/instrumenter/back
would workgoto: boolean
- Whether emitting storybook/instrumenter/goto
would worknext: boolean
- Whether emitting storybook/instrumenter/next
would workend: boolean
- Whether emitting storybook/instrumenter/end
would workThese values are provided in the controlStates
object on the storybook/instrumenter/sync
event payload.
FAQs
Unknown package
We found that @storybook/instrumenter demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 12 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.