spasm.js 0.1.0
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.md +21 -0
- package/lib.commonjs/README.md +20 -0
- package/lib.commonjs/identifyEvent.js +812 -0
- package/lib.commonjs/identifyEvent.js.map +1 -0
- package/lib.commonjs/index.js +7 -0
- package/lib.commonjs/index.js.map +1 -0
- package/lib.commonjs/interfaces.js +16 -0
- package/lib.commonjs/interfaces.js.map +1 -0
- package/lib.commonjs/package.json +4 -0
- package/lib.commonjs/utils.js +36 -0
- package/lib.commonjs/utils.js.map +1 -0
- package/lib.esm/README.md +20 -0
- package/lib.esm/identifyEvent.js +778 -0
- package/lib.esm/identifyEvent.js.map +1 -0
- package/lib.esm/index.js +3 -0
- package/lib.esm/index.js.map +1 -0
- package/lib.esm/interfaces.js +12 -0
- package/lib.esm/interfaces.js.map +1 -0
- package/lib.esm/package.json +4 -0
- package/lib.esm/utils.js +31 -0
- package/lib.esm/utils.js.map +1 -0
- package/package.json +49 -0
- package/src.ts/identifyEvent.ts +1103 -0
- package/src.ts/index.ts +3 -0
- package/src.ts/interfaces.ts +283 -0
- package/src.ts/utils.ts +33 -0
|
@@ -0,0 +1,1103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventBaseProtocol, EventPrivateKey, UnknownPostOrEvent,
|
|
3
|
+
UnknownEvent, DmpEvent, DmpEventSignedClosed, NostrEvent,
|
|
4
|
+
NostrSpasmEvent, NostrSpasmEventSignedOpened, Post,
|
|
5
|
+
NostrSpasmTag, AnyTag, StandardizedEvent, SpasmEvent
|
|
6
|
+
} from "./interfaces";
|
|
7
|
+
import { isObjectWithValues, convertHexToBech32 } from "./utils";
|
|
8
|
+
|
|
9
|
+
// Post-or-Event
|
|
10
|
+
// │
|
|
11
|
+
// │ isWeb2Post()
|
|
12
|
+
// ├── Web2Post
|
|
13
|
+
// │ ├── no signature
|
|
14
|
+
// │ └── no signed_message
|
|
15
|
+
// │
|
|
16
|
+
// │ isWeb3Post()
|
|
17
|
+
// ├── Web3Post
|
|
18
|
+
// │ ├── signature*
|
|
19
|
+
// │ └── signed_message*
|
|
20
|
+
// │ │
|
|
21
|
+
// │ │ identifyEvent()
|
|
22
|
+
// │ └── Event (see below)
|
|
23
|
+
// │
|
|
24
|
+
// │ identifyEvent()
|
|
25
|
+
// └── Event
|
|
26
|
+
// │
|
|
27
|
+
// │ isDmpEvent()
|
|
28
|
+
// ├── DMP event
|
|
29
|
+
// │ ├── version*
|
|
30
|
+
// │ ├── action*
|
|
31
|
+
// │ └── license*
|
|
32
|
+
// │
|
|
33
|
+
// │ isDmpEventSignedClosed()
|
|
34
|
+
// ├── DMP event closed
|
|
35
|
+
// │ ├── signature*
|
|
36
|
+
// │ └── signedString*
|
|
37
|
+
// │ └── DMP event*
|
|
38
|
+
// │ ├── version*
|
|
39
|
+
// │ ├── action*
|
|
40
|
+
// │ └── license*
|
|
41
|
+
// │
|
|
42
|
+
// │ isDmpEventSignedOpened()
|
|
43
|
+
// ├── DMP event opened
|
|
44
|
+
// │ ├── signature*
|
|
45
|
+
// │ ├── signedString*
|
|
46
|
+
// │ └── signedObject*
|
|
47
|
+
// │ └── DMP event*
|
|
48
|
+
// │ ├── version*
|
|
49
|
+
// │ ├── action*
|
|
50
|
+
// │ └── license*
|
|
51
|
+
// │
|
|
52
|
+
// │ isNostrEvent()
|
|
53
|
+
// ├── Nostr event
|
|
54
|
+
// │ ├── id*
|
|
55
|
+
// │ ├── content*
|
|
56
|
+
// │ ├── created_at*
|
|
57
|
+
// │ ├── kind*
|
|
58
|
+
// │ └── pubkey*
|
|
59
|
+
// │
|
|
60
|
+
// │ isNostrEventSignedOpened()
|
|
61
|
+
// ├── Nostr event signed
|
|
62
|
+
// │ ├── sig*
|
|
63
|
+
// │ ├── id*
|
|
64
|
+
// │ ├── content*
|
|
65
|
+
// │ ├── created_at*
|
|
66
|
+
// │ ├── kind*
|
|
67
|
+
// │ └── pubkey*
|
|
68
|
+
// │
|
|
69
|
+
// │ isNostrSpasmEvent()
|
|
70
|
+
// ├── Nostr SPASM event
|
|
71
|
+
// │ ├── id*
|
|
72
|
+
// │ ├── content*
|
|
73
|
+
// │ ├── created_at*
|
|
74
|
+
// │ ├── kind*
|
|
75
|
+
// │ ├── pubkey*
|
|
76
|
+
// │ └── tags*
|
|
77
|
+
// │ ├── spasm_version*
|
|
78
|
+
// │ ├── spasm_target?
|
|
79
|
+
// │ ├── spasm_action?
|
|
80
|
+
// │ └── spasm_title?
|
|
81
|
+
// │
|
|
82
|
+
// │ isNostrSpasmEventSignedOpened()
|
|
83
|
+
// └── Nostr SPASM event signed
|
|
84
|
+
// ├── sig*
|
|
85
|
+
// ├── id*
|
|
86
|
+
// ├── content*
|
|
87
|
+
// ├── created_at*
|
|
88
|
+
// ├── kind*
|
|
89
|
+
// ├── pubkey*
|
|
90
|
+
// └── tags*
|
|
91
|
+
// ├── spasm_version*
|
|
92
|
+
// ├── spasm_target?
|
|
93
|
+
// ├── spasm_action?
|
|
94
|
+
// └── spasm_title?
|
|
95
|
+
//
|
|
96
|
+
|
|
97
|
+
type EventType =
|
|
98
|
+
"DmpEvent" |
|
|
99
|
+
"DmpEventSignedClosed" |
|
|
100
|
+
"DmpEventSignedOpened" |
|
|
101
|
+
"NostrEvent" |
|
|
102
|
+
"NostrEventSignedOpened" |
|
|
103
|
+
"NostrSpasmEvent" |
|
|
104
|
+
"NostrSpasmEventSignedOpened" |
|
|
105
|
+
"SpasmEvent" |
|
|
106
|
+
"unknown"
|
|
107
|
+
|
|
108
|
+
type EventInfoType = EventType | "Post"
|
|
109
|
+
|
|
110
|
+
interface EventInfo {
|
|
111
|
+
type: EventInfoType | false
|
|
112
|
+
hasSignature: boolean
|
|
113
|
+
baseProtocol: EventBaseProtocol | false
|
|
114
|
+
privateKey: EventPrivateKey | false
|
|
115
|
+
isSpasmCompatible: boolean
|
|
116
|
+
hasExtraSpasmFields: boolean
|
|
117
|
+
license: string | false
|
|
118
|
+
// originalEvent: UnknownEvent
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type WebType = "web2" | "web3"
|
|
122
|
+
|
|
123
|
+
type EventIsSealedUnderKeyName = "signed_message" | "signedObject"
|
|
124
|
+
|
|
125
|
+
// interface Web3Post extends Post {
|
|
126
|
+
// signed_message: string
|
|
127
|
+
// }
|
|
128
|
+
|
|
129
|
+
interface KnownPostOrEventInfo {
|
|
130
|
+
webType: WebType | false
|
|
131
|
+
eventIsSealed: boolean
|
|
132
|
+
eventIsSealedUnderKeyName: EventIsSealedUnderKeyName | false
|
|
133
|
+
eventInfo: EventInfo | false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// web2 post example
|
|
137
|
+
// webType: "web2",
|
|
138
|
+
// eventIsSealed: false,
|
|
139
|
+
// eventIsSealedUnderKeyName: false,
|
|
140
|
+
// eventInfo: false
|
|
141
|
+
|
|
142
|
+
// web3 post example
|
|
143
|
+
// webType: "web3",
|
|
144
|
+
// eventIsSealed: true,
|
|
145
|
+
// eventIsSealedUnderKeyName: "signed_message",
|
|
146
|
+
// eventInfo: {
|
|
147
|
+
// type: "DmpEventSignedClosed",
|
|
148
|
+
// hasSignature: true
|
|
149
|
+
// baseProtocol: "dmp",
|
|
150
|
+
// privateKey: "ethereum",
|
|
151
|
+
// isSpasmCompatible: true,
|
|
152
|
+
// hasExtraSpasmFields: false,
|
|
153
|
+
// }
|
|
154
|
+
|
|
155
|
+
// web3 event example
|
|
156
|
+
// webType: "web3",
|
|
157
|
+
// eventIsSealed: false,
|
|
158
|
+
// eventIsSealedUnderKeyName: false,
|
|
159
|
+
// eventInfo: {
|
|
160
|
+
// type: "NostrSpasmEventSignedOpened",
|
|
161
|
+
// hasSignature: true
|
|
162
|
+
// baseProtocol: "nostr",
|
|
163
|
+
// privateKey: "nostr",
|
|
164
|
+
// isSpasmCompatible: true,
|
|
165
|
+
// hasExtraSpasmFields: true,
|
|
166
|
+
// }
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
There are usually 3 types of objects passed to this function:
|
|
170
|
+
- web3 post - an object is a post with a web3 event sealed inside
|
|
171
|
+
- web3 event - an object itself is a web3 event
|
|
172
|
+
- web2 post - an object is a post without a web3 event (e.g. RSS)
|
|
173
|
+
*/
|
|
174
|
+
export const identifyPostOrEvent = (
|
|
175
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
176
|
+
): KnownPostOrEventInfo => {
|
|
177
|
+
|
|
178
|
+
const info: KnownPostOrEventInfo = {
|
|
179
|
+
webType: false,
|
|
180
|
+
eventIsSealed: false,
|
|
181
|
+
eventIsSealedUnderKeyName: false,
|
|
182
|
+
eventInfo: {
|
|
183
|
+
type: false,
|
|
184
|
+
hasSignature: false,
|
|
185
|
+
baseProtocol: false,
|
|
186
|
+
privateKey: false,
|
|
187
|
+
isSpasmCompatible: false,
|
|
188
|
+
hasExtraSpasmFields: false,
|
|
189
|
+
license: false
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return info
|
|
194
|
+
|
|
195
|
+
let unknownEventOrWeb2Post: UnknownPostOrEvent
|
|
196
|
+
|
|
197
|
+
// Option 1.
|
|
198
|
+
// If an object is a post with a sealed event (signed string),
|
|
199
|
+
// we need to extract the event by parsing the signed string.
|
|
200
|
+
// We then check if that extracted object is a valid web3 event.
|
|
201
|
+
if (isWeb3Post(unknownPostOrEvent)) {
|
|
202
|
+
info.webType = "web3"
|
|
203
|
+
info.eventIsSealed = true
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
'signed_message' in unknownPostOrEvent &&
|
|
207
|
+
typeof(unknownPostOrEvent.signed_message) === "string"
|
|
208
|
+
) {
|
|
209
|
+
const signedObject = JSON.parse(unknownPostOrEvent.signed_message)
|
|
210
|
+
info.eventIsSealedUnderKeyName = 'signed_message'
|
|
211
|
+
unknownEventOrWeb2Post = signedObject
|
|
212
|
+
|
|
213
|
+
// Edge cases.
|
|
214
|
+
// Signed DMP event sealed in the Post under the key
|
|
215
|
+
// 'signed_message' doesn't have signature inside the signed
|
|
216
|
+
// string, so we cannot just parse the string to extract the
|
|
217
|
+
// object and then pass it into an identify function.
|
|
218
|
+
// Instead, we have to attach a signer and signature to the
|
|
219
|
+
// signed string, which is a type of DmpEventSignedClosed.
|
|
220
|
+
if (
|
|
221
|
+
isDmpEvent(unknownEventOrWeb2Post) &&
|
|
222
|
+
'signer' in unknownPostOrEvent &&
|
|
223
|
+
typeof(unknownPostOrEvent.signer) === "string" &&
|
|
224
|
+
'signature' in unknownPostOrEvent &&
|
|
225
|
+
typeof(unknownPostOrEvent.signature) === "string"
|
|
226
|
+
) {
|
|
227
|
+
// Recreating DmpEventSignedClosed
|
|
228
|
+
unknownEventOrWeb2Post = {
|
|
229
|
+
signer: unknownPostOrEvent.signer,
|
|
230
|
+
signedString: unknownPostOrEvent.signed_message,
|
|
231
|
+
signature: unknownPostOrEvent.signature
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Option 2.
|
|
237
|
+
// If an object doesn't have a sealed event (signed string), then
|
|
238
|
+
// - either the object itself is a web3 event,
|
|
239
|
+
// - or the object is a web2 post (e.g., from an RSS feed).
|
|
240
|
+
} else {
|
|
241
|
+
info.webType = false
|
|
242
|
+
info.eventIsSealed = false
|
|
243
|
+
info.eventIsSealedUnderKeyName = false
|
|
244
|
+
unknownEventOrWeb2Post = unknownPostOrEvent
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const eventInfo: EventInfo = identifyEvent(unknownEventOrWeb2Post)
|
|
248
|
+
|
|
249
|
+
// An object has been identified as a web3 event.
|
|
250
|
+
if (eventInfo?.type && typeof(eventInfo.type) === "string") {
|
|
251
|
+
// web3 post and web3 event
|
|
252
|
+
info.webType = "web3"
|
|
253
|
+
info.eventInfo = eventInfo
|
|
254
|
+
return info
|
|
255
|
+
|
|
256
|
+
// An object has not been identified as a web3 event.
|
|
257
|
+
} else {
|
|
258
|
+
// web2 post
|
|
259
|
+
info.webType = "web2"
|
|
260
|
+
info.eventInfo = false
|
|
261
|
+
return info
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export const isWeb2Post = (
|
|
266
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
267
|
+
): boolean => {
|
|
268
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
269
|
+
|
|
270
|
+
if (
|
|
271
|
+
// signatures
|
|
272
|
+
'sig' in unknownPostOrEvent ||
|
|
273
|
+
'signature' in unknownPostOrEvent ||
|
|
274
|
+
'signed_message' in unknownPostOrEvent ||
|
|
275
|
+
'signedObject' in unknownPostOrEvent ||
|
|
276
|
+
'signedString' in unknownPostOrEvent
|
|
277
|
+
) {
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (isNostrEvent(unknownPostOrEvent)) return false
|
|
282
|
+
|
|
283
|
+
if (isDmpEvent(unknownPostOrEvent)) return false
|
|
284
|
+
|
|
285
|
+
if (isDmpEventSignedClosed(unknownPostOrEvent)) return false
|
|
286
|
+
|
|
287
|
+
if (isDmpEventSignedOpened(unknownPostOrEvent)) return false
|
|
288
|
+
|
|
289
|
+
return false
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export const isWeb3Post = (
|
|
293
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
294
|
+
): boolean => {
|
|
295
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
296
|
+
|
|
297
|
+
if (
|
|
298
|
+
'signed_message' in unknownPostOrEvent &&
|
|
299
|
+
typeof(unknownPostOrEvent.signed_message) === "string"
|
|
300
|
+
) {
|
|
301
|
+
return true
|
|
302
|
+
}
|
|
303
|
+
return false
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export const identifyEvent = (
|
|
307
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
308
|
+
): EventInfo => {
|
|
309
|
+
console.log("identifyEvent called")
|
|
310
|
+
|
|
311
|
+
const eventInfo: EventInfo = {
|
|
312
|
+
type: "unknown",
|
|
313
|
+
hasSignature: false,
|
|
314
|
+
baseProtocol: false,
|
|
315
|
+
privateKey: false,
|
|
316
|
+
isSpasmCompatible: false,
|
|
317
|
+
hasExtraSpasmFields: false,
|
|
318
|
+
license: false
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return eventInfo
|
|
322
|
+
|
|
323
|
+
eventInfo.license = identifyLicense(unknownPostOrEvent)
|
|
324
|
+
|
|
325
|
+
// TODO: refactor
|
|
326
|
+
// verifySignature()
|
|
327
|
+
// add key 'isSignatureValid' to EventInfo
|
|
328
|
+
// check privateKey after discovering eventInfo.type
|
|
329
|
+
|
|
330
|
+
if (hasSignature(unknownPostOrEvent)) eventInfo.hasSignature = true
|
|
331
|
+
|
|
332
|
+
// Another approach is to set a private key after identifying
|
|
333
|
+
// the event type (eventInfo.type).
|
|
334
|
+
if (eventInfo.hasSignature) {
|
|
335
|
+
eventInfo.privateKey = identifyPrivateKey(unknownPostOrEvent)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (hasExtraSpasmFields(unknownPostOrEvent)) eventInfo.hasExtraSpasmFields = true
|
|
339
|
+
|
|
340
|
+
// DMP
|
|
341
|
+
// Might be DMP or Nostr or web2 Post
|
|
342
|
+
if (!eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields) {
|
|
343
|
+
if (isDmpEvent(unknownPostOrEvent)) {
|
|
344
|
+
eventInfo.type = "DmpEvent"
|
|
345
|
+
eventInfo.baseProtocol = "dmp"
|
|
346
|
+
eventInfo.isSpasmCompatible = true
|
|
347
|
+
return eventInfo
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Might be DMP or Nostr with signature or Post with signature
|
|
351
|
+
} else if (eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields) {
|
|
352
|
+
if (isDmpEventSignedOpened(unknownPostOrEvent)) {
|
|
353
|
+
eventInfo.type = "DmpEventSignedOpened"
|
|
354
|
+
eventInfo.baseProtocol = "dmp"
|
|
355
|
+
eventInfo.isSpasmCompatible = true
|
|
356
|
+
return eventInfo
|
|
357
|
+
} else if (isDmpEventSignedClosed(unknownPostOrEvent)) {
|
|
358
|
+
eventInfo.type = "DmpEventSignedClosed"
|
|
359
|
+
eventInfo.baseProtocol = "dmp"
|
|
360
|
+
eventInfo.isSpasmCompatible = true
|
|
361
|
+
return eventInfo
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Nostr
|
|
366
|
+
if (eventInfo.hasSignature && eventInfo.hasExtraSpasmFields) {
|
|
367
|
+
// Looks like Nostr event with signature and SPASM fields
|
|
368
|
+
if (isNostrSpasmEventSignedOpened(unknownPostOrEvent)) {
|
|
369
|
+
eventInfo.type = "NostrSpasmEventSignedOpened"
|
|
370
|
+
eventInfo.baseProtocol = "nostr"
|
|
371
|
+
eventInfo.isSpasmCompatible = true
|
|
372
|
+
return eventInfo
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
} else if (eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields ) {
|
|
376
|
+
// Looks like Nostr event with signature without SPASM fields
|
|
377
|
+
if (isNostrEventSignedOpened(unknownPostOrEvent)) {
|
|
378
|
+
eventInfo.type = "NostrEventSignedOpened"
|
|
379
|
+
eventInfo.baseProtocol = "nostr"
|
|
380
|
+
eventInfo.isSpasmCompatible = false
|
|
381
|
+
return eventInfo
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
} else if (!eventInfo.hasSignature && eventInfo.hasExtraSpasmFields) {
|
|
385
|
+
// Looks like Nostr event without signature, but with SPASM fields
|
|
386
|
+
if (isNostrSpasmEvent(unknownPostOrEvent)) {
|
|
387
|
+
eventInfo.type = "NostrSpasmEvent"
|
|
388
|
+
eventInfo.baseProtocol = "nostr"
|
|
389
|
+
eventInfo.isSpasmCompatible = true
|
|
390
|
+
return eventInfo
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
} else if (!eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields) {
|
|
394
|
+
// Looks like Nostr event without signature and without SPASM fields
|
|
395
|
+
if (isNostrEvent(unknownPostOrEvent)) {
|
|
396
|
+
eventInfo.type = "NostrEvent"
|
|
397
|
+
eventInfo.baseProtocol = "nostr"
|
|
398
|
+
eventInfo.isSpasmCompatible = false
|
|
399
|
+
return eventInfo
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// DMP event signature key is 'signature'.
|
|
405
|
+
// Nostr event signature key is 'sig'.
|
|
406
|
+
// Post signature key is 'signature'.
|
|
407
|
+
// Post signature can be from Nostr or DMP events.
|
|
408
|
+
type SignatureKey = 'signature' | 'sig'
|
|
409
|
+
|
|
410
|
+
export const hasSignature = (
|
|
411
|
+
unknownPostOrEvent: UnknownPostOrEvent,
|
|
412
|
+
signatureKey?: SignatureKey,
|
|
413
|
+
signatureLength: number = 40
|
|
414
|
+
): boolean => {
|
|
415
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
416
|
+
|
|
417
|
+
let keys: SignatureKey[] = signatureKey
|
|
418
|
+
|
|
419
|
+
? [signatureKey] // signature key is provided
|
|
420
|
+
: ['signature', 'sig']; // check all signature keys
|
|
421
|
+
|
|
422
|
+
for (let key of keys) {
|
|
423
|
+
if (key in unknownPostOrEvent &&
|
|
424
|
+
typeof(unknownPostOrEvent[key]) === 'string' &&
|
|
425
|
+
unknownPostOrEvent[key].length > signatureLength) {
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return false
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export const identifyLicense = (
|
|
434
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
435
|
+
): string | false => {
|
|
436
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
437
|
+
|
|
438
|
+
let license: string | false = false
|
|
439
|
+
|
|
440
|
+
// Option 1.
|
|
441
|
+
// A license can be inside a 'license' key
|
|
442
|
+
// or inside tags (e.g. SPASM tags in Nostr events).
|
|
443
|
+
if (
|
|
444
|
+
'license' in unknownPostOrEvent &&
|
|
445
|
+
typeof(unknownPostOrEvent['license']) === 'string' &&
|
|
446
|
+
unknownPostOrEvent['license'].length > 0
|
|
447
|
+
) {
|
|
448
|
+
license = unknownPostOrEvent['license'];
|
|
449
|
+
if (license) return license
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
license = identifyLicenseInsideTags(unknownPostOrEvent)
|
|
453
|
+
if (license) return license
|
|
454
|
+
|
|
455
|
+
// Option 2.
|
|
456
|
+
// If no license was found, then we should try to extract
|
|
457
|
+
// an object (event) from a signed string if such a string
|
|
458
|
+
// exists, and then check that object for a license.
|
|
459
|
+
const signedObject = extractSealedEvent(unknownPostOrEvent)
|
|
460
|
+
|
|
461
|
+
if (!signedObject) return false
|
|
462
|
+
|
|
463
|
+
if (!isObjectWithValues(signedObject)) return false
|
|
464
|
+
|
|
465
|
+
if (
|
|
466
|
+
'license' in signedObject &&
|
|
467
|
+
typeof(signedObject['license']) === 'string' &&
|
|
468
|
+
signedObject['license'].length > 0
|
|
469
|
+
) {
|
|
470
|
+
license = signedObject['license'];
|
|
471
|
+
if (license) return license
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
license = identifyLicenseInsideTags(signedObject)
|
|
475
|
+
if (license) return license
|
|
476
|
+
|
|
477
|
+
return false
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export const identifyLicenseInsideTags = (
|
|
481
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
482
|
+
): string | false => {
|
|
483
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
484
|
+
|
|
485
|
+
let license: string | false = false
|
|
486
|
+
|
|
487
|
+
// A license can be placed inside SPASM tags
|
|
488
|
+
if ('tags' in unknownPostOrEvent &&
|
|
489
|
+
Array.isArray(unknownPostOrEvent.tags)) {
|
|
490
|
+
|
|
491
|
+
// Nostr events have tags of array type: NostrSpasmTag | AnyTag
|
|
492
|
+
// Post events have tags of string type: string
|
|
493
|
+
unknownPostOrEvent.tags.forEach(function (tag: NostrSpasmTag | AnyTag | string) {
|
|
494
|
+
if (Array.isArray(tag)) {
|
|
495
|
+
if (
|
|
496
|
+
tag[0] === "license" &&
|
|
497
|
+
typeof(tag[1]) === "string"
|
|
498
|
+
) {
|
|
499
|
+
license = tag[1]
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return license
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export const extractSealedEvent = (
|
|
509
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
510
|
+
): UnknownEvent | false => {
|
|
511
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
512
|
+
|
|
513
|
+
let signedObject: UnknownEvent | false = false
|
|
514
|
+
|
|
515
|
+
if (
|
|
516
|
+
'signed_message' in unknownPostOrEvent &&
|
|
517
|
+
typeof(unknownPostOrEvent['signed_message'] === "string")
|
|
518
|
+
) {
|
|
519
|
+
signedObject = JSON.parse(unknownPostOrEvent['signed_message'])
|
|
520
|
+
|
|
521
|
+
} else if (
|
|
522
|
+
'signedString' in unknownPostOrEvent &&
|
|
523
|
+
typeof(unknownPostOrEvent['signedString'] === "string")
|
|
524
|
+
) {
|
|
525
|
+
signedObject = JSON.parse(unknownPostOrEvent['signedString'])
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return signedObject
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
type PrivateKeyType = "ethereum" | "nostr"
|
|
532
|
+
|
|
533
|
+
export const identifyPrivateKey = (
|
|
534
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
535
|
+
): PrivateKeyType | false => {
|
|
536
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* If an object has 'sig' key, then it's most likely a Nostr event.
|
|
540
|
+
* Currently, all Nostr events can only be signed with a Nostr private
|
|
541
|
+
* key, so we can assume that a private key is "nostr".
|
|
542
|
+
* In the future there might be an option to sign Nostr events with
|
|
543
|
+
* different keys, so we will have to update the logic.
|
|
544
|
+
*/
|
|
545
|
+
if ('sig' in unknownPostOrEvent &&
|
|
546
|
+
typeof(unknownPostOrEvent['sig']) === 'string' &&
|
|
547
|
+
unknownPostOrEvent['sig'].length > 40) {
|
|
548
|
+
return 'nostr';
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* A post with a sealed event (hidden inside the signed string)
|
|
553
|
+
* under the key like 'signed_message' or 'signedObject' will
|
|
554
|
+
* usually have a signature inside a 'signature' key.
|
|
555
|
+
*
|
|
556
|
+
* So if an object has a 'signature' key, then it can be:
|
|
557
|
+
* - a DMP event signed with the Ethereum private key,
|
|
558
|
+
* - has 'signature'
|
|
559
|
+
* - a Post with a sealed DMP event signed with an Ethereum private key,
|
|
560
|
+
* - has 'signature'
|
|
561
|
+
* - a Post with a sealed Nostr event signed with a Nostr private key.
|
|
562
|
+
* - has 'signature'
|
|
563
|
+
* - and also has 'sig' inside 'signed_message'
|
|
564
|
+
*
|
|
565
|
+
* In other words, a Post with a sealed Nostr event will have
|
|
566
|
+
* the same signature recorded twice in different places:
|
|
567
|
+
* 1. Inside a 'signature' key in the Post (envelope).
|
|
568
|
+
* 2. Inside a 'sig' key in the object extracted from a signed string.
|
|
569
|
+
* While, a Post with a sealed DMP event will only have the signature
|
|
570
|
+
* specified once inside the 'signature' key.
|
|
571
|
+
*
|
|
572
|
+
* Thus, we have to convert the signed string into an object,
|
|
573
|
+
* then check whether it has a 'sig' key (Nostr event),
|
|
574
|
+
* otherwise assume that it was signed with an Ethereum private key.
|
|
575
|
+
*/
|
|
576
|
+
if ('signature' in unknownPostOrEvent &&
|
|
577
|
+
typeof(unknownPostOrEvent?.['signature']) === 'string' &&
|
|
578
|
+
unknownPostOrEvent?.['signature'].length > 40) {
|
|
579
|
+
|
|
580
|
+
if ('signed_message' in unknownPostOrEvent &&
|
|
581
|
+
typeof(unknownPostOrEvent?.['signed_message']) === 'string') {
|
|
582
|
+
|
|
583
|
+
const signedObject = JSON.parse(unknownPostOrEvent?.['signed_message'])
|
|
584
|
+
|
|
585
|
+
if (!isObjectWithValues(signedObject)) return false
|
|
586
|
+
|
|
587
|
+
if ('sig' in unknownPostOrEvent &&
|
|
588
|
+
typeof(unknownPostOrEvent['sig']) === 'string' &&
|
|
589
|
+
unknownPostOrEvent['sig'].length > 40) {
|
|
590
|
+
|
|
591
|
+
return 'nostr';
|
|
592
|
+
|
|
593
|
+
} else {
|
|
594
|
+
return 'ethereum'
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if ('signedString' in unknownPostOrEvent &&
|
|
599
|
+
typeof(unknownPostOrEvent?.['signedString']) === 'string') {
|
|
600
|
+
|
|
601
|
+
const signedObject = JSON.parse(unknownPostOrEvent?.['signedString'])
|
|
602
|
+
|
|
603
|
+
if (!isObjectWithValues(signedObject)) return false
|
|
604
|
+
|
|
605
|
+
if (isDmpEvent(signedObject)) return 'ethereum'
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
export const hasExtraSpasmFields = (
|
|
612
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
613
|
+
): boolean => {
|
|
614
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
615
|
+
|
|
616
|
+
if ('tags' in unknownPostOrEvent &&
|
|
617
|
+
Array.isArray(unknownPostOrEvent.tags)) {
|
|
618
|
+
|
|
619
|
+
// spasm_version is optional
|
|
620
|
+
let hasSpasmVersion: boolean = false
|
|
621
|
+
// spasm_target is optional (e.g. new posts have no target)
|
|
622
|
+
let hasSpasmTarget: boolean = false
|
|
623
|
+
// spasm_action is optional for Nostr (can be taken from 'kind' key)
|
|
624
|
+
let hasSpasmAction: boolean = false
|
|
625
|
+
// spasm_title is optional (e.g. comments/reactions have no title)
|
|
626
|
+
let hasSpasmTitle: boolean = false
|
|
627
|
+
|
|
628
|
+
// Nostr events have tags of array type: NostrSpasmTag | AnyTag
|
|
629
|
+
// Post events have tags of string type: string
|
|
630
|
+
unknownPostOrEvent.tags.forEach(function (tag: NostrSpasmTag | AnyTag | string) {
|
|
631
|
+
if (Array.isArray(tag)) {
|
|
632
|
+
if (tag[0] === "spasm_version") {
|
|
633
|
+
hasSpasmVersion = true
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (tag[0] === "spasm_target") {
|
|
637
|
+
hasSpasmTarget = true
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (tag[0] === "spasm_action") {
|
|
641
|
+
hasSpasmAction = true
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (tag[0] === "spasm_title") {
|
|
645
|
+
hasSpasmTitle = true
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
})
|
|
649
|
+
|
|
650
|
+
if (hasSpasmVersion || hasSpasmTarget || hasSpasmAction || hasSpasmTitle) {
|
|
651
|
+
return true
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return false
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export const isNostrEvent = (
|
|
658
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
659
|
+
): boolean => {
|
|
660
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
661
|
+
|
|
662
|
+
// Unsigned Nostr event can be without 'id'
|
|
663
|
+
// if (!('id' in unknownPostOrEvent)) return false
|
|
664
|
+
|
|
665
|
+
// content
|
|
666
|
+
if (!('content' in unknownPostOrEvent)) return false
|
|
667
|
+
if (!unknownPostOrEvent.content) return false
|
|
668
|
+
if (typeof(unknownPostOrEvent.content) !== "string") return false
|
|
669
|
+
|
|
670
|
+
// created_at
|
|
671
|
+
if (!('created_at' in unknownPostOrEvent)) return false
|
|
672
|
+
// 0 is a valid created_at
|
|
673
|
+
if (
|
|
674
|
+
!unknownPostOrEvent.created_at &&
|
|
675
|
+
unknownPostOrEvent.created_at !== 0
|
|
676
|
+
) return false
|
|
677
|
+
if (typeof(unknownPostOrEvent.created_at) !== "number") return false
|
|
678
|
+
|
|
679
|
+
// kind
|
|
680
|
+
if (!('kind' in unknownPostOrEvent)) return false
|
|
681
|
+
// 0 is a valid kind
|
|
682
|
+
if (
|
|
683
|
+
!unknownPostOrEvent.kind &&
|
|
684
|
+
unknownPostOrEvent.kind !== 0
|
|
685
|
+
) return false
|
|
686
|
+
if (typeof(unknownPostOrEvent.kind) !== "number") return false
|
|
687
|
+
|
|
688
|
+
// pubkey
|
|
689
|
+
if (!('pubkey' in unknownPostOrEvent)) return false
|
|
690
|
+
if (!unknownPostOrEvent.pubkey) return false
|
|
691
|
+
if (typeof(unknownPostOrEvent.pubkey) !== "string") return false
|
|
692
|
+
|
|
693
|
+
// tags
|
|
694
|
+
// TODO: check if tags is a mandatory field
|
|
695
|
+
// if (!('tags' in unknownPostOrEvent)) return false
|
|
696
|
+
|
|
697
|
+
// sig
|
|
698
|
+
// Unsigned Nostr event can be without 'sig'
|
|
699
|
+
// if (!('sig' in unknownPostOrEvent)) return false
|
|
700
|
+
return true
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export const isNostrEventSignedOpened = (
|
|
704
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
705
|
+
): boolean => {
|
|
706
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
707
|
+
|
|
708
|
+
if (!isNostrEvent(unknownPostOrEvent)) return false
|
|
709
|
+
|
|
710
|
+
// Signed Nostr event must have 'id'
|
|
711
|
+
if (!('id' in unknownPostOrEvent)) return false
|
|
712
|
+
|
|
713
|
+
if (!hasSignature(unknownPostOrEvent as NostrEvent, 'sig')) return false
|
|
714
|
+
|
|
715
|
+
return true
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export const isNostrSpasmEvent = (
|
|
719
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
720
|
+
): boolean => {
|
|
721
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
722
|
+
|
|
723
|
+
if (!isNostrEvent(unknownPostOrEvent)) return false
|
|
724
|
+
|
|
725
|
+
if (!hasExtraSpasmFields(unknownPostOrEvent as NostrEvent)) return false
|
|
726
|
+
|
|
727
|
+
return true
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export const isNostrSpasmEventSignedOpened = (
|
|
731
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
732
|
+
): boolean => {
|
|
733
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
734
|
+
|
|
735
|
+
if (!isNostrSpasmEvent(unknownPostOrEvent)) return false
|
|
736
|
+
|
|
737
|
+
if (!isNostrEventSignedOpened(unknownPostOrEvent as NostrSpasmEvent)) return false
|
|
738
|
+
|
|
739
|
+
return true
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export const isDmpEvent = (
|
|
743
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
744
|
+
): boolean => {
|
|
745
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
746
|
+
|
|
747
|
+
// TODO: think what if unknownPostOrEvent is Post with version, action, license
|
|
748
|
+
if (!('version' in unknownPostOrEvent)) return false
|
|
749
|
+
if (!('action' in unknownPostOrEvent)) return false
|
|
750
|
+
if (!('license' in unknownPostOrEvent)) return false
|
|
751
|
+
// time is optional
|
|
752
|
+
// if (!('time' in unknownPostOrEvent)) return false
|
|
753
|
+
// target is optional (e.g. a new post has no target)
|
|
754
|
+
// if (!('target' in unknownPostOrEvent)) return false
|
|
755
|
+
// title is optional (e.g. a comment has no title)
|
|
756
|
+
// if (!('title' in unknownPostOrEvent)) return false
|
|
757
|
+
// text is optional (e.g. an event has only title)
|
|
758
|
+
// if (!('text' in unknownPostOrEvent)) return false
|
|
759
|
+
|
|
760
|
+
if (!unknownPostOrEvent?.version?.startsWith("dmp")) return false
|
|
761
|
+
return true
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
export const isDmpEventSignedClosed = (
|
|
765
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
766
|
+
): boolean => {
|
|
767
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
768
|
+
|
|
769
|
+
if (!('signedString' in unknownPostOrEvent)) return false
|
|
770
|
+
if (!('signature' in unknownPostOrEvent)) return false
|
|
771
|
+
// signer is optional
|
|
772
|
+
// if (!('signer' in unknownPostOrEvent)) return false
|
|
773
|
+
|
|
774
|
+
if (typeof(unknownPostOrEvent.signedString) !== "string") return false
|
|
775
|
+
const signedObject: DmpEvent = JSON.parse(unknownPostOrEvent.signedString)
|
|
776
|
+
|
|
777
|
+
if (!isDmpEvent(signedObject)) return false
|
|
778
|
+
|
|
779
|
+
return true
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export const isDmpEventSignedOpened = (
|
|
783
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
784
|
+
): boolean => {
|
|
785
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
786
|
+
|
|
787
|
+
if (!isDmpEventSignedClosed) return false
|
|
788
|
+
|
|
789
|
+
if ('signedObject' in unknownPostOrEvent) {
|
|
790
|
+
if (isDmpEvent(unknownPostOrEvent.signedObject)) return true
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return false
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* 1. Post can contain DPM event without signature as a string inside
|
|
798
|
+
* signed_message, so signature is attached with 'signature' key.
|
|
799
|
+
*
|
|
800
|
+
* isPostWithDmpEventSignedOpened()
|
|
801
|
+
* - contains isDmpEventSignedOpened()
|
|
802
|
+
* isPostWithDmpEventSignedClosed()
|
|
803
|
+
* - contains isDmpEventSignedClosed()
|
|
804
|
+
* isPostWithDmpEvent()
|
|
805
|
+
* - contains isDmpEvent()
|
|
806
|
+
*
|
|
807
|
+
* 2. Post can contain Nostr event with signature as a string inside
|
|
808
|
+
* signed_message, and signature is also attached with 'signature' key.
|
|
809
|
+
* In other words, signature will be passed twice:
|
|
810
|
+
* - inside 'sig' key of parsed 'signed_message'
|
|
811
|
+
* - inside 'signature' key of the post object.
|
|
812
|
+
*
|
|
813
|
+
* isPostWithNostrSpasmEventSignedOpened()
|
|
814
|
+
* - contains isNostrSpasmEventSignedOpened()
|
|
815
|
+
* isPostWithNostrEventSignedOpened()
|
|
816
|
+
* - contains isNostrEventSignedOpened()
|
|
817
|
+
* isPostWithNostrSpasmEvent()
|
|
818
|
+
* - contains isNostrSpasmEvent()
|
|
819
|
+
* isPostWithNostrEvent
|
|
820
|
+
* - contains isNostrEvent()
|
|
821
|
+
*/
|
|
822
|
+
|
|
823
|
+
export const isPostWithDmpEvent = (
|
|
824
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
825
|
+
): boolean => {
|
|
826
|
+
return !!(identifyEventInsidePost(unknownPostOrEvent) === "DmpEvent")
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
export const isPostWithDmpEventSignedOpened = (
|
|
830
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
831
|
+
): boolean => {
|
|
832
|
+
return !!(identifyEventInsidePost(unknownPostOrEvent) === "DmpEventSignedOpened")
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
export const isPostWithDmpEventSignedClosed = (
|
|
836
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
837
|
+
): boolean => {
|
|
838
|
+
return !!(identifyEventInsidePost(unknownPostOrEvent) === "DmpEventSignedClosed")
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
export const isPostWithNostrSpasmEventSignedOpened = (
|
|
842
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
843
|
+
): boolean => {
|
|
844
|
+
return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrSpasmEventSignedOpened")
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export const isPostWithNostrEventSignedOpened = (
|
|
848
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
849
|
+
): boolean => {
|
|
850
|
+
return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrEventSignedOpened")
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
export const isPostWithNostrSpasmEvent = (
|
|
854
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
855
|
+
): boolean => {
|
|
856
|
+
return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrSpasmEvent")
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export const isPostWithNostrEvent = (
|
|
860
|
+
unknownPostOrEvent: UnknownEvent
|
|
861
|
+
): boolean => {
|
|
862
|
+
return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrEvent")
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
export const identifyEventInsidePost = (
|
|
866
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
867
|
+
): EventType | false => {
|
|
868
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
869
|
+
|
|
870
|
+
if (!('signed_message' in unknownPostOrEvent)) return false
|
|
871
|
+
if (typeof(unknownPostOrEvent.signed_message) !== "string") return false
|
|
872
|
+
const signedObject: NostrSpasmEvent | DmpEvent = JSON.parse(unknownPostOrEvent.signed_message)
|
|
873
|
+
|
|
874
|
+
// isNostrEvent() will return true for NostrSpasmEvent,
|
|
875
|
+
// so the order of calling the functions is important.
|
|
876
|
+
// Sorted by popularity/frequency.
|
|
877
|
+
// TODO: Ideally, refactor and call identifyEvent(), which has
|
|
878
|
+
// if statements and calls isSomething... functions in the right
|
|
879
|
+
// order, depending on whether event has signature, etc.
|
|
880
|
+
// DMP
|
|
881
|
+
if (isDmpEventSignedOpened(signedObject)) return "DmpEventSignedOpened"
|
|
882
|
+
if (isDmpEventSignedClosed(signedObject)) return "DmpEventSignedClosed"
|
|
883
|
+
if (isDmpEvent(signedObject)) return "DmpEvent"
|
|
884
|
+
// Nostr
|
|
885
|
+
if (isNostrSpasmEventSignedOpened(signedObject)) return "NostrSpasmEventSignedOpened"
|
|
886
|
+
if (isNostrSpasmEvent(signedObject)) return "NostrSpasmEvent"
|
|
887
|
+
if (isNostrEventSignedOpened(signedObject)) return "NostrEventSignedOpened"
|
|
888
|
+
if (isNostrEvent(signedObject)) return "NostrEvent"
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export const standartizePostOrEvent = (
|
|
892
|
+
unknownPostOrEvent: UnknownPostOrEvent,
|
|
893
|
+
info?: KnownPostOrEventInfo
|
|
894
|
+
): StandardizedEvent | false => {
|
|
895
|
+
|
|
896
|
+
if (!isObjectWithValues(unknownPostOrEvent)) return false
|
|
897
|
+
|
|
898
|
+
// Info about post/event might be provided.
|
|
899
|
+
// If not, then we should identify an event.
|
|
900
|
+
if (!info) {
|
|
901
|
+
info = identifyPostOrEvent(unknownPostOrEvent)
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
if (!info || !info.webType) return false
|
|
905
|
+
|
|
906
|
+
let standartizedEvent: StandardizedEvent = {} as StandardizedEvent;
|
|
907
|
+
|
|
908
|
+
// DMP event submitted via UI
|
|
909
|
+
if (
|
|
910
|
+
info.eventInfo &&
|
|
911
|
+
info.eventInfo.type === "DmpEventSignedClosed" &&
|
|
912
|
+
info.eventIsSealed === false
|
|
913
|
+
) {
|
|
914
|
+
standartizedEvent = standartizeDmpEventSignedClosed(
|
|
915
|
+
unknownPostOrEvent as DmpEventSignedClosed
|
|
916
|
+
)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// Nostr SPASM event submitted via UI
|
|
920
|
+
if (
|
|
921
|
+
info.eventInfo &&
|
|
922
|
+
info.eventInfo.type === "NostrSpasmEventSignedOpened" &&
|
|
923
|
+
info.eventIsSealed === false
|
|
924
|
+
) {
|
|
925
|
+
standartizedEvent = standartizeNostrSpasmEventSignedOpened(
|
|
926
|
+
unknownPostOrEvent as NostrSpasmEventSignedOpened
|
|
927
|
+
)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// Post with sealed DMP event (received e.g. via SPASM module)
|
|
931
|
+
if (
|
|
932
|
+
info.eventInfo &&
|
|
933
|
+
info.eventInfo.type === "DmpEventSignedClosed" &&
|
|
934
|
+
info.eventIsSealed === true
|
|
935
|
+
) {
|
|
936
|
+
standartizedEvent = standartizePostWithDmpEventSignedClosed(
|
|
937
|
+
unknownPostOrEvent as Post
|
|
938
|
+
)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Post with sealed Nostr event (received e.g. via SPASM module)
|
|
942
|
+
if (
|
|
943
|
+
info.eventInfo &&
|
|
944
|
+
info.eventInfo.type === "NostrSpasmEventSignedOpened" &&
|
|
945
|
+
info.eventIsSealed === true
|
|
946
|
+
) {
|
|
947
|
+
standartizedEvent = standartizePostWithNostrSpasmEventSignedOpened(
|
|
948
|
+
unknownPostOrEvent as Post
|
|
949
|
+
)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return standartizedEvent
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// standardizeDmpEventSignedClosed
|
|
956
|
+
export const standartizeDmpEventSignedClosed = (
|
|
957
|
+
event: DmpEventSignedClosed,
|
|
958
|
+
): StandardizedEvent | null => {
|
|
959
|
+
|
|
960
|
+
if (!isObjectWithValues(event)) return null
|
|
961
|
+
|
|
962
|
+
if (!isDmpEventSignedClosed(event)) return null
|
|
963
|
+
|
|
964
|
+
const signedString = event.signedString
|
|
965
|
+
const signedObject: DmpEvent = JSON.parse(signedString)
|
|
966
|
+
const signature = event.signature
|
|
967
|
+
const signer = event.signer
|
|
968
|
+
const target = signedObject.target
|
|
969
|
+
const action = signedObject.action
|
|
970
|
+
const title = signedObject.title
|
|
971
|
+
const text = signedObject.text
|
|
972
|
+
const signedDate = signedObject.time
|
|
973
|
+
|
|
974
|
+
return {
|
|
975
|
+
signedString,
|
|
976
|
+
signature,
|
|
977
|
+
signer,
|
|
978
|
+
target,
|
|
979
|
+
action,
|
|
980
|
+
title,
|
|
981
|
+
text,
|
|
982
|
+
signedDate
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// standardizeNostrSpasmEventSignedOpened
|
|
987
|
+
export const standartizeNostrSpasmEventSignedOpened = (
|
|
988
|
+
event: NostrSpasmEventSignedOpened,
|
|
989
|
+
): StandardizedEvent | null => {
|
|
990
|
+
|
|
991
|
+
if (!isObjectWithValues(event)) return null
|
|
992
|
+
|
|
993
|
+
if (!isNostrSpasmEventSignedOpened(event)) return null
|
|
994
|
+
|
|
995
|
+
const signedString = JSON.stringify(event)
|
|
996
|
+
// const signedObject: DmpEvent = JSON.parse(signedString)
|
|
997
|
+
const signature = event.sig
|
|
998
|
+
const signer = convertHexToBech32(event.pubkey)
|
|
999
|
+
const text = event.content
|
|
1000
|
+
|
|
1001
|
+
// Convert the Unix timestamp to a JavaScript Date object
|
|
1002
|
+
const date = new Date(event.created_at * 1000);
|
|
1003
|
+
|
|
1004
|
+
// Format the date in ISO format
|
|
1005
|
+
const timestamptz = date.toISOString();
|
|
1006
|
+
|
|
1007
|
+
const signedDate = timestamptz
|
|
1008
|
+
|
|
1009
|
+
let target: string
|
|
1010
|
+
let action: string
|
|
1011
|
+
let title: string
|
|
1012
|
+
|
|
1013
|
+
if (event.tags &&
|
|
1014
|
+
Array.isArray(event.tags)
|
|
1015
|
+
) {
|
|
1016
|
+
event.tags.forEach(function (tag) {
|
|
1017
|
+
if (Array.isArray(tag) && tag[0] === "spasm_target") {
|
|
1018
|
+
target = tag[1]
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (Array.isArray(tag) && tag[0] === "spasm_action") {
|
|
1022
|
+
action = tag[1]
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (Array.isArray(tag) && tag[0] === "spasm_title") {
|
|
1026
|
+
title = tag[1]
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
return {
|
|
1032
|
+
signedString,
|
|
1033
|
+
signature,
|
|
1034
|
+
signer,
|
|
1035
|
+
target,
|
|
1036
|
+
action,
|
|
1037
|
+
title,
|
|
1038
|
+
text,
|
|
1039
|
+
signedDate
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// standartizePostWithDmpEventSignedClosed
|
|
1044
|
+
export const standartizePostWithDmpEventSignedClosed = (
|
|
1045
|
+
post: Post,
|
|
1046
|
+
): StandardizedEvent | null => {
|
|
1047
|
+
|
|
1048
|
+
if (!isObjectWithValues(post)) return null
|
|
1049
|
+
|
|
1050
|
+
if (
|
|
1051
|
+
!('signed_message' in post) ||
|
|
1052
|
+
typeof(post.signed_message) !== "string"
|
|
1053
|
+
) {
|
|
1054
|
+
return null
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const signedString = post.signed_message
|
|
1058
|
+
const signedObject: DmpEvent = JSON.parse(signedString)
|
|
1059
|
+
const signature = post.signature
|
|
1060
|
+
const signer = post.signer
|
|
1061
|
+
const target = signedObject.target
|
|
1062
|
+
const action = signedObject.action
|
|
1063
|
+
const title = signedObject.title
|
|
1064
|
+
const text = signedObject.text
|
|
1065
|
+
const signedDate = signedObject.time
|
|
1066
|
+
|
|
1067
|
+
return {
|
|
1068
|
+
signedString,
|
|
1069
|
+
signature,
|
|
1070
|
+
signer,
|
|
1071
|
+
target,
|
|
1072
|
+
action,
|
|
1073
|
+
title,
|
|
1074
|
+
text,
|
|
1075
|
+
signedDate
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// standardizePostWithNostrSpasmEventSignedOpened
|
|
1080
|
+
export const standartizePostWithNostrSpasmEventSignedOpened = (
|
|
1081
|
+
post: Post,
|
|
1082
|
+
): StandardizedEvent | null => {
|
|
1083
|
+
|
|
1084
|
+
if (!isObjectWithValues(post)) return null
|
|
1085
|
+
|
|
1086
|
+
if (
|
|
1087
|
+
!('signed_message' in post) ||
|
|
1088
|
+
typeof(post.signed_message) !== "string"
|
|
1089
|
+
) {
|
|
1090
|
+
return null
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Extract the event
|
|
1094
|
+
const event = extractSealedEvent(post)
|
|
1095
|
+
|
|
1096
|
+
return standartizeNostrSpasmEventSignedOpened(event as NostrSpasmEventSignedOpened)
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
export const convertToSpasm = (
|
|
1100
|
+
unknownPostOrEvent: UnknownPostOrEvent
|
|
1101
|
+
): SpasmEvent | false => {
|
|
1102
|
+
return standartizePostOrEvent(unknownPostOrEvent)
|
|
1103
|
+
}
|