@1sat/cli 0.0.120 → 0.0.121

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.
@@ -0,0 +1,106 @@
1
+ /**
2
+ * File-backed `IPermissionStore` for `1sat serve wallet-api`.
3
+ *
4
+ * One JSON document per wallet (`<dataDir>/permissions-<chain>.json`, mode
5
+ * 0600) keyed by the canonical permission key string, mirroring the
6
+ * IndexedDB store in `@1sat/wallet-browser`: `putGrant` upserts by key,
7
+ * `findGrant` is an exact-key lookup, `listGrants` filters by originator
8
+ * and/or type, and `deleteAllForOriginator` returns how many grants it
9
+ * removed. Every operation reads the file fresh and writes it back
10
+ * atomically (temp file + rename), so several instances over the same path
11
+ * observe each other's writes.
12
+ */
13
+
14
+ import {
15
+ existsSync,
16
+ mkdirSync,
17
+ readFileSync,
18
+ renameSync,
19
+ writeFileSync,
20
+ } from 'node:fs'
21
+ import { dirname, join } from 'node:path'
22
+ import {
23
+ type IPermissionStore,
24
+ type ListGrantsFilter,
25
+ type PermissionKey,
26
+ type StoredGrant,
27
+ permissionKeyToString,
28
+ } from '@1sat/wallet'
29
+
30
+ const FILE_VERSION = 1
31
+
32
+ interface PermissionFile {
33
+ version: number
34
+ grants: Record<string, StoredGrant>
35
+ }
36
+
37
+ /** Where the CLI keeps wallet-api grants for a chain. */
38
+ export function permissionStorePath(dataDir: string, chain: string): string {
39
+ return join(dataDir, `permissions-${chain}.json`)
40
+ }
41
+
42
+ export class FilePermissionStore implements IPermissionStore {
43
+ constructor(private readonly path: string) {}
44
+
45
+ async findGrant(key: PermissionKey): Promise<StoredGrant | null> {
46
+ return this.load().grants[permissionKeyToString(key)] ?? null
47
+ }
48
+
49
+ async putGrant(grant: StoredGrant): Promise<void> {
50
+ const file = this.load()
51
+ file.grants[permissionKeyToString(grant.key)] = grant
52
+ this.save(file)
53
+ }
54
+
55
+ async deleteGrant(key: PermissionKey): Promise<void> {
56
+ const file = this.load()
57
+ const id = permissionKeyToString(key)
58
+ if (!(id in file.grants)) return
59
+ delete file.grants[id]
60
+ this.save(file)
61
+ }
62
+
63
+ async deleteAllForOriginator(originator: string): Promise<number> {
64
+ const file = this.load()
65
+ let removed = 0
66
+ for (const [id, grant] of Object.entries(file.grants)) {
67
+ if (grant.key.originator === originator) {
68
+ delete file.grants[id]
69
+ removed++
70
+ }
71
+ }
72
+ if (removed > 0) this.save(file)
73
+ return removed
74
+ }
75
+
76
+ async listGrants(filter?: ListGrantsFilter): Promise<StoredGrant[]> {
77
+ const out: StoredGrant[] = []
78
+ for (const grant of Object.values(this.load().grants)) {
79
+ if (filter?.originator && grant.key.originator !== filter.originator)
80
+ continue
81
+ if (filter?.type && grant.key.type !== filter.type) continue
82
+ out.push(grant)
83
+ }
84
+ return out
85
+ }
86
+
87
+ private load(): PermissionFile {
88
+ if (!existsSync(this.path)) return { version: FILE_VERSION, grants: {} }
89
+ const raw = readFileSync(this.path, 'utf8')
90
+ const parsed = JSON.parse(raw) as Partial<PermissionFile>
91
+ if (parsed.version !== FILE_VERSION) {
92
+ throw new Error(
93
+ `Unsupported permission store version ${String(parsed.version)} in ${this.path}`,
94
+ )
95
+ }
96
+ return { version: FILE_VERSION, grants: parsed.grants ?? {} }
97
+ }
98
+
99
+ private save(file: PermissionFile): void {
100
+ const dir = dirname(this.path)
101
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 })
102
+ const tmp = `${this.path}.${process.pid}.tmp`
103
+ writeFileSync(tmp, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 })
104
+ renameSync(tmp, this.path)
105
+ }
106
+ }