instbyte 1.12.1 → 1.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,10 +5,14 @@
5
5
  <h1 align="center">Instbyte</h1>
6
6
 
7
7
  <p align="center">
8
- <a href="https://www.npmjs.com/package/instbyte"><img src="https://img.shields.io/npm/v/instbyte" alt="npm version"></a>
9
- <a href="https://github.com/mohitgauniyal/instbyte/blob/main/LICENSE">
10
- <img src="https://img.shields.io/github/license/mohitgauniyal/instbyte" alt="license">
11
- </a>
8
+ <a href="https://www.npmjs.com/package/instbyte"><img src="https://img.shields.io/npm/v/instbyte?style=flat-square" alt="npm version"></a>
9
+ <a href="https://github.com/mohitgauniyal/instbyte/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/mohitgauniyal/instbyte/ci.yml?branch=main&style=flat-square&label=build" alt="build status"></a>
10
+ <a href="https://www.npmjs.com/package/instbyte"><img src="https://img.shields.io/node/v/instbyte?style=flat-square" alt="node version"></a>
11
+ <a href="https://www.npmjs.com/package/instbyte"><img src="https://img.shields.io/npm/dm/instbyte?style=flat-square" alt="npm downloads"></a>
12
+ <a href="https://github.com/mohitgauniyal/instbyte"><img src="https://img.shields.io/github/languages/code-size/mohitgauniyal/instbyte?style=flat-square" alt="code size"></a>
13
+ <a href="https://github.com/mohitgauniyal/instbyte/commits/main"><img src="https://img.shields.io/github/last-commit/mohitgauniyal/instbyte?style=flat-square" alt="last commit"></a>
14
+ <a href="https://github.com/mohitgauniyal/instbyte/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mohitgauniyal/instbyte?style=flat-square" alt="license"></a>
15
+ <a href="https://github.com/mohitgauniyal/instbyte/issues"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs Welcome"></a>
12
16
  </p>
13
17
 
14
18
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instbyte",
3
- "version": "1.12.1",
3
+ "version": "1.12.2",
4
4
  "description": "A self-hosted LAN sharing utility for fast, frictionless file, link, and snippet exchange across devices — no cloud required.",
5
5
  "main": "bin/instbyte.js",
6
6
  "bin": {
package/server/cleanup.js CHANGED
@@ -2,26 +2,39 @@ const db = require("./db");
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
  const config = require("./config");
5
+ const seenBy = require("./seen");
5
6
 
6
- setInterval(() => {
7
- if (config.storage.retention === null) return;
8
- const cutoff = Date.now() - config.storage.retention;
7
+ // One retention pass: delete expired, unpinned items along with their files
8
+ // and any stale in-memory seenBy entries. Returns a promise so tests can await
9
+ // a single pass; the interval below just fires it and ignores the result.
10
+ function purgeExpired() {
11
+ return new Promise((resolve) => {
12
+ if (config.storage.retention === null) return resolve();
13
+ const cutoff = Date.now() - config.storage.retention;
9
14
 
10
- db.all(
11
- `SELECT * FROM items WHERE created_at < ? AND pinned = 0`,
12
- [cutoff],
13
- (err, rows) => {
14
- if (err || !rows) return;
15
- rows.forEach((item) => {
16
- if (item.filename) {
17
- const uploadsDir = process.env.INSTBYTE_UPLOADS
18
- || path.join(__dirname, "../uploads");
19
- const filePath = path.join(uploadsDir, item.filename);
20
- if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
21
- }
15
+ db.all(
16
+ `SELECT * FROM items WHERE created_at < ? AND pinned = 0`,
17
+ [cutoff],
18
+ (err, rows) => {
19
+ if (err || !rows) return resolve();
20
+ rows.forEach((item) => {
21
+ if (item.filename) {
22
+ const uploadsDir = process.env.INSTBYTE_UPLOADS
23
+ || path.join(__dirname, "../uploads");
24
+ const filePath = path.join(uploadsDir, item.filename);
25
+ if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
26
+ }
22
27
 
23
- db.run(`DELETE FROM items WHERE id=?`, [item.id]);
24
- });
25
- }
26
- );
27
- }, 10 * 60 * 1000);
28
+ db.run(`DELETE FROM items WHERE id=?`, [item.id]);
29
+ seenBy.delete(item.id); // drop stale in-memory seen tracking
30
+ });
31
+ resolve();
32
+ }
33
+ );
34
+ });
35
+ }
36
+
37
+ const interval = setInterval(purgeExpired, 10 * 60 * 1000);
38
+ interval.unref(); // don't keep the process alive just for cleanup
39
+
40
+ module.exports = { purgeExpired };
package/server/seen.js ADDED
@@ -0,0 +1,5 @@
1
+ // Shared in-memory "seen by" tracking — item id → Set of viewer names.
2
+ // Kept in its own module so both the request handlers (server.js) and the
3
+ // retention job (cleanup.js) mutate the same Map without a circular require.
4
+ // Resets on server restart, no DB needed.
5
+ module.exports = new Map();
package/server/server.js CHANGED
@@ -121,7 +121,8 @@ const MAGIC_NUMBERS = {
121
121
  webp: [Buffer.from([0x52, 0x49, 0x46, 0x46])],
122
122
  pdf: [Buffer.from([0x25, 0x50, 0x44, 0x46])],
123
123
  zip: [Buffer.from([0x50, 0x4B, 0x03, 0x04]), Buffer.from([0x50, 0x4B, 0x05, 0x06])],
124
- mp4: [Buffer.from([0x00, 0x00, 0x00, 0x18]), Buffer.from([0x00, 0x00, 0x00, 0x20])],
124
+ // mp4 is handled separately in checkMagicNumber its signature is the
125
+ // "ftyp" marker at bytes 4-7, not a fixed sequence at the start.
125
126
  };
126
127
 
127
128
  // Extension → magic group mapping
@@ -147,18 +148,25 @@ function checkMagicNumber(filePath, ext) {
147
148
  const group = EXT_TO_MAGIC[ext.toLowerCase()];
148
149
  if (!group) return true; // no check defined for this type — allow through
149
150
 
150
- const signatures = MAGIC_NUMBERS[group];
151
- if (!signatures) return true;
152
-
151
+ let buf;
153
152
  try {
154
153
  const fd = fs.openSync(filePath, 'r');
155
- const buf = Buffer.alloc(8);
154
+ buf = Buffer.alloc(8);
156
155
  fs.readSync(fd, buf, 0, 8, 0);
157
156
  fs.closeSync(fd);
158
- return signatures.some(sig => buf.slice(0, sig.length).equals(sig));
159
157
  } catch (e) {
160
158
  return false; // can't read → reject
161
159
  }
160
+
161
+ // MP4: bytes 0-3 are the atom size (which varies between files), so the real
162
+ // file-type signature is the "ftyp" marker at bytes 4-7. Check that instead.
163
+ if (group === 'mp4') {
164
+ return buf.slice(4, 8).toString('latin1') === 'ftyp';
165
+ }
166
+
167
+ const signatures = MAGIC_NUMBERS[group];
168
+ if (!signatures) return true;
169
+ return signatures.some(sig => buf.slice(0, sig.length).equals(sig));
162
170
  }
163
171
 
164
172
  function hexToHsl(hex) {
@@ -684,7 +692,7 @@ app.delete("/channels/:name", (req, res) => {
684
692
  db.all("SELECT * FROM items WHERE channel=?", [name], (err, rows) => {
685
693
  rows.forEach(item => {
686
694
  if (item.filename) {
687
- const filePath = path.join(__dirname, "../uploads", item.filename);
695
+ const filePath = path.join(UPLOADS_DIR, item.filename);
688
696
  if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
689
697
  }
690
698
  });
@@ -967,9 +975,9 @@ app.get("/logo-dynamic.png", (req, res) => {
967
975
  /* ============================
968
976
  SOCKET CONNECTION LOGGING
969
977
  ============================ */
970
- // in-memory seen tracking — item id → Set of socket ids
971
- // resets on server restart, no DB needed
972
- const seenBy = new Map();
978
+ // in-memory seen tracking — item id → Set of viewer names.
979
+ // Shared with cleanup.js so expired items get purged from it too.
980
+ const seenBy = require("./seen");
973
981
 
974
982
  // Broadcast state — null when IDLE, populated when LIVE
975
983
  let currentBroadcast = null;