@mblaney/holster 2.1.1 → 2.1.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 +9 -14
- package/build/holster.js +1 -1
- package/build/holster.js.map +3 -3
- package/dist/holster.js +3 -3
- package/dist/holster.js.map +3 -3
- package/package.json +6 -2
- package/src/wire.js +9 -5
- package/ts/src/wire.ts +9 -5
- package/utils/user-storage.js +86 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mblaney/holster",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Holster is a service that allows synchronising data between devices using Node, Deno, Bun or the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -14,11 +14,15 @@
|
|
|
14
14
|
"type-check": "tsc -p tsconfig.json",
|
|
15
15
|
"type-check:tests": "tsc -p tsconfig.test.json"
|
|
16
16
|
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"holster-user-storage": "utils/user-storage.js"
|
|
19
|
+
},
|
|
17
20
|
"files": [
|
|
18
21
|
"src",
|
|
19
22
|
"ts/src",
|
|
20
23
|
"build",
|
|
21
|
-
"dist"
|
|
24
|
+
"dist",
|
|
25
|
+
"utils"
|
|
22
26
|
],
|
|
23
27
|
"keywords": [],
|
|
24
28
|
"author": "Malcolm Blaney",
|
package/src/wire.js
CHANGED
|
@@ -297,19 +297,23 @@ const Wire = opt => {
|
|
|
297
297
|
`[HOLSTER] Per-user storage limits enabled (${userCount} user${userCount !== 1 ? "s" : ""}, default: ${defLimitStr})`,
|
|
298
298
|
)
|
|
299
299
|
const sorted = [...userStorage.entries()].sort(([, a], [, b]) => b - a)
|
|
300
|
-
const
|
|
300
|
+
const logUser = pub => {
|
|
301
301
|
const total = userStorage.get(pub) ?? 0
|
|
302
302
|
const limit = userLimits.get(pub) ?? defaultLimit
|
|
303
303
|
const limitStr = limit === 0 ? "blocked" : formatBytes(limit)
|
|
304
|
-
|
|
304
|
+
console.log(
|
|
305
|
+
` ${pub.slice(0, 16)}... used: ${formatBytes(total)} / limit: ${limitStr}`,
|
|
306
|
+
)
|
|
305
307
|
}
|
|
306
308
|
if (userCount <= 5) {
|
|
307
|
-
sorted.forEach(([pub]) =>
|
|
309
|
+
sorted.forEach(([pub]) => logUser(pub))
|
|
308
310
|
} else {
|
|
309
311
|
console.log(" Top 5 by storage used:")
|
|
310
|
-
sorted.slice(0, 5).forEach(([pub]) =>
|
|
311
|
-
console.log(` Run \`node examples/user-storage.js\` to view all users`)
|
|
312
|
+
sorted.slice(0, 5).forEach(([pub]) => logUser(pub))
|
|
312
313
|
}
|
|
314
|
+
console.log(
|
|
315
|
+
" Run `npx holster-user-storage` to view full keys and all users",
|
|
316
|
+
)
|
|
313
317
|
}
|
|
314
318
|
|
|
315
319
|
// The check function is required because user data must provide a public key
|
package/ts/src/wire.ts
CHANGED
|
@@ -353,19 +353,23 @@ const Wire = (opt: HolsterOptions): WireAPI => {
|
|
|
353
353
|
`[HOLSTER] Per-user storage limits enabled (${userCount} user${userCount !== 1 ? "s" : ""}, default: ${defLimitStr})`,
|
|
354
354
|
)
|
|
355
355
|
const sorted = [...userStorage.entries()].sort(([, a], [, b]) => b - a)
|
|
356
|
-
const
|
|
356
|
+
const logUser = (pub: string): void => {
|
|
357
357
|
const total = userStorage.get(pub) ?? 0
|
|
358
358
|
const limit = userLimits.get(pub) ?? defaultLimit
|
|
359
359
|
const limitStr = limit === 0 ? "blocked" : formatBytes(limit)
|
|
360
|
-
|
|
360
|
+
console.log(
|
|
361
|
+
` ${pub.slice(0, 16)}... used: ${formatBytes(total)} / limit: ${limitStr}`,
|
|
362
|
+
)
|
|
361
363
|
}
|
|
362
364
|
if (userCount <= 5) {
|
|
363
|
-
sorted.forEach(([pub]) =>
|
|
365
|
+
sorted.forEach(([pub]) => logUser(pub))
|
|
364
366
|
} else {
|
|
365
367
|
console.log(" Top 5 by storage used:")
|
|
366
|
-
sorted.slice(0, 5).forEach(([pub]) =>
|
|
367
|
-
console.log(` Run \`node examples/user-storage.js\` to view all users`)
|
|
368
|
+
sorted.slice(0, 5).forEach(([pub]) => logUser(pub))
|
|
368
369
|
}
|
|
370
|
+
console.log(
|
|
371
|
+
" Run `npx holster-user-storage` to view full keys and all users",
|
|
372
|
+
)
|
|
369
373
|
}
|
|
370
374
|
|
|
371
375
|
const check = async (
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Displays per-user storage stats for a Holster relay with userLimit enabled.
|
|
3
|
+
// Usage: node utils/user-storage.js [dir] [--limit N] [--offset N]
|
|
4
|
+
|
|
5
|
+
import {readFileSync, existsSync} from "node:fs"
|
|
6
|
+
import {resolve} from "node:path"
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2)
|
|
9
|
+
const dir = resolve(args.find(a => !a.startsWith("--")) || ".")
|
|
10
|
+
|
|
11
|
+
if (!existsSync(dir)) {
|
|
12
|
+
console.error(`Error: directory not found: ${dir}`)
|
|
13
|
+
process.exit(1)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const getFlag = (name, defaultVal) => {
|
|
17
|
+
const idx = args.indexOf(`--${name}`)
|
|
18
|
+
if (idx === -1) return defaultVal
|
|
19
|
+
const val = parseInt(args[idx + 1], 10)
|
|
20
|
+
return isNaN(val) ? defaultVal : val
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const limit = getFlag("limit", 10)
|
|
24
|
+
const offset = getFlag("offset", 0)
|
|
25
|
+
|
|
26
|
+
const readJSON = file => {
|
|
27
|
+
try {
|
|
28
|
+
return {data: JSON.parse(readFileSync(`${dir}/${file}`, "utf8"))}
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err.code === "ENOENT") return {missing: true}
|
|
31
|
+
return {error: err.message}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const formatBytes = bytes => {
|
|
36
|
+
if (bytes < 1024) return `${bytes} B`
|
|
37
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`
|
|
38
|
+
if (bytes < 1073741824) return `${(bytes / 1048576).toFixed(1)} MB`
|
|
39
|
+
return `${(bytes / 1073741824).toFixed(1)} GB`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_STORAGE_LIMIT = 1 // MB
|
|
43
|
+
|
|
44
|
+
const userStorage = readJSON(".user_storage.json")
|
|
45
|
+
const userLimit = readJSON(".user_limit.json")
|
|
46
|
+
|
|
47
|
+
console.log(`Per-user storage report for ${dir}:`)
|
|
48
|
+
|
|
49
|
+
if (userStorage.missing) {
|
|
50
|
+
console.log(" No user storage recorded yet.")
|
|
51
|
+
} else if (userStorage.error) {
|
|
52
|
+
console.error(` Error reading .user_storage.json: ${userStorage.error}`)
|
|
53
|
+
process.exit(1)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (userLimit.missing) {
|
|
57
|
+
console.log(" No per-user limits defined.")
|
|
58
|
+
} else if (userLimit.error) {
|
|
59
|
+
console.error(` Error reading .user_limit.json: ${userLimit.error}`)
|
|
60
|
+
process.exit(1)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const users = Object.entries(userStorage.data || {})
|
|
64
|
+
.map(([pub, total]) => {
|
|
65
|
+
return {pub, total}
|
|
66
|
+
})
|
|
67
|
+
.sort((a, b) => b.total - a.total)
|
|
68
|
+
|
|
69
|
+
const page = users.slice(offset, offset + limit)
|
|
70
|
+
|
|
71
|
+
console.log(` Total users: ${users.length}`)
|
|
72
|
+
if (page.length > 0) {
|
|
73
|
+
console.log(
|
|
74
|
+
` Showing full public keys for ${page.length} user${page.length !== 1 ? "s" : ""} (offset: ${offset}, limit: ${limit})`,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const data = userLimit.data || {}
|
|
79
|
+
for (const {pub, total} of page) {
|
|
80
|
+
const pubLimit =
|
|
81
|
+
typeof data[pub] === "number" ? data[pub] : DEFAULT_STORAGE_LIMIT
|
|
82
|
+
const limitStr =
|
|
83
|
+
pubLimit === 0 ? "0 B (blocked)" : formatBytes(pubLimit * 1048576)
|
|
84
|
+
console.log(` ${pub}`)
|
|
85
|
+
console.log(` used: ${formatBytes(total)} / limit: ${limitStr}`)
|
|
86
|
+
}
|