Skip to content
This repository was archived by the owner on Dec 21, 2021. It is now read-only.

Commit 4f62e4b

Browse files
committed
fix: Reimplement browser group key persistence on top of idb.
1 parent 49c44cb commit 4f62e4b

File tree

8 files changed

+164
-184
lines changed

8 files changed

+164
-184
lines changed

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153
"debug": "^4.3.2",
154154
"env-paths": "^2.2.1",
155155
"eventemitter3": "^4.0.7",
156+
"idb-keyval": "^5.0.5",
156157
"lodash": "^4.17.21",
157158
"mem": "^8.1.1",
158159
"node-abort-controller": "^1.2.1",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { PersistentStore } from './GroupKeyStore'
2+
import { get, set, del, clear, keys, createStore } from 'idb-keyval'
3+
4+
export default class BrowserPersistentStore implements PersistentStore<string, string> {
5+
readonly clientId: string
6+
readonly streamId: string
7+
private store?: any
8+
9+
constructor({ clientId, streamId }: { clientId: string, streamId: string }) {
10+
this.streamId = encodeURIComponent(streamId)
11+
this.clientId = encodeURIComponent(clientId)
12+
this.store = createStore(`streamr-client::${clientId}::${streamId}`, 'GroupKeys')
13+
}
14+
15+
async has(key: string) {
16+
const val = await this.get(key)
17+
return val == null
18+
}
19+
20+
async get(key: string) {
21+
return get(key, this.store)
22+
}
23+
24+
async set(key: string, value: string) {
25+
const had = await this.has(key)
26+
await set(key, value, this.store)
27+
return had
28+
}
29+
30+
async delete(key: string) {
31+
if (!await this.has(key)) {
32+
return false
33+
}
34+
35+
await del(key, this.store)
36+
return true
37+
}
38+
39+
async clear() {
40+
const size = await this.size()
41+
await clear(this.store)
42+
return !!size
43+
}
44+
45+
async size() {
46+
const allKeys = await keys(this.store)
47+
return allKeys.length
48+
}
49+
50+
get [Symbol.toStringTag]() {
51+
return this.constructor.name
52+
}
53+
}

src/stream/encryption/BrowserStore.ts

Lines changed: 0 additions & 83 deletions
This file was deleted.

src/stream/encryption/GroupKeyStore.ts

Lines changed: 4 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
import envPaths from 'env-paths'
2-
import { dirname, join } from 'path'
3-
import { promises as fs } from 'fs'
4-
import { open, Database } from 'sqlite'
5-
import sqlite3 from 'sqlite3'
6-
71
import { GroupKey } from './Encryption'
8-
import { pOnce } from '../../utils'
2+
import ServerPersistentStore from './ServerPersistentStore'
93

10-
interface Storage<K, V> {
4+
export interface PersistentStore<K, V> {
115
get(key: K): Promise<V | undefined>
126
set(key: K, value: V): Promise<boolean>
137
has(key: K): Promise<boolean>
@@ -16,95 +10,6 @@ interface Storage<K, V> {
1610
size(): Promise<number>
1711
}
1812

19-
class ServerStorage implements Storage<string, string> {
20-
readonly clientId: string
21-
readonly streamId: string
22-
readonly dbFilePath: string
23-
private store?: Database
24-
private error?: Error
25-
26-
constructor({ clientId, streamId }: { clientId: string, streamId: string }) {
27-
this.streamId = encodeURIComponent(streamId)
28-
this.clientId = encodeURIComponent(clientId)
29-
const paths = envPaths('streamr-client')
30-
const dbFilePath = join(paths.data, clientId, 'GroupKeys.db')
31-
this.dbFilePath = dbFilePath
32-
33-
this.init = pOnce(this.init.bind(this))
34-
}
35-
36-
async init() {
37-
try {
38-
await fs.mkdir(dirname(this.dbFilePath), { recursive: true })
39-
// open the database
40-
const store = await open({
41-
filename: this.dbFilePath,
42-
driver: sqlite3.Database
43-
})
44-
await store.exec(`CREATE TABLE IF NOT EXISTS GroupKeys (
45-
id TEXT,
46-
groupKey TEXT,
47-
streamId TEXT
48-
)`)
49-
await store.exec('CREATE UNIQUE INDEX IF NOT EXISTS name ON GroupKeys (id)')
50-
this.store = store
51-
} catch (err) {
52-
if (!this.error) {
53-
this.error = err
54-
}
55-
}
56-
57-
if (this.error) {
58-
throw this.error
59-
}
60-
}
61-
62-
async get(key: string) {
63-
await this.init()
64-
const value = await this.store!.get('SELECT groupKey FROM GroupKeys WHERE id = ? AND streamId = ?', key, this.streamId)
65-
return value?.groupKey
66-
}
67-
68-
async has(key: string) {
69-
await this.init()
70-
const value = await this.store!.get('SELECT COUNT(*) FROM GroupKeys WHERE id = ? AND streamId = ?', key, this.streamId)
71-
return value && value['COUNT(*)'] != null && value['COUNT(*)'] !== 0
72-
}
73-
74-
async set(key: string, value: string) {
75-
await this.init()
76-
const result = await this.store!.run('INSERT INTO GroupKeys VALUES ($id, $groupKey, $streamId) ON CONFLICT DO NOTHING', {
77-
$id: key,
78-
$groupKey: value,
79-
$streamId: this.streamId,
80-
})
81-
82-
return !!result?.changes
83-
}
84-
85-
async delete(key: string) {
86-
await this.init()
87-
const result = await this.store!.run('DELETE FROM GroupKeys WHERE id = ? AND streamId = ?', key, this.streamId)
88-
return !!result?.changes
89-
}
90-
91-
async clear() {
92-
await this.init()
93-
const result = await this.store!.run('DELETE FROM GroupKeys WHERE streamId = ?', this.streamId)
94-
return !!result?.changes
95-
}
96-
97-
async size() {
98-
await this.init()
99-
const size = await this.store!.get('SELECT COUNT(*) FROM GroupKeys WHERE streamId = ?;', this.streamId)
100-
return size && size['COUNT(*)']
101-
}
102-
103-
get [Symbol.toStringTag]() {
104-
return this.constructor.name
105-
}
106-
}
107-
10813
type GroupKeyId = string
10914

11015
type GroupKeyStoreOptions = {
@@ -114,9 +19,9 @@ type GroupKeyStoreOptions = {
11419
}
11520

11621
export class GroupKeyPersistence {
117-
store: Storage<string, string>
22+
store: PersistentStore<string, string>
11823
constructor({ clientId, streamId }: { clientId: string, streamId: string }) {
119-
this.store = new ServerStorage({ clientId, streamId })
24+
this.store = new ServerPersistentStore({ clientId, streamId })
12025
}
12126

12227
async has(groupKeyId: string) {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import envPaths from 'env-paths'
2+
import { dirname, join } from 'path'
3+
import { promises as fs } from 'fs'
4+
import { open, Database } from 'sqlite'
5+
import sqlite3 from 'sqlite3'
6+
7+
import { PersistentStore } from './GroupKeyStore'
8+
import { pOnce } from '../../utils'
9+
10+
export default class ServerPersistentStore implements PersistentStore<string, string> {
11+
readonly clientId: string
12+
readonly streamId: string
13+
readonly dbFilePath: string
14+
private store?: Database
15+
private error?: Error
16+
17+
constructor({ clientId, streamId }: { clientId: string, streamId: string }) {
18+
this.streamId = encodeURIComponent(streamId)
19+
this.clientId = encodeURIComponent(clientId)
20+
const paths = envPaths('streamr-client')
21+
const dbFilePath = join(paths.data, clientId, 'GroupKeys.db')
22+
this.dbFilePath = dbFilePath
23+
24+
this.init = pOnce(this.init.bind(this))
25+
}
26+
27+
async init() {
28+
try {
29+
await fs.mkdir(dirname(this.dbFilePath), { recursive: true })
30+
// open the database
31+
const store = await open({
32+
filename: this.dbFilePath,
33+
driver: sqlite3.Database
34+
})
35+
await store.exec(`CREATE TABLE IF NOT EXISTS GroupKeys (
36+
id TEXT,
37+
groupKey TEXT,
38+
streamId TEXT
39+
)`)
40+
await store.exec('CREATE UNIQUE INDEX IF NOT EXISTS name ON GroupKeys (id)')
41+
this.store = store
42+
} catch (err) {
43+
if (!this.error) {
44+
this.error = err
45+
}
46+
}
47+
48+
if (this.error) {
49+
throw this.error
50+
}
51+
}
52+
53+
async get(key: string) {
54+
await this.init()
55+
const value = await this.store!.get('SELECT groupKey FROM GroupKeys WHERE id = ? AND streamId = ?', key, this.streamId)
56+
return value?.groupKey
57+
}
58+
59+
async has(key: string) {
60+
await this.init()
61+
const value = await this.store!.get('SELECT COUNT(*) FROM GroupKeys WHERE id = ? AND streamId = ?', key, this.streamId)
62+
return value && value['COUNT(*)'] != null && value['COUNT(*)'] !== 0
63+
}
64+
65+
async set(key: string, value: string) {
66+
await this.init()
67+
const result = await this.store!.run('INSERT INTO GroupKeys VALUES ($id, $groupKey, $streamId) ON CONFLICT DO NOTHING', {
68+
$id: key,
69+
$groupKey: value,
70+
$streamId: this.streamId,
71+
})
72+
73+
return !!result?.changes
74+
}
75+
76+
async delete(key: string) {
77+
await this.init()
78+
const result = await this.store!.run('DELETE FROM GroupKeys WHERE id = ? AND streamId = ?', key, this.streamId)
79+
return !!result?.changes
80+
}
81+
82+
async clear() {
83+
await this.init()
84+
const result = await this.store!.run('DELETE FROM GroupKeys WHERE streamId = ?', this.streamId)
85+
return !!result?.changes
86+
}
87+
88+
async size() {
89+
await this.init()
90+
const size = await this.store!.get('SELECT COUNT(*) FROM GroupKeys WHERE streamId = ?;', this.streamId)
91+
return size && size['COUNT(*)']
92+
}
93+
94+
get [Symbol.toStringTag]() {
95+
return this.constructor.name
96+
}
97+
}

src/stream/encryption/ServerStore.ts

Whitespace-only changes.

webpack.config.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ module.exports = (env, argv) => {
103103
'node-fetch': path.resolve(__dirname, './src/shim/node-fetch.js'),
104104
'node-webcrypto-ossl': path.resolve(__dirname, 'src/shim/crypto.js'),
105105
'streamr-client-protocol': path.resolve(__dirname, 'node_modules/streamr-client-protocol/dist/src'),
106-
// swap out PersistentStore for browser
107-
[path.resolve(__dirname, 'src/stream/PersistentStore')]: path.resolve(__dirname, 'src/stream/BrowserStore'),
106+
// swap out ServerPersistentStore for BrowserPersistentStore
107+
[path.resolve(__dirname, 'src/stream/encryption/ServerPersistentStore')]: (
108+
path.resolve(__dirname, 'src/stream/encryption/BrowserPersistentStore')
109+
),
108110
}
109111
},
110112
plugins: [

0 commit comments

Comments
 (0)