instbyte 1.9.1 → 1.9.3
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/LICENSE +21 -21
- package/README.md +360 -230
- package/bin/instbyte.js +27 -27
- package/client/assets/lucide.min.js +12 -0
- package/client/css/app.css +1972 -1972
- package/client/index.html +94 -94
- package/client/js/app.js +1648 -1613
- package/package.json +11 -4
- package/server/cleanup.js +27 -26
- package/server/config.js +69 -69
- package/server/db.js +60 -60
- package/server/server.js +871 -865
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instbyte",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.3",
|
|
4
4
|
"description": "A self-hosted LAN sharing utility for fast, frictionless file, link, and snippet exchange across devices — no cloud required.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "bin/instbyte.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"instbyte": "bin/instbyte.js"
|
|
8
8
|
},
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
"client/"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"start": "node
|
|
16
|
-
"dev": "nodemon
|
|
15
|
+
"start": "node bin/instbyte.js",
|
|
16
|
+
"dev": "nodemon bin/instbyte.js",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest"
|
|
17
19
|
},
|
|
18
20
|
"keywords": [
|
|
19
21
|
"lan",
|
|
@@ -44,5 +46,10 @@
|
|
|
44
46
|
"sharp": "^0.33.2",
|
|
45
47
|
"socket.io": "^4.6.1",
|
|
46
48
|
"sqlite3": "^5.1.6"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"nodemon": "^3.1.0",
|
|
52
|
+
"supertest": "^7.2.2",
|
|
53
|
+
"vitest": "^2.1.8"
|
|
47
54
|
}
|
|
48
55
|
}
|
package/server/cleanup.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
const db = require("./db");
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const config = require("./config");
|
|
5
|
-
|
|
6
|
-
setInterval(() => {
|
|
7
|
-
if (config.storage.retention === null) return;
|
|
8
|
-
const cutoff = Date.now() - config.storage.retention;
|
|
9
|
-
|
|
10
|
-
db.all(
|
|
11
|
-
`SELECT * FROM items WHERE created_at < ? AND pinned = 0`,
|
|
12
|
-
[cutoff],
|
|
13
|
-
(err, rows) => {
|
|
14
|
-
rows
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
const db = require("./db");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const config = require("./config");
|
|
5
|
+
|
|
6
|
+
setInterval(() => {
|
|
7
|
+
if (config.storage.retention === null) return;
|
|
8
|
+
const cutoff = Date.now() - config.storage.retention;
|
|
9
|
+
|
|
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
|
+
}
|
|
22
|
+
|
|
23
|
+
db.run(`DELETE FROM items WHERE id=?`, [item.id]);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}, 10 * 60 * 1000);
|
package/server/config.js
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
// These are the defaults — what you get with zero config file
|
|
5
|
-
const defaults = {
|
|
6
|
-
server: {
|
|
7
|
-
port: 3000,
|
|
8
|
-
},
|
|
9
|
-
auth: {
|
|
10
|
-
passphrase: "" // empty = no password required
|
|
11
|
-
},
|
|
12
|
-
storage: {
|
|
13
|
-
maxFileSize: 2 * 1024 * 1024 * 1024, // 2GB in bytes
|
|
14
|
-
retention: 24 * 60 * 60 * 1000, // 24 hours in ms
|
|
15
|
-
},
|
|
16
|
-
branding: {
|
|
17
|
-
appName: "Instbyte",
|
|
18
|
-
logoPath: "",
|
|
19
|
-
faviconPath: "",
|
|
20
|
-
primaryColor: "#111827"
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function parseFileSize(val) {
|
|
25
|
-
if (typeof val === "number") return val;
|
|
26
|
-
const units = { KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 };
|
|
27
|
-
const match = String(val).match(/^(\d+(\.\d+)?)\s*(KB|MB|GB)$/i);
|
|
28
|
-
if (!match) return defaults.storage.maxFileSize;
|
|
29
|
-
return parseFloat(match[1]) * units[match[3].toUpperCase()];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function parseRetention(val) {
|
|
33
|
-
if (String(val).toLowerCase() === "never") return null;
|
|
34
|
-
if (typeof val === "number") return val;
|
|
35
|
-
const units = { h: 3600000, d: 86400000 };
|
|
36
|
-
const match = String(val).match(/^(\d+)(h|d)$/i);
|
|
37
|
-
if (!match) return defaults.storage.retention;
|
|
38
|
-
return parseInt(match[1]) * units[match[2].toLowerCase()];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function loadConfig() {
|
|
42
|
-
const configPath = path.join(process.cwd(), "instbyte.config.json");
|
|
43
|
-
let userConfig = {};
|
|
44
|
-
|
|
45
|
-
if (fs.existsSync(configPath)) {
|
|
46
|
-
try {
|
|
47
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
48
|
-
userConfig = JSON.parse(raw);
|
|
49
|
-
console.log("Config loaded from instbyte.config.json");
|
|
50
|
-
} catch (e) {
|
|
51
|
-
console.warn("Warning: instbyte.config.json is invalid JSON, using defaults.");
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Deep merge user config over defaults
|
|
56
|
-
const config = {
|
|
57
|
-
server: { ...defaults.server, ...(userConfig.server || {}) },
|
|
58
|
-
auth: { ...defaults.auth, ...(userConfig.auth || {}) },
|
|
59
|
-
storage: { ...defaults.storage, ...(userConfig.storage || {}) },
|
|
60
|
-
branding: { ...defaults.branding, ...(userConfig.branding || {}) }
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Parse human-readable values like "500MB" or "48h"
|
|
64
|
-
config.storage.maxFileSize = parseFileSize(config.storage.maxFileSize);
|
|
65
|
-
config.storage.retention = parseRetention(config.storage.retention);
|
|
66
|
-
|
|
67
|
-
return config;
|
|
68
|
-
}
|
|
69
|
-
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
// These are the defaults — what you get with zero config file
|
|
5
|
+
const defaults = {
|
|
6
|
+
server: {
|
|
7
|
+
port: 3000,
|
|
8
|
+
},
|
|
9
|
+
auth: {
|
|
10
|
+
passphrase: "" // empty = no password required
|
|
11
|
+
},
|
|
12
|
+
storage: {
|
|
13
|
+
maxFileSize: 2 * 1024 * 1024 * 1024, // 2GB in bytes
|
|
14
|
+
retention: 24 * 60 * 60 * 1000, // 24 hours in ms
|
|
15
|
+
},
|
|
16
|
+
branding: {
|
|
17
|
+
appName: "Instbyte",
|
|
18
|
+
logoPath: "",
|
|
19
|
+
faviconPath: "",
|
|
20
|
+
primaryColor: "#111827"
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function parseFileSize(val) {
|
|
25
|
+
if (typeof val === "number") return val;
|
|
26
|
+
const units = { KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3 };
|
|
27
|
+
const match = String(val).match(/^(\d+(\.\d+)?)\s*(KB|MB|GB)$/i);
|
|
28
|
+
if (!match) return defaults.storage.maxFileSize;
|
|
29
|
+
return parseFloat(match[1]) * units[match[3].toUpperCase()];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseRetention(val) {
|
|
33
|
+
if (String(val).toLowerCase() === "never") return null;
|
|
34
|
+
if (typeof val === "number") return val;
|
|
35
|
+
const units = { h: 3600000, d: 86400000 };
|
|
36
|
+
const match = String(val).match(/^(\d+)(h|d)$/i);
|
|
37
|
+
if (!match) return defaults.storage.retention;
|
|
38
|
+
return parseInt(match[1]) * units[match[2].toLowerCase()];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function loadConfig() {
|
|
42
|
+
const configPath = path.join(process.cwd(), "instbyte.config.json");
|
|
43
|
+
let userConfig = {};
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(configPath)) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
48
|
+
userConfig = JSON.parse(raw);
|
|
49
|
+
console.log("Config loaded from instbyte.config.json");
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.warn("Warning: instbyte.config.json is invalid JSON, using defaults.");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Deep merge user config over defaults
|
|
56
|
+
const config = {
|
|
57
|
+
server: { ...defaults.server, ...(userConfig.server || {}) },
|
|
58
|
+
auth: { ...defaults.auth, ...(userConfig.auth || {}) },
|
|
59
|
+
storage: { ...defaults.storage, ...(userConfig.storage || {}) },
|
|
60
|
+
branding: { ...defaults.branding, ...(userConfig.branding || {}) }
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Parse human-readable values like "500MB" or "48h"
|
|
64
|
+
config.storage.maxFileSize = parseFileSize(config.storage.maxFileSize);
|
|
65
|
+
config.storage.retention = parseRetention(config.storage.retention);
|
|
66
|
+
|
|
67
|
+
return config;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
70
|
module.exports = loadConfig();
|
package/server/db.js
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
const sqlite3 = require("sqlite3").verbose();
|
|
2
|
-
const path = require("path");
|
|
3
|
-
|
|
4
|
-
const dbPath = process.env.INSTBYTE_DATA
|
|
5
|
-
? path.join(process.env.INSTBYTE_DATA, "db.sqlite")
|
|
6
|
-
: path.join(__dirname, "../db.sqlite");
|
|
7
|
-
|
|
8
|
-
const db = new sqlite3.Database(dbPath);
|
|
9
|
-
db.configure("busyTimeout", 5000);
|
|
10
|
-
|
|
11
|
-
db.serialize(() => {
|
|
12
|
-
|
|
13
|
-
// ITEMS TABLE
|
|
14
|
-
db.run(`
|
|
15
|
-
CREATE TABLE IF NOT EXISTS items (
|
|
16
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17
|
-
type TEXT,
|
|
18
|
-
content TEXT,
|
|
19
|
-
filename TEXT,
|
|
20
|
-
size INTEGER DEFAULT 0,
|
|
21
|
-
channel TEXT,
|
|
22
|
-
uploader TEXT,
|
|
23
|
-
pinned INTEGER DEFAULT 0,
|
|
24
|
-
created_at INTEGER
|
|
25
|
-
)
|
|
26
|
-
`);
|
|
27
|
-
|
|
28
|
-
// CHANNELS TABLE
|
|
29
|
-
db.run(`
|
|
30
|
-
CREATE TABLE IF NOT EXISTS channels (
|
|
31
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
-
name TEXT UNIQUE,
|
|
33
|
-
pinned INTEGER DEFAULT 0
|
|
34
|
-
)
|
|
35
|
-
`);
|
|
36
|
-
|
|
37
|
-
db.run(`ALTER TABLE items ADD COLUMN size INTEGER DEFAULT 0`, () => { });
|
|
38
|
-
db.run(`ALTER TABLE channels ADD COLUMN pinned INTEGER DEFAULT 0`, () => { });
|
|
39
|
-
|
|
40
|
-
db.run(`ALTER TABLE items ADD COLUMN title TEXT DEFAULT ''`, () => { });
|
|
41
|
-
db.run(`ALTER TABLE items ADD COLUMN edited_at INTEGER DEFAULT NULL`, () => { });
|
|
42
|
-
|
|
43
|
-
// Insert default channels if empty
|
|
44
|
-
db.get("SELECT COUNT(*) as count FROM channels", (err, row) => {
|
|
45
|
-
if (err) {
|
|
46
|
-
console.error("Channel count error:", err);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (row.count === 0) {
|
|
51
|
-
const defaults = ["general", "projects", "assets", "temp"];
|
|
52
|
-
defaults.forEach(name => {
|
|
53
|
-
db.run("INSERT INTO channels (name) VALUES (?)", [name]);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
module.exports = db;
|
|
1
|
+
const sqlite3 = require("sqlite3").verbose();
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const dbPath = process.env.INSTBYTE_DATA
|
|
5
|
+
? path.join(process.env.INSTBYTE_DATA, "db.sqlite")
|
|
6
|
+
: path.join(__dirname, "../db.sqlite");
|
|
7
|
+
|
|
8
|
+
const db = new sqlite3.Database(dbPath);
|
|
9
|
+
db.configure("busyTimeout", 5000);
|
|
10
|
+
|
|
11
|
+
db.serialize(() => {
|
|
12
|
+
|
|
13
|
+
// ITEMS TABLE
|
|
14
|
+
db.run(`
|
|
15
|
+
CREATE TABLE IF NOT EXISTS items (
|
|
16
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17
|
+
type TEXT,
|
|
18
|
+
content TEXT,
|
|
19
|
+
filename TEXT,
|
|
20
|
+
size INTEGER DEFAULT 0,
|
|
21
|
+
channel TEXT,
|
|
22
|
+
uploader TEXT,
|
|
23
|
+
pinned INTEGER DEFAULT 0,
|
|
24
|
+
created_at INTEGER
|
|
25
|
+
)
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
// CHANNELS TABLE
|
|
29
|
+
db.run(`
|
|
30
|
+
CREATE TABLE IF NOT EXISTS channels (
|
|
31
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
+
name TEXT UNIQUE,
|
|
33
|
+
pinned INTEGER DEFAULT 0
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
|
|
37
|
+
db.run(`ALTER TABLE items ADD COLUMN size INTEGER DEFAULT 0`, () => { });
|
|
38
|
+
db.run(`ALTER TABLE channels ADD COLUMN pinned INTEGER DEFAULT 0`, () => { });
|
|
39
|
+
|
|
40
|
+
db.run(`ALTER TABLE items ADD COLUMN title TEXT DEFAULT ''`, () => { });
|
|
41
|
+
db.run(`ALTER TABLE items ADD COLUMN edited_at INTEGER DEFAULT NULL`, () => { });
|
|
42
|
+
|
|
43
|
+
// Insert default channels if empty
|
|
44
|
+
db.get("SELECT COUNT(*) as count FROM channels", (err, row) => {
|
|
45
|
+
if (err) {
|
|
46
|
+
console.error("Channel count error:", err);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (row.count === 0) {
|
|
51
|
+
const defaults = ["general", "projects", "assets", "temp"];
|
|
52
|
+
defaults.forEach(name => {
|
|
53
|
+
db.run("INSERT INTO channels (name) VALUES (?)", [name]);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
module.exports = db;
|