Skip to content

Commit f287700

Browse files
Merge pull request #423 from H7GhosT:util/snapshot-server
Snapshot manager mini-app and fix loading dialogs from old state
2 parents 79fdbfb + 721f1f8 commit f287700

File tree

15 files changed

+1627
-35
lines changed

15 files changed

+1627
-35
lines changed

‎README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Run `node build` to build the minimized production version of the app. Copy `pub
3434
* [TinyLD](https://github.com/komodojp/tinyld) ([MIT License](https://github.com/komodojp/tinyld/blob/develop/license))
3535
* [libwebp.js](https://libwebpjs.appspot.com/)
3636
* fastBlur
37+
* [mp4-muxer](https://github.com/Vanilagy/mp4-muxer) ([MIT License](https://github.com/Vanilagy/mp4-muxer/blob/main/LICENSE))
3738

3839
### Debugging
3940
You are welcome in helping to minimize the impact of bugs. There are classes, binded to global context. Look through the code for certain one and just get it by its name in developer tools.
@@ -47,6 +48,8 @@ Source maps are included in production build for your convenience.
4748

4849
Should be applied like that: http://localhost:8080/?test=1
4950

51+
#### Taking local storage snapshots
52+
You can also take and load snapshots of the local storage and indexed DB using the `./snapshot-server` [mini-app](/snapshot-server/README.md). Check the `README.md` under this folder for more details.
5053

5154
### Troubleshooting & Suggesting
5255

‎snapshot-server/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
snapshots
2+
node_modules

‎snapshot-server/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Storage snapshot manager mini-app
2+
3+
Take and manage snapshots of the local storage and indexed DB.
4+
5+
### Starting the app
6+
7+
First, shut down the dev server of the main app, as we don't want anything happening in the background while we do our dirty stuff here.
8+
9+
```sh
10+
cd ./snapshot-server
11+
12+
pnpm start --port=8080 # Make sure the port matches your usual development port, don't specify if you want the 8080 default
13+
```
14+
15+
Go to `http://localhost:8080`, then in `chrome://inspect` (or your browser's equivalent), just in case check if there is no active service worker, or any other workers for the localhost if there are any. Note that the workers may interfere with the read/write operations in the indexed DB.
16+
17+
Here you can take and manage snapshots of the local storage and indexed DB, that will be saved locally under `./snapshot-server/snapshots/**`.
18+
19+
Be careful and don't spam any button, these operations can take some time so use this at your own risk. If you don't get the success message after clicking the `Load` button after at most a few seconds, it is likely there is a service worker running in the background.

‎snapshot-server/index.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
const express = require('express');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
const {parser} = require('stream-json');
6+
const {streamObject} = require('stream-json/streamers/StreamObject');
7+
8+
9+
const app = express();
10+
const PORT = 8080;
11+
const SNAPSHOT_DIR = path.join(__dirname, 'snapshots');
12+
13+
// Ensure snapshot folder exists
14+
if(!fs.existsSync(SNAPSHOT_DIR)) {
15+
fs.mkdirSync(SNAPSHOT_DIR);
16+
}
17+
18+
// app.use(bodyParser.json({limit: '100mb'})); // Accept large JSON payloads
19+
app.use(express.static(path.join(__dirname, 'public')));
20+
app.use(express.text({type: 'text/plain', limit: '100mb'}));
21+
22+
// List all snapshots
23+
app.get('/api/snapshots', async(req, res) => {
24+
const jsonFiles = fs.readdirSync(SNAPSHOT_DIR)
25+
.filter(f => f.endsWith('.json'));
26+
27+
const meta = await Promise.all(jsonFiles.map(async f => ({
28+
name: f,
29+
comment: await getComment(f),
30+
timestamp: fs.statSync(path.join(SNAPSHOT_DIR, f)).mtimeMs
31+
})));
32+
33+
const sorted = meta
34+
.sort((a, b) => b.timestamp - a.timestamp);
35+
36+
res.json(sorted);
37+
});
38+
39+
// Save a new snapshot
40+
app.post('/api/snapshots', (req, res) => {
41+
const data = req.body;
42+
const filename = `snapshot-${getFormattedDate()}.json`;
43+
const filepath = path.join(SNAPSHOT_DIR, filename);
44+
45+
fs.writeFile(filepath, data, (err) => {
46+
if(err) {
47+
res.status = 500;
48+
res.json({message: 'something went wrong'});
49+
} else {
50+
res.json({success: true, filename});
51+
}
52+
});
53+
});
54+
55+
// Load a snapshot by filename
56+
app.get('/api/snapshots/:filename', (req, res) => {
57+
const {filename} = req.params;
58+
const filepath = path.join(SNAPSHOT_DIR, filename);
59+
60+
if(!fs.existsSync(filepath)) {
61+
return res.status(404).json({error: 'Snapshot not found'});
62+
}
63+
64+
const data = fs.readFileSync(filepath, 'utf-8');
65+
res.json(JSON.parse(data));
66+
});
67+
68+
// Delete a snapshot by filename
69+
app.delete('/api/snapshots/:filename', (req, res) => {
70+
const {filename} = req.params;
71+
const filepath = path.join(SNAPSHOT_DIR, filename);
72+
73+
if(!fs.existsSync(filepath)) {
74+
return res.status(404).json({error: 'Snapshot not found'});
75+
}
76+
77+
fs.unlinkSync(filepath);
78+
res.json({success: true});
79+
});
80+
81+
// Start server with optional port argument
82+
const portArg = process.argv.find(arg => arg.startsWith('--port='));
83+
const portToUse = portArg ? parseInt(portArg.split('=')[1], 10) : PORT;
84+
85+
app.listen(portToUse, () => {
86+
console.log(`🟢 Server running at http://localhost:${portToUse}`);
87+
});
88+
89+
function getFormattedDate() {
90+
const d = new Date();
91+
const pad = n => String(n).padStart(2, '0');
92+
const date = [d.getFullYear(), pad(d.getMonth() + 1), pad(d.getDate())].join('-');
93+
const time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join('-');
94+
return `${date}_${time}`;
95+
}
96+
97+
const getComment = (f) => new Promise((_resolve) => {
98+
const timeout = setTimeout(() => {resolve('')}, 500); // Don't let it stall
99+
100+
const resolve = (value) => {
101+
_resolve(value);
102+
clearTimeout(timeout);
103+
pipeline.destroy(); // Stop once we get the value
104+
};
105+
106+
const pipeline = fs.createReadStream(path.join(SNAPSHOT_DIR, f))
107+
.pipe(parser())
108+
.pipe(streamObject())
109+
110+
// Assuming comment is positioned first in the json
111+
112+
pipeline.on('data', ({key, value}) => {
113+
if(key === 'comment') resolve(value);
114+
else resolve('');
115+
});
116+
117+
pipeline.on('close', () => {
118+
resolve('');
119+
});
120+
121+
pipeline.on('error', () => {
122+
resolve('')
123+
});
124+
});

‎snapshot-server/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "snapshot-server",
3+
"version": "1.0.0",
4+
"description": "A little app (webpage + server) to take snapshot of the local storage and IndexedDB of the browser",
5+
"main": "index.js",
6+
"scripts": {
7+
"dev": "nodemon index.js",
8+
"start": "node ."
9+
},
10+
"keywords": [],
11+
"author": "",
12+
"license": "ISC",
13+
"dependencies": {
14+
"express": "^5.1.0",
15+
"stream-json": "^1.9.1"
16+
},
17+
"devDependencies": {
18+
"nodemon": "^3.1.10"
19+
}
20+
}

0 commit comments

Comments
 (0)