
Research
Malicious npm Packages Impersonate Flashbots SDKs, Targeting Ethereum Wallet Credentials
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
app-manager
Advanced tools
Script for managing the lifecycles of multiple apps on a single page
Script for managing the lifecycles of multiple apps on a single page
export default {
slots: {
APP: {
querySelector: '.app',
},
},
fragments: {
EXAMPLE1_FRAGMENT: {
slot: 'APP',
async loadScript(state) {
return fetchScriptForExample1(state);
},
async ssrGetMarkup(querySelector, state, query) {
return /* @html */`
<div class="${querySelector.slice(1)}">
${await fetchMarkupForExample1(state, query)}
</div>
`;
},
},
EXAMPLE2_FRAGMENT: {
slot: 'APP',
async loadScript(state) {
return fetchScriptForExample2(state);
},
async ssrGetMarkup(querySelector, state, query) {
return /* @html */`
<div class="${querySelector.slice(1)}">
${await fetchMarkupForExample2(state, query)}
</div>
`;
},
}
},
routes: {
EXAMPLE1_APP: {
path: '/apps/example1',
fragment: 'EXAMPLE1_FRAGMENT',
},
EXAMPLE2_APP: {
path: '/apps/example2',
fragment: 'EXAMPLE2_FRAGMENT',
}
}
}
import appManager from 'app-manager';
import config from './config';
// ...
const options = {
importTimeout: 3000,
};
appManager(config, eventEmitter, options);
import appManagerServer from 'app-manager/server';
import config from './config';
const { getSlotsMarkup } = appManagerServer(config);
// ...
app.get('/apps/*', async (req, res, next) => {
try {
const renderedMarkup = await getSlotsMarkup(req.originalUrl, req.query);
return res.send(/* @html */`
<!DOCTYPE html>
<html>
<body>
${renderedMarkup.APP}
<script src="/static/main.js"></script>
</body>
</html>
`);
} catch (err) {
next(err);
}
});
app-manager
exports a function that takes three parameters:
config
- describes your app to app-managerevents
- a event emitter module with the same API as the native node.js moduleoptions
- Optional options objectAn object containing maps of the slots, fragments, and routes that comprise your app.
{
slots: {
SLOT_NAME: slot, // As below
},
fragments: {
FRAGMENT_NAME: fragment, // As below
},
routes: {
ROUTE_NAME: route, // As below
},
}
The state object is passed as a parameter into almost every function within and without app-manager. It contains the useful current state of the browser and the derived state of the application.
{
resource: '/app/some-path?query=value', // The pathname and query string currently displayed in the browser
title: 'My Page', // The current document page title
historyState: null, // The current contents of the history state object
eventTitle: 'hc-initialise', // The name of the event (as below) that caused the current action to occur
route, // The config for the current route (as below)
prevRoute, // The config for the route that was previous to the current one. Is null when app is first loaded.
...additionalState, // Any additional state returned from the getAdditionalState function passed into options (as below)
}
A route
determines which fragments are displayed on which path
.
{
path: '/app/:path', // Path or...
paths: ['/app/:path1', '/app/:path2'], // ...paths that uniquely identifies this app. Analogous to a route in express.
fragment: 'FRAGMENT_NAME', // Fragment name or...
fragments: ['APP_FRAGMENT_NAME', 'HEADER_FRAGMENT_NAME'], // ...ordered array of fragment names to be displayed for that route. If two fragments occupy the same slot, the first will take precedence.
}
A slot
is a wrapper for a DOM element on a page.
{
querySelector: '.app', // Uniquely returns a single DOM element when passed to document.querySelector.
async getLoadingMarkup(state) {
// Optional function that should return some markup to be displayed in a slot between a fragment being unmounted and a new one being mounted in its place.
return /* @html */`<img src="/spinner.gif" alt="Loading ${state.route.name}" />`
},
async getErrorMarkup(state) {
// Optional function that returns a markup string to be displayed in a slot if one of the lifecycle methods for a script throws an error.
return /* @html */`<p class="error">An error occurred while browsing to ${state.route.name}.`;
},
async ssrGetErrorMarkup(querySelector, state, ...otherArgs) {
// Optional function that's called if options.ssrHaltOnError is false and ssrGetMarkup for a fragment which is being loaded into this slot throws an error. Should return a markup string.
return /* @html */`
<div class="${querySelector.slice(1)}">
<p class="error">An error occurred while loading ${state.route.name}.</p>
</div>
`;
},
},
A fragment
is the container for your script
.
{
slot: 'SLOT_NAME', // Slot name or...
slots: ['LEFT_SLOT_NAME', 'RIGHT_SLOT_NAME'], // ...ordered array of slot names. Fragment will be loaded in the first empty slot possible.
async loadScript(state) {
// * Optional function (only needed if using app-manager to manage client-side lifecycle) that fetches the script (as below) for your fragment.
// ...
return script;
},
async ssrGetMarkup(querySelector, state, ...otherArgs) {
// * Optional function (only needed if using app-manager for server-side rendering) that fetches the markup needed to render your fragment into a DOM.
// * Should contain any serialised app state you wish to use to hydrate the app on the client, any inline styles, etc.
// * Function is called with the querySelector of the slot into which it is being mounted, state (as below), and any arguments you pass into the 'appManagerServer.getSlotsMarkup' function.
// ...
return markupString;
},
}
A script
is the entry-point to your code.
It should contain the lifecycle methods to be called as the user browses around your site:
{
version: 6 // Lets app-manager know which schema to expect from your script
async hydrate(container, state) {
// * Optional function that will be called if the parent fragment is included on the page on first load
// * If rendering your app isomorphically, you will likely wish to read app state from the DOM here.
// * Function is called with the element into which the fragment is to be mounted and state (as below)
const appState = window['__your_app_state__']
ReactDOM.hydrate(container, <YourApp {...appState} />);
},
async render(container, state) {
// * Optional function that will be called if the user browses onto an path for which the parent fragment is to be mounted.
// * If your app relies on initial state, you should fetch it here
// * Function is called with the element into which the fragment is to be mounted and state (as below)
const props = await getInitialStateFromServer(state.params);
ReactDOM.render(container, <YourApp {...props} />);
},
async onStateChange(container, state) {
// * Optional function that will be called when an event is fired from the history api, and the fragment is to remain mounted on the page
// * If your fragment has multiple views that should be routed between (for example with path params), this lifecycle method will be where routing is managed.
// * Function is called with state (as below)
await updateAppState(state);
},
async unmount(container, state) {
// * Optional function that will be called when the user browses away from an path on which the parent fragment is mounted.
// * Useful to clear up any event listeners, and to reset any mutable state.
// * Function is called with the element into which the fragment is to be mounted and state (as below)
ReactDOM.unmountComponentAtNode(container);
},
}
Suggested: EventEmitter3
{
emit(eventTitle, data) {
// ...
},
on(eventTitle, callback) {
// ...
},
removeListener(eventTitle, callback) {
// ...
},
}
All properties in the options object are optional.
{
importTimeout: 4000, // Timeout for loading scripts and finding elements in the DOM. Defaults to 4000
ssrHaltOnError: false, // Boolean value that determines whether the getSlotsMarkup function should throw on error or call the slot's ssrGetErrorMarkup. Defaults to false.
parentQuerySelector: '.app', // Query selector that is passed into the getElement function (below) to find the parent element of your app. If not provided, document.body is used.
async getRouteName(state) {
// Function that takes a state object and returns the name of a route derived from that state.
// Defaults to a function that scans the list of routes passed in config and performs an equality check between the current resource (pathname + query) and each route's path
const params = await deriveParamsFromResource(state.resource);
return params.routeName;
},
async getAdditionalState(state) {
// Function that allows you to return an object of arbitrary values that will be passed into the state object
const params = await deriveParamsFromResource(state.resource);
return {
params,
};
},
async getElement(container, querySelector) {
// Function that takes a parent DOM Element and the query selector for the slot we're trying to find on the page, and should return a DOM Element.
// Defaults to container.querySelector(querySelector)
return container.querySelector(querySelector);
},
async getLayout(state) {
// Function that is called when browsing onto an app-manager controlled page without hydrating.
// Allows you to set the default static DOM into which the app will be rendered.
// Useful when nesting app-manager instances.
return /* @html */`<div class="app"></div>`;
}.
}
FAQs
Script for managing the lifecycles of multiple apps on a single page
The npm package app-manager receives a total of 1 weekly downloads. As such, app-manager popularity was classified as not popular.
We found that app-manager demonstrated a not healthy version release cadence and project activity because the last version was released 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
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.
Security News
Ruby maintainers from Bundler and rbenv teams are building rv to bring Python uv's speed and unified tooling approach to Ruby development.
Security News
Following last week’s supply chain attack, Nx published findings on the GitHub Actions exploit and moved npm publishing to Trusted Publishers.