nostr-double-ratchet 0.0.38 → 0.0.48
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 +52 -15
- package/dist/AppKeys.d.ts +52 -0
- package/dist/AppKeys.d.ts.map +1 -0
- package/dist/AppKeysManager.d.ts +136 -0
- package/dist/AppKeysManager.d.ts.map +1 -0
- package/dist/Invite.d.ts +5 -6
- package/dist/Invite.d.ts.map +1 -1
- package/dist/Session.d.ts +29 -0
- package/dist/Session.d.ts.map +1 -1
- package/dist/SessionManager.d.ts +15 -8
- package/dist/SessionManager.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/inviteUtils.d.ts +6 -6
- package/dist/inviteUtils.d.ts.map +1 -1
- package/dist/nostr-double-ratchet.es.js +2518 -2168
- package/dist/nostr-double-ratchet.umd.js +1 -1
- package/dist/types.d.ts +24 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +20 -2
- package/dist/utils.d.ts.map +1 -1
- package/package.json +5 -19
- package/src/AppKeys.ts +210 -0
- package/src/AppKeysManager.ts +405 -0
- package/src/Invite.ts +9 -8
- package/src/Session.ts +46 -3
- package/src/SessionManager.ts +341 -174
- package/src/index.ts +3 -3
- package/src/inviteUtils.ts +12 -11
- package/src/types.ts +28 -5
- package/src/utils.ts +42 -5
- package/LICENSE +0 -21
- package/dist/DeviceManager.d.ts +0 -127
- package/dist/DeviceManager.d.ts.map +0 -1
- package/dist/InviteList.d.ts +0 -43
- package/dist/InviteList.d.ts.map +0 -1
- package/dist/UserRecord.d.ts +0 -117
- package/dist/UserRecord.d.ts.map +0 -1
- package/src/DeviceManager.ts +0 -565
- package/src/InviteList.ts +0 -333
- package/src/UserRecord.ts +0 -338
package/src/DeviceManager.ts
DELETED
|
@@ -1,565 +0,0 @@
|
|
|
1
|
-
import { generateSecretKey, getPublicKey, VerifiedEvent } from "nostr-tools"
|
|
2
|
-
import { bytesToHex } from "@noble/hashes/utils"
|
|
3
|
-
import { InviteList, DeviceEntry } from "./InviteList"
|
|
4
|
-
import { DevicePayload } from "./inviteUtils"
|
|
5
|
-
import { NostrSubscribe, NostrPublish, INVITE_LIST_EVENT_KIND, Unsubscribe, IdentityKey } from "./types"
|
|
6
|
-
import { StorageAdapter, InMemoryStorageAdapter } from "./StorageAdapter"
|
|
7
|
-
import { SessionManager } from "./SessionManager"
|
|
8
|
-
|
|
9
|
-
export interface OwnerDeviceOptions {
|
|
10
|
-
ownerPublicKey: string
|
|
11
|
-
identityKey: IdentityKey
|
|
12
|
-
deviceId: string
|
|
13
|
-
deviceLabel: string
|
|
14
|
-
nostrSubscribe: NostrSubscribe
|
|
15
|
-
nostrPublish: NostrPublish
|
|
16
|
-
storage?: StorageAdapter
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface DelegateDeviceOptions {
|
|
20
|
-
deviceId: string
|
|
21
|
-
deviceLabel: string
|
|
22
|
-
nostrSubscribe: NostrSubscribe
|
|
23
|
-
nostrPublish: NostrPublish
|
|
24
|
-
storage?: StorageAdapter
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface RestoreDelegateOptions {
|
|
28
|
-
deviceId: string
|
|
29
|
-
devicePublicKey: string
|
|
30
|
-
devicePrivateKey: Uint8Array
|
|
31
|
-
ephemeralPublicKey: string
|
|
32
|
-
ephemeralPrivateKey: Uint8Array
|
|
33
|
-
sharedSecret: string
|
|
34
|
-
nostrSubscribe: NostrSubscribe
|
|
35
|
-
nostrPublish: NostrPublish
|
|
36
|
-
storage?: StorageAdapter
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface CreateDelegateResult {
|
|
40
|
-
manager: DelegateDeviceManager
|
|
41
|
-
payload: DevicePayload
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface IDeviceManager {
|
|
45
|
-
init(): Promise<void>
|
|
46
|
-
getDeviceId(): string
|
|
47
|
-
getIdentityPublicKey(): string
|
|
48
|
-
getIdentityKey(): IdentityKey
|
|
49
|
-
getEphemeralKeypair(): { publicKey: string; privateKey: Uint8Array } | null
|
|
50
|
-
getSharedSecret(): string | null
|
|
51
|
-
getOwnerPublicKey(): string | null
|
|
52
|
-
close(): void
|
|
53
|
-
createSessionManager(sessionStorage?: StorageAdapter): SessionManager
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Owner's main device. Has identity key and can manage InviteList. */
|
|
57
|
-
export class OwnerDeviceManager implements IDeviceManager {
|
|
58
|
-
private readonly deviceId: string
|
|
59
|
-
private readonly deviceLabel: string
|
|
60
|
-
private readonly nostrSubscribe: NostrSubscribe
|
|
61
|
-
private readonly nostrPublish: NostrPublish
|
|
62
|
-
private readonly storage: StorageAdapter
|
|
63
|
-
private readonly ownerPublicKey: string
|
|
64
|
-
private readonly identityKey: IdentityKey
|
|
65
|
-
|
|
66
|
-
private inviteList: InviteList | null = null
|
|
67
|
-
private initialized = false
|
|
68
|
-
private subscriptions: Unsubscribe[] = []
|
|
69
|
-
|
|
70
|
-
private readonly storageVersion = "1"
|
|
71
|
-
private get versionPrefix(): string {
|
|
72
|
-
return `v${this.storageVersion}`
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
constructor(options: OwnerDeviceOptions) {
|
|
76
|
-
this.deviceId = options.deviceId
|
|
77
|
-
this.deviceLabel = options.deviceLabel
|
|
78
|
-
this.nostrSubscribe = options.nostrSubscribe
|
|
79
|
-
this.nostrPublish = options.nostrPublish
|
|
80
|
-
this.storage = options.storage || new InMemoryStorageAdapter()
|
|
81
|
-
this.ownerPublicKey = options.ownerPublicKey
|
|
82
|
-
this.identityKey = options.identityKey
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async init(): Promise<void> {
|
|
86
|
-
if (this.initialized) return
|
|
87
|
-
this.initialized = true
|
|
88
|
-
|
|
89
|
-
const local = await this.loadInviteList()
|
|
90
|
-
const remote = await this.fetchInviteList(this.ownerPublicKey)
|
|
91
|
-
const inviteList = this.mergeInviteLists(local, remote)
|
|
92
|
-
|
|
93
|
-
if (!inviteList.getDevice(this.deviceId)) {
|
|
94
|
-
const device = inviteList.createDevice(this.deviceLabel, this.deviceId)
|
|
95
|
-
inviteList.addDevice(device)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.inviteList = inviteList
|
|
99
|
-
await this.saveInviteList(inviteList)
|
|
100
|
-
|
|
101
|
-
const event = inviteList.getEvent()
|
|
102
|
-
await this.nostrPublish(event).catch((error) => {
|
|
103
|
-
console.error("Failed to publish InviteList:", error)
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
this.subscribeToOwnInviteList()
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
getDeviceId(): string {
|
|
110
|
-
return this.deviceId
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
getIdentityPublicKey(): string {
|
|
114
|
-
return this.ownerPublicKey
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
getIdentityKey(): IdentityKey {
|
|
118
|
-
return this.identityKey
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
getEphemeralKeypair(): { publicKey: string; privateKey: Uint8Array } | null {
|
|
122
|
-
const device = this.inviteList?.getDevice(this.deviceId)
|
|
123
|
-
if (!device?.ephemeralPublicKey || !device?.ephemeralPrivateKey) {
|
|
124
|
-
return null
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
publicKey: device.ephemeralPublicKey,
|
|
128
|
-
privateKey: device.ephemeralPrivateKey,
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
getSharedSecret(): string | null {
|
|
133
|
-
const device = this.inviteList?.getDevice(this.deviceId)
|
|
134
|
-
return device?.sharedSecret || null
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
getOwnerPublicKey(): string {
|
|
138
|
-
return this.ownerPublicKey
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
getInviteList(): InviteList | null {
|
|
142
|
-
return this.inviteList
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
getOwnDevices(): DeviceEntry[] {
|
|
146
|
-
return this.inviteList?.getAllDevices() || []
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async addDevice(payload: DevicePayload): Promise<void> {
|
|
150
|
-
await this.init()
|
|
151
|
-
|
|
152
|
-
await this.modifyInviteList((list) => {
|
|
153
|
-
const device: DeviceEntry = {
|
|
154
|
-
ephemeralPublicKey: payload.ephemeralPubkey,
|
|
155
|
-
sharedSecret: payload.sharedSecret,
|
|
156
|
-
deviceId: payload.deviceId,
|
|
157
|
-
deviceLabel: payload.deviceLabel,
|
|
158
|
-
createdAt: Math.floor(Date.now() / 1000),
|
|
159
|
-
identityPubkey: payload.identityPubkey,
|
|
160
|
-
}
|
|
161
|
-
list.addDevice(device)
|
|
162
|
-
})
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async revokeDevice(deviceId: string): Promise<void> {
|
|
166
|
-
if (deviceId === this.deviceId) {
|
|
167
|
-
throw new Error("Cannot revoke own device")
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
await this.init()
|
|
171
|
-
|
|
172
|
-
await this.modifyInviteList((list) => {
|
|
173
|
-
list.removeDevice(deviceId)
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async updateDeviceLabel(deviceId: string, label: string): Promise<void> {
|
|
178
|
-
await this.init()
|
|
179
|
-
|
|
180
|
-
await this.modifyInviteList((list) => {
|
|
181
|
-
list.updateDeviceLabel(deviceId, label)
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
close(): void {
|
|
186
|
-
for (const unsubscribe of this.subscriptions) {
|
|
187
|
-
unsubscribe()
|
|
188
|
-
}
|
|
189
|
-
this.subscriptions = []
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
createSessionManager(sessionStorage?: StorageAdapter): SessionManager {
|
|
193
|
-
if (!this.initialized) {
|
|
194
|
-
throw new Error("DeviceManager must be initialized before creating SessionManager")
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const ephemeralKeypair = this.getEphemeralKeypair()
|
|
198
|
-
const sharedSecret = this.getSharedSecret()
|
|
199
|
-
|
|
200
|
-
if (!ephemeralKeypair || !sharedSecret) {
|
|
201
|
-
throw new Error("Ephemeral keypair and shared secret required for SessionManager")
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return new SessionManager(
|
|
205
|
-
this.ownerPublicKey,
|
|
206
|
-
this.identityKey,
|
|
207
|
-
this.deviceId,
|
|
208
|
-
this.nostrSubscribe,
|
|
209
|
-
this.nostrPublish,
|
|
210
|
-
this.ownerPublicKey,
|
|
211
|
-
{ ephemeralKeypair, sharedSecret },
|
|
212
|
-
sessionStorage || this.storage,
|
|
213
|
-
)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private inviteListKey(): string {
|
|
217
|
-
return `${this.versionPrefix}/device-manager/invite-list`
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private async loadInviteList(): Promise<InviteList | null> {
|
|
221
|
-
const data = await this.storage.get<string>(this.inviteListKey())
|
|
222
|
-
if (!data) return null
|
|
223
|
-
try {
|
|
224
|
-
return InviteList.deserialize(data)
|
|
225
|
-
} catch {
|
|
226
|
-
return null
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private async saveInviteList(list: InviteList): Promise<void> {
|
|
231
|
-
await this.storage.put(this.inviteListKey(), list.serialize())
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private fetchInviteList(pubkey: string, timeoutMs = 500): Promise<InviteList | null> {
|
|
235
|
-
return new Promise((resolve) => {
|
|
236
|
-
let latestEvent: { event: VerifiedEvent; inviteList: InviteList } | null = null
|
|
237
|
-
let resolved = false
|
|
238
|
-
|
|
239
|
-
setTimeout(() => {
|
|
240
|
-
if (resolved) return
|
|
241
|
-
resolved = true
|
|
242
|
-
unsubscribe()
|
|
243
|
-
resolve(latestEvent?.inviteList ?? null)
|
|
244
|
-
}, timeoutMs)
|
|
245
|
-
|
|
246
|
-
let unsubscribe: () => void = () => {}
|
|
247
|
-
unsubscribe = this.nostrSubscribe(
|
|
248
|
-
{
|
|
249
|
-
kinds: [INVITE_LIST_EVENT_KIND],
|
|
250
|
-
authors: [pubkey],
|
|
251
|
-
"#d": ["double-ratchet/invite-list"],
|
|
252
|
-
},
|
|
253
|
-
(event) => {
|
|
254
|
-
if (resolved) return
|
|
255
|
-
try {
|
|
256
|
-
const inviteList = InviteList.fromEvent(event)
|
|
257
|
-
if (!latestEvent || event.created_at >= latestEvent.event.created_at) {
|
|
258
|
-
latestEvent = { event, inviteList }
|
|
259
|
-
}
|
|
260
|
-
} catch {
|
|
261
|
-
// Invalid event
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
if (resolved) {
|
|
267
|
-
unsubscribe()
|
|
268
|
-
}
|
|
269
|
-
})
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private mergeInviteLists(local: InviteList | null, remote: InviteList | null): InviteList {
|
|
273
|
-
if (local && remote) return local.merge(remote)
|
|
274
|
-
if (local) return local
|
|
275
|
-
if (remote) return remote
|
|
276
|
-
return new InviteList(this.ownerPublicKey)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private async modifyInviteList(change: (list: InviteList) => void): Promise<void> {
|
|
280
|
-
const remote = await this.fetchInviteList(this.ownerPublicKey)
|
|
281
|
-
const merged = this.mergeInviteLists(this.inviteList, remote)
|
|
282
|
-
change(merged)
|
|
283
|
-
|
|
284
|
-
const event = merged.getEvent()
|
|
285
|
-
await this.nostrPublish(event)
|
|
286
|
-
await this.saveInviteList(merged)
|
|
287
|
-
this.inviteList = merged
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
private subscribeToOwnInviteList(): void {
|
|
291
|
-
const unsubscribe = this.nostrSubscribe(
|
|
292
|
-
{
|
|
293
|
-
kinds: [INVITE_LIST_EVENT_KIND],
|
|
294
|
-
authors: [this.ownerPublicKey],
|
|
295
|
-
"#d": ["double-ratchet/invite-list"],
|
|
296
|
-
},
|
|
297
|
-
(event) => {
|
|
298
|
-
try {
|
|
299
|
-
const remote = InviteList.fromEvent(event)
|
|
300
|
-
if (this.inviteList) {
|
|
301
|
-
this.inviteList = this.inviteList.merge(remote)
|
|
302
|
-
this.saveInviteList(this.inviteList).catch(console.error)
|
|
303
|
-
}
|
|
304
|
-
} catch {
|
|
305
|
-
// Invalid event, ignore
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
this.subscriptions.push(unsubscribe)
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/** Delegate device. Has own identity key, waits for activation, checks revocation. */
|
|
315
|
-
export class DelegateDeviceManager implements IDeviceManager {
|
|
316
|
-
private readonly deviceId: string
|
|
317
|
-
private readonly nostrSubscribe: NostrSubscribe
|
|
318
|
-
private readonly nostrPublish: NostrPublish
|
|
319
|
-
private readonly storage: StorageAdapter
|
|
320
|
-
|
|
321
|
-
private readonly devicePublicKey: string
|
|
322
|
-
private readonly devicePrivateKey: Uint8Array
|
|
323
|
-
private readonly ephemeralPublicKey: string
|
|
324
|
-
private readonly ephemeralPrivateKey: Uint8Array
|
|
325
|
-
private readonly sharedSecret: string
|
|
326
|
-
|
|
327
|
-
private ownerPubkeyFromActivation?: string
|
|
328
|
-
private initialized = false
|
|
329
|
-
private subscriptions: Unsubscribe[] = []
|
|
330
|
-
|
|
331
|
-
private readonly storageVersion = "1"
|
|
332
|
-
private get versionPrefix(): string {
|
|
333
|
-
return `v${this.storageVersion}`
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private constructor(
|
|
337
|
-
deviceId: string,
|
|
338
|
-
nostrSubscribe: NostrSubscribe,
|
|
339
|
-
nostrPublish: NostrPublish,
|
|
340
|
-
storage: StorageAdapter,
|
|
341
|
-
devicePublicKey: string,
|
|
342
|
-
devicePrivateKey: Uint8Array,
|
|
343
|
-
ephemeralPublicKey: string,
|
|
344
|
-
ephemeralPrivateKey: Uint8Array,
|
|
345
|
-
sharedSecret: string,
|
|
346
|
-
) {
|
|
347
|
-
this.deviceId = deviceId
|
|
348
|
-
this.nostrSubscribe = nostrSubscribe
|
|
349
|
-
this.nostrPublish = nostrPublish
|
|
350
|
-
this.storage = storage
|
|
351
|
-
this.devicePublicKey = devicePublicKey
|
|
352
|
-
this.devicePrivateKey = devicePrivateKey
|
|
353
|
-
this.ephemeralPublicKey = ephemeralPublicKey
|
|
354
|
-
this.ephemeralPrivateKey = ephemeralPrivateKey
|
|
355
|
-
this.sharedSecret = sharedSecret
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
static create(options: DelegateDeviceOptions): CreateDelegateResult {
|
|
359
|
-
const devicePrivateKey = generateSecretKey()
|
|
360
|
-
const devicePublicKey = getPublicKey(devicePrivateKey)
|
|
361
|
-
const ephemeralPrivateKey = generateSecretKey()
|
|
362
|
-
const ephemeralPublicKey = getPublicKey(ephemeralPrivateKey)
|
|
363
|
-
const sharedSecret = bytesToHex(generateSecretKey())
|
|
364
|
-
|
|
365
|
-
const manager = new DelegateDeviceManager(
|
|
366
|
-
options.deviceId,
|
|
367
|
-
options.nostrSubscribe,
|
|
368
|
-
options.nostrPublish,
|
|
369
|
-
options.storage || new InMemoryStorageAdapter(),
|
|
370
|
-
devicePublicKey,
|
|
371
|
-
devicePrivateKey,
|
|
372
|
-
ephemeralPublicKey,
|
|
373
|
-
ephemeralPrivateKey,
|
|
374
|
-
sharedSecret,
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
const payload: DevicePayload = {
|
|
378
|
-
ephemeralPubkey: ephemeralPublicKey,
|
|
379
|
-
sharedSecret,
|
|
380
|
-
deviceId: options.deviceId,
|
|
381
|
-
deviceLabel: options.deviceLabel,
|
|
382
|
-
identityPubkey: devicePublicKey,
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return { manager, payload }
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
static restore(options: RestoreDelegateOptions): DelegateDeviceManager {
|
|
389
|
-
return new DelegateDeviceManager(
|
|
390
|
-
options.deviceId,
|
|
391
|
-
options.nostrSubscribe,
|
|
392
|
-
options.nostrPublish,
|
|
393
|
-
options.storage || new InMemoryStorageAdapter(),
|
|
394
|
-
options.devicePublicKey,
|
|
395
|
-
options.devicePrivateKey,
|
|
396
|
-
options.ephemeralPublicKey,
|
|
397
|
-
options.ephemeralPrivateKey,
|
|
398
|
-
options.sharedSecret,
|
|
399
|
-
)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
async init(): Promise<void> {
|
|
403
|
-
if (this.initialized) return
|
|
404
|
-
this.initialized = true
|
|
405
|
-
|
|
406
|
-
const storedOwnerPubkey = await this.storage.get<string>(this.ownerPubkeyKey())
|
|
407
|
-
if (storedOwnerPubkey) {
|
|
408
|
-
this.ownerPubkeyFromActivation = storedOwnerPubkey
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
getDeviceId(): string {
|
|
413
|
-
return this.deviceId
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
getIdentityPublicKey(): string {
|
|
417
|
-
return this.devicePublicKey
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
getIdentityKey(): Uint8Array {
|
|
421
|
-
return this.devicePrivateKey
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
getEphemeralKeypair(): { publicKey: string; privateKey: Uint8Array } {
|
|
425
|
-
return {
|
|
426
|
-
publicKey: this.ephemeralPublicKey,
|
|
427
|
-
privateKey: this.ephemeralPrivateKey,
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
getSharedSecret(): string {
|
|
432
|
-
return this.sharedSecret
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
getOwnerPublicKey(): string | null {
|
|
436
|
-
return this.ownerPubkeyFromActivation || null
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
async waitForActivation(timeoutMs = 60000): Promise<string> {
|
|
440
|
-
if (this.ownerPubkeyFromActivation) {
|
|
441
|
-
return this.ownerPubkeyFromActivation
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
return new Promise((resolve, reject) => {
|
|
445
|
-
const timeout = setTimeout(() => {
|
|
446
|
-
unsubscribe()
|
|
447
|
-
reject(new Error("Activation timeout"))
|
|
448
|
-
}, timeoutMs)
|
|
449
|
-
|
|
450
|
-
// Subscribe to all InviteList events and look for our deviceId
|
|
451
|
-
const unsubscribe = this.nostrSubscribe(
|
|
452
|
-
{
|
|
453
|
-
kinds: [INVITE_LIST_EVENT_KIND],
|
|
454
|
-
"#d": ["double-ratchet/invite-list"],
|
|
455
|
-
},
|
|
456
|
-
async (event) => {
|
|
457
|
-
try {
|
|
458
|
-
const inviteList = InviteList.fromEvent(event)
|
|
459
|
-
const device = inviteList.getDevice(this.deviceId)
|
|
460
|
-
|
|
461
|
-
if (device && device.ephemeralPublicKey === this.ephemeralPublicKey) {
|
|
462
|
-
clearTimeout(timeout)
|
|
463
|
-
unsubscribe()
|
|
464
|
-
this.ownerPubkeyFromActivation = event.pubkey
|
|
465
|
-
await this.storage.put(this.ownerPubkeyKey(), event.pubkey)
|
|
466
|
-
resolve(event.pubkey)
|
|
467
|
-
}
|
|
468
|
-
} catch {
|
|
469
|
-
// Invalid InviteList
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
this.subscriptions.push(unsubscribe)
|
|
475
|
-
})
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
async isRevoked(): Promise<boolean> {
|
|
479
|
-
const ownerPubkey = this.getOwnerPublicKey()
|
|
480
|
-
if (!ownerPubkey) return false
|
|
481
|
-
|
|
482
|
-
const inviteList = await this.fetchInviteList(ownerPubkey)
|
|
483
|
-
if (!inviteList) return true
|
|
484
|
-
|
|
485
|
-
const device = inviteList.getDevice(this.deviceId)
|
|
486
|
-
return !device || device.ephemeralPublicKey !== this.ephemeralPublicKey
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
close(): void {
|
|
490
|
-
for (const unsubscribe of this.subscriptions) {
|
|
491
|
-
unsubscribe()
|
|
492
|
-
}
|
|
493
|
-
this.subscriptions = []
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
createSessionManager(sessionStorage?: StorageAdapter): SessionManager {
|
|
497
|
-
if (!this.initialized) {
|
|
498
|
-
throw new Error("DeviceManager must be initialized before creating SessionManager")
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const ownerPublicKey = this.getOwnerPublicKey()
|
|
502
|
-
if (!ownerPublicKey) {
|
|
503
|
-
throw new Error("Owner public key required for SessionManager - device must be activated first")
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
return new SessionManager(
|
|
507
|
-
this.devicePublicKey,
|
|
508
|
-
this.devicePrivateKey,
|
|
509
|
-
this.deviceId,
|
|
510
|
-
this.nostrSubscribe,
|
|
511
|
-
this.nostrPublish,
|
|
512
|
-
ownerPublicKey,
|
|
513
|
-
{
|
|
514
|
-
ephemeralKeypair: {
|
|
515
|
-
publicKey: this.ephemeralPublicKey,
|
|
516
|
-
privateKey: this.ephemeralPrivateKey,
|
|
517
|
-
},
|
|
518
|
-
sharedSecret: this.sharedSecret,
|
|
519
|
-
},
|
|
520
|
-
sessionStorage || this.storage,
|
|
521
|
-
)
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
private ownerPubkeyKey(): string {
|
|
525
|
-
return `${this.versionPrefix}/device-manager/owner-pubkey`
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
private fetchInviteList(pubkey: string, timeoutMs = 500): Promise<InviteList | null> {
|
|
529
|
-
return new Promise((resolve) => {
|
|
530
|
-
let latestEvent: { event: VerifiedEvent; inviteList: InviteList } | null = null
|
|
531
|
-
let resolved = false
|
|
532
|
-
|
|
533
|
-
setTimeout(() => {
|
|
534
|
-
if (resolved) return
|
|
535
|
-
resolved = true
|
|
536
|
-
unsubscribe()
|
|
537
|
-
resolve(latestEvent?.inviteList ?? null)
|
|
538
|
-
}, timeoutMs)
|
|
539
|
-
|
|
540
|
-
let unsubscribe: () => void = () => {}
|
|
541
|
-
unsubscribe = this.nostrSubscribe(
|
|
542
|
-
{
|
|
543
|
-
kinds: [INVITE_LIST_EVENT_KIND],
|
|
544
|
-
authors: [pubkey],
|
|
545
|
-
"#d": ["double-ratchet/invite-list"],
|
|
546
|
-
},
|
|
547
|
-
(event) => {
|
|
548
|
-
if (resolved) return
|
|
549
|
-
try {
|
|
550
|
-
const inviteList = InviteList.fromEvent(event)
|
|
551
|
-
if (!latestEvent || event.created_at >= latestEvent.event.created_at) {
|
|
552
|
-
latestEvent = { event, inviteList }
|
|
553
|
-
}
|
|
554
|
-
} catch {
|
|
555
|
-
// Invalid event
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
)
|
|
559
|
-
|
|
560
|
-
if (resolved) {
|
|
561
|
-
unsubscribe()
|
|
562
|
-
}
|
|
563
|
-
})
|
|
564
|
-
}
|
|
565
|
-
}
|