Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
streamlit-notification-center-component
Advanced tools
Streamlit component that listens for postMessage events and reports back
Streamlit component that listens to postMessage() traffic with a certain format.
pip install streamlit_notification_center_component
If you wish to test the component outside of another project, open two shells. In one, you'll want to run the react code in one shell and the python __init__.py
in another. However, before you do this you need to open __init__.py
first and change the flag _RELEASE
from True
to False
or you'll get no output.
In shell no.1
cd notification_center/frontend
npm install # if you haven't done this before
npm start
In shell no.2
source venv/bin/activate
streamlit run notification_center/__init__.py
If all went well, you'll see a window open up with some text and some buttons.
import streamlit as st
from streamlit_notification_center_component import notification_center
notification_center(key="globalnc")
function ncSendMessage(target, body) {
Array.from(window.parent.frames).forEach((frame) => {
frame.postMessage({ type: 'nc', target: target, payload: body }, "*")
})
}
function ncClearTarget(target) {
Array.from(window.parent.frames).forEach((frame) => {
frame.postMessage({ type: 'nc', target: target, command: 'clear' }, "*")
})
}
function ncClearAll() {
Array.from(window.parent.frames).forEach((frame) => {
frame.postMessage({ type: 'nc', command: 'clear-all' }, "*")
})
}
Let's talk about what's going on here. Each of these three functions loop over all frames in the parent and invoke a postMessage on them. The type: 'nc'
code MUST be present or the notification_center widget will ignore the message.
In cases where clearing the data from state on the streamlit side is desired, a command:
property needs to be included. There are two possible values:
target:
property must also be included to identify what to cleartype: 'nc'
needs be present. All messages stored in the st.session_state._nc_data
dict will be removed.Lastly, when it comes to a normal message to be queued, the way this works is that streamlit will receive the value from the component's call to Streamlit.setComponentValue()
. This will be a pre-vetted object conforming to the following TypeScript type:
export type NotificationMessage = {
type: 'nc';
target?: string;
payload?: any;
from?: string;
command?: null | 'clear' | 'clear-all';
}
When not sending a command to clear and type
, target
and payload
are all present, then the dict st.session_state._nc_data
will have its key .setdefault(target, [])
. This ensures that there is at least an empty array of values for the supplied target. Then the payload object is appended to this array. All streamlit code can check for and use values.
In addition to providing the streamlit component for use, the component comes with several useful python functions that make working with the component much easier. These functions are obviously provided to aid on the streamlit/python side of things.
The nc_ensure()
function is provided to ensure that the environment is setup properly for use. It is heavily used by the internal functions and can be used in your own scripts. For example, if you wish to setup your use of notification_center()
in a separate file, you might do something like this:
from notification_center import nc_ensure, nc_add_style
def nc_setup():
# Ensure notification center globals are ready
nc_ensure()
# Add styles for use with nc_html()
nc_add_style(f'* {{font-size:200%;}}')
This function would ensure that any poking or prodding to st.session_state._nc_
variables would be ready to go if this is something you needed to do.
The nc_get()
function allows the retrieval of the array of messages sent to the key in question. If no value exists with the supplied key then the default_value, defaulting to None, is what is returned.
let button_presses = nc_get('button_clicks')
if button_presses:
# do something
The value returned here will be a list or array of zero or more values sent to the key
specified.
Similar to nc_get()
however this function returns the last value in the list associated with the supplied key
. If the list is empty or none exists with that key, then the default_value
is returned.
let last_command = nc_get_last('executed_commands')
st.write(f'The last command executed was {last_command}')
This function returns the entire dictionary of values stored on the session state
let dictionary = nc_get_all()
# work on the dictionary
This is the value executed when postMessage()
function is called and the NotificationCenter processes the contents successfully. The key
value is used as a dictionary key and the value
is appended to the array of previous messages for the same target.
nc_set('person', {'name': 'Brielle', 'gender': 'Female'})
This function returns True
if the supplied key exists in the dictionary of NotificationCenter values, or False
otherwise.
if nc_has('people'):
people = nc_get('people')
# do work on the people list
This function will locate an existing key
in the dictionary. These keys have a list as their value. The .clear()
function will be invoked on the list.
# User clicked on delete all people
nc_clear('people')
This function removes all keys and values from the NotificationCenter data store.
nc_clear_all()
This function allows you to specify a replacement or compiled regular expression that is used to search the contents of a to-be-rendered HTML snippet. The value or function (Callable) will be used in the place of the sought value. If the regular expression captured groups, they will be passed to the function as parameters.
nc_add_substitution(re.compile(r':exit-button:(.*?):'), lambda title: f'<button>{title}</button>')
nc_html(
f'''
<body>:exit-button:Click to exit:</body>
'''
)
This code would replace the ":exit-button:Click to exit:" with "Click to exit" as an example.
This function takes a script source (URL or path) and optionally a dictionary of HTML attributes, and appends a formatted script tag or raw source to the session state list. If 'raw' is False, the 'src' and 'attributes' are used to create a ' calls.
nc_add_script_src('https://remotejslibrary.com/script.js')
nc_html(...) # remotejslibrary is automatically inserted into the nc_html
This function takes a link source (URL or path) and optionally a dictionary of HTML attributes, and appends a formatted link tag or raw source to the '_nc_link_rels' session state list. If 'raw' is False, the 'href' and 'attributes' are used to create a '' tag using BeautifulSoup. Each of these tags are inserted in subsequent nc_html()
calls.
nc_add_link_rel('https://remotescript.com/style.css')
nc_html(...) # remote css script is automatically inserted into the nc_html
The function nc_html()
is a more function version of the st.components.v1.html()
. The nc_html()
function will pre-inject CSS, JavaScript and any HTML snippets previously setup as global pre-defines. The nc_add_script()
function allows you to add a snippet of JavaScript to all nc_html()
calls before the supplied HTML string.
nc_add_script(
f'''
Object.defineProperty(Array.prototype, 'first', { get: function() { return this?.[0] } })
'''
)
After running this function, the next time nc_html()
is invoked, the component added to the page will have access to JavaScript arrays with a getter on each instance called .first
that dynamically returns the first value or null
if there isn't one.
The function nc_html()
is a more function version of the st.components.v1.html()
. The nc_html()
function will pre-inject CSS, JavaScript and any HTML snippets previously setup as global pre-defines. The nc_add_style()
function allows you to add a snippet of CSS to all nc_html()
calls before the supplied HTML string.
nc_add_styles(f'* {{ font-size: 200%; }}')
When executed, all subsequent nc_html()
calls will find their contents' font sizes increased to 200%.
This function adds a new snippet of html that will be included in all calls to nc_html()
.
nc_add_html(f'<hr/>')
When next nc_html()
is executed, it will always be preceded by a horizontal rule.
This function removes all previously specified script sources.
This function removes all previously specified link tags.
This function removes all previously specified substitutions.
Resets all JavaScript snippets to the defaults. The defaults are the three aforementioned JavaScript Helper functions.
This function removes all globally added CSS snippets previously added using nc_add_style()
.
Simlar in function to nc_reset_scripts()
or nc_reset_styles()
, the function nc_reset_html()
removes all previously added HTML snippets.
Probably one of the most useful function, this call will execute the supplied callback whenever any new value is set with a key
/target
that matches the to
value supplied to this function.
unsub = nc_listen('add_person', lambda person: print(person))
# when done
unsub()
This function also returns a function or Callable
that when invoked will trigger an unsubscribe on the internal event manager.
The creme of the crop, injects a component of HTML into the page. This HTML iframe that gets injected will be prefixed by any previous calls to nc_add_html()
, nc_add_style()
, and nc_add_script()
. Even when no other code is injected, at least the three helper JavaScript functions above will be present. This allows simple code like this to work:
nc_html(f"<a onclick=\"ncSendMessage('target', {{...}})\">Send Message</a>")
This function invokes both nc_add_link_rel()
and nc_add_script_src()
to include Bootstrap 5.13 CSS and JavaScript libraries for each subsequent use of nc_html()
.
Okay, down to brass tacks. The streamlit_notification_center_component
's primary purpose is to allow code in other custom components to send data back to streamlit in one shared location or to allow HTML in a call to st.components.v1.html
to communicate back to the Python streamlit side of things. Currently layout is very limited in streamlit from an HTML perspective. Being able to write a quick layout using something like a table or set of inline-block divs is doable using the st.components.v1.html
approach. However as soon as you have an action or other feature that needs to send data back to streamlit, you've moved into custom component territory.
st.components.v1.html
Let's take the most common example first. If you wanted to introduce the following HTML with a stylized link that looks like a button
from notification_center import notification_center, nc_html, nc_add_style, nc_listen
notification_center(key="globalnc")
nc_listen('data', lambda data: print('data added', data))
nc_add_script(
f'''
function sendData() {{ ncSendMessage('data', {{msg: 'Stylized Button Pressed'}}) }}
'''
)
nc_add_style(
f'''
a.stylized-button {{
padding: 0.25em;
margin: 0.1em;
border: 1px solid slategrey;
background-color: lightslategray;
border-radius: 5px;
color: lightyellow;
cursor: pointer;
display: inline-block;
user-select: none;
}}
'''
)
nc_html(f'<a class="stylized-button" onclick="sendData()">Button</a>')
What's going on here? Well, first off we are creating a new instance of the notification_center()
component. The key supplied can be anything but will positively identify this NotificationCenter instance from another should there be one. If debug printing is enabled (see below) then you'll see "[NotificationCenter(key)][target] value" strings in the python logs. Note that key will be the key=
value supplied when the notification_center()
component is created, target
will be the key the array of messages will be appended to and value
will be the array of values for that target
.
Then we are injecting into the page a block of HTML with some CSS and JavaScript. You'll note in this example that the ncSendMessage
function from above is being used in the block. This is the easiest way to handle sending a postMessage()
call to all frames in the parent of the iframe this code will reside in. The NotificationCenter
component will be residing in another iframe with how streamlit works.
We now have the ability to inject custom HTML that can speak back to streamlit into your streamlit pages. Something that was not provided by default behavior.
Other components should be able to import notification_center
and use the nc-
prefixed python functions for communicating with NotificationCenter data. As components send messages to the NotificationCenter, those values can be checked on the python side and can be worked with.
If you're running into trouble, turn on debug messaging. This is a runtime variable that is set in
st.session_state.setdefault('_nc_debug', False)
To turn it on, simply change the value to True
st.session_state._nc_debug = True
After this point you'll see NotificationCenter messages appearing in your Python console.
FAQs
Streamlit component that listens for postMessage events and reports back
We found that streamlit-notification-center-component demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer 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
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.