SharedService is a JavaScript library for building real-time, multi-tab applications with shared state and services. It leverages SharedWorker to synchronize UI state and actions across browser tabs, making it easy to build collaborative or multi-instance web apps.
- Features
- Demo Project
- Installation
- Get Started
- Advanced Usage
- Data Persistence
- TODO & Roadmap
- Contributing
- License
- Support
- Share UI state and actions between browser tabs using
SharedWorker. - Centralize all data and services in a single worker for consistency.
- Simple React integration via hooks and helpers.
- Extensible for custom actions and data persistence.
- Demo project and online example included.
A TODO demo project here:
Online demo open in multiple tabs
$ npm install @shared-service/core @shared-service/react
- In
Reactapp root endpoint:
import React from 'react';
import ReactDOM from 'react-dom';
import { initSharedService } from '@shared-service/react';
import App from './App';
const worker = new SharedWorker('./worker.js', { type: 'module' });
initSharedService({ port: worker.port });
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);- In SharedWorker file
worker.js:
import { SharedServiceServer } from '@shared-service/core';
const sharedServiceServer = new SharedServiceServer({
count: 0,
});
/*global onconnect*/
onconnect = function(e) {
sharedServiceServer.onNewPort(e.ports[0]);
};- In React component:
import React from 'react';
import { useSharedState } from '@shared-service/react';
export default function App() {
const [count, setCount] = useSharedState('count', 0);
return (
<div className="App">
<div className="Counter">
<p>
Counter:
{count}
</p>
<button type="button" onClick={() => setCount(count + 1)}>
+1 to global
</button>
</div>
</div>
);
}In SharedWorker file worker.js:
sharedServiceServer.registerExecutor('increaseCount', () => {
const count = sharedServiceServer.getState('count');
sharedServiceServer.setState('count', count + 1);
});
sharedServiceServer.registerExecutor('markAsCompleted', (id) => {
const tasks = sharedServiceServer.getState('tasks');
const updatedTasks = tasks.map(task => {
if (id === task.id) {
return {...task, completed: true }
}
return task;
});
sharedServiceServer.setState('tasks', updatedTasks);
});In React component:
import React from 'react';
import { useSharedState } from '@shared-service/react';
export default function App() {
const [count] = useSharedState('count', 0);
const increaseCount = () => {
return $sharedService.execute('increaseCount');
};
const markAsCompleted = () => {
return $sharedService.execute('markAsCompleted', ['todo-id']);
};
return (
<div className="App">
<div className="Counter">
<p>
Counter:
{count}
</p>
<button type="button" onClick={increaseCount}>
+1 to global
</button>
<button type="button" onClick={markAsCompleted}>
Click to mark as completed
</button>
</div>
</div>
);
}In SharedWorker file worker.js:
import localforage from 'localforage';
async function initStorage() {
const storage = localforage.createInstance({
name: 'myApp',
});
await storage.ready();
const keys = await storage.keys();
const promises = keys.map((key) =>
storage.getItem(key).then((data) => {
sharedServiceServer.setState(key, data);
}),
);
await Promise.all(promises);
sharedServiceServer.on('stateChange', ({ key, state }) => {
storage.setItem(key, state);
});
}
initStorage();- Support Vue
- Run
ShareServiceat browser extension background and normal page - Run
ShareServiceat Electron main and render process
Contributions, issues, and feature requests are welcome! Feel free to check issues or submit a pull request.
This project is licensed under the MIT License. See the LICENSE file for details.
For questions, feedback, or support, open an issue on GitHub or contact the maintainers.