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.
@@ -0,0 +1,778 @@
1
+ import { isObjectWithValues, convertHexToBech32 } from "./utils";
2
+ // web2 post example
3
+ // webType: "web2",
4
+ // eventIsSealed: false,
5
+ // eventIsSealedUnderKeyName: false,
6
+ // eventInfo: false
7
+ // web3 post example
8
+ // webType: "web3",
9
+ // eventIsSealed: true,
10
+ // eventIsSealedUnderKeyName: "signed_message",
11
+ // eventInfo: {
12
+ // type: "DmpEventSignedClosed",
13
+ // hasSignature: true
14
+ // baseProtocol: "dmp",
15
+ // privateKey: "ethereum",
16
+ // isSpasmCompatible: true,
17
+ // hasExtraSpasmFields: false,
18
+ // }
19
+ // web3 event example
20
+ // webType: "web3",
21
+ // eventIsSealed: false,
22
+ // eventIsSealedUnderKeyName: false,
23
+ // eventInfo: {
24
+ // type: "NostrSpasmEventSignedOpened",
25
+ // hasSignature: true
26
+ // baseProtocol: "nostr",
27
+ // privateKey: "nostr",
28
+ // isSpasmCompatible: true,
29
+ // hasExtraSpasmFields: true,
30
+ // }
31
+ /**
32
+ There are usually 3 types of objects passed to this function:
33
+ - web3 post - an object is a post with a web3 event sealed inside
34
+ - web3 event - an object itself is a web3 event
35
+ - web2 post - an object is a post without a web3 event (e.g. RSS)
36
+ */
37
+ export const identifyPostOrEvent = (unknownPostOrEvent) => {
38
+ const info = {
39
+ webType: false,
40
+ eventIsSealed: false,
41
+ eventIsSealedUnderKeyName: false,
42
+ eventInfo: {
43
+ type: false,
44
+ hasSignature: false,
45
+ baseProtocol: false,
46
+ privateKey: false,
47
+ isSpasmCompatible: false,
48
+ hasExtraSpasmFields: false,
49
+ license: false
50
+ }
51
+ };
52
+ if (!isObjectWithValues(unknownPostOrEvent))
53
+ return info;
54
+ let unknownEventOrWeb2Post;
55
+ // Option 1.
56
+ // If an object is a post with a sealed event (signed string),
57
+ // we need to extract the event by parsing the signed string.
58
+ // We then check if that extracted object is a valid web3 event.
59
+ if (isWeb3Post(unknownPostOrEvent)) {
60
+ info.webType = "web3";
61
+ info.eventIsSealed = true;
62
+ if ('signed_message' in unknownPostOrEvent &&
63
+ typeof (unknownPostOrEvent.signed_message) === "string") {
64
+ const signedObject = JSON.parse(unknownPostOrEvent.signed_message);
65
+ info.eventIsSealedUnderKeyName = 'signed_message';
66
+ unknownEventOrWeb2Post = signedObject;
67
+ // Edge cases.
68
+ // Signed DMP event sealed in the Post under the key
69
+ // 'signed_message' doesn't have signature inside the signed
70
+ // string, so we cannot just parse the string to extract the
71
+ // object and then pass it into an identify function.
72
+ // Instead, we have to attach a signer and signature to the
73
+ // signed string, which is a type of DmpEventSignedClosed.
74
+ if (isDmpEvent(unknownEventOrWeb2Post) &&
75
+ 'signer' in unknownPostOrEvent &&
76
+ typeof (unknownPostOrEvent.signer) === "string" &&
77
+ 'signature' in unknownPostOrEvent &&
78
+ typeof (unknownPostOrEvent.signature) === "string") {
79
+ // Recreating DmpEventSignedClosed
80
+ unknownEventOrWeb2Post = {
81
+ signer: unknownPostOrEvent.signer,
82
+ signedString: unknownPostOrEvent.signed_message,
83
+ signature: unknownPostOrEvent.signature
84
+ };
85
+ }
86
+ }
87
+ // Option 2.
88
+ // If an object doesn't have a sealed event (signed string), then
89
+ // - either the object itself is a web3 event,
90
+ // - or the object is a web2 post (e.g., from an RSS feed).
91
+ }
92
+ else {
93
+ info.webType = false;
94
+ info.eventIsSealed = false;
95
+ info.eventIsSealedUnderKeyName = false;
96
+ unknownEventOrWeb2Post = unknownPostOrEvent;
97
+ }
98
+ const eventInfo = identifyEvent(unknownEventOrWeb2Post);
99
+ // An object has been identified as a web3 event.
100
+ if (eventInfo?.type && typeof (eventInfo.type) === "string") {
101
+ // web3 post and web3 event
102
+ info.webType = "web3";
103
+ info.eventInfo = eventInfo;
104
+ return info;
105
+ // An object has not been identified as a web3 event.
106
+ }
107
+ else {
108
+ // web2 post
109
+ info.webType = "web2";
110
+ info.eventInfo = false;
111
+ return info;
112
+ }
113
+ };
114
+ export const isWeb2Post = (unknownPostOrEvent) => {
115
+ if (!isObjectWithValues(unknownPostOrEvent))
116
+ return false;
117
+ if (
118
+ // signatures
119
+ 'sig' in unknownPostOrEvent ||
120
+ 'signature' in unknownPostOrEvent ||
121
+ 'signed_message' in unknownPostOrEvent ||
122
+ 'signedObject' in unknownPostOrEvent ||
123
+ 'signedString' in unknownPostOrEvent) {
124
+ return false;
125
+ }
126
+ if (isNostrEvent(unknownPostOrEvent))
127
+ return false;
128
+ if (isDmpEvent(unknownPostOrEvent))
129
+ return false;
130
+ if (isDmpEventSignedClosed(unknownPostOrEvent))
131
+ return false;
132
+ if (isDmpEventSignedOpened(unknownPostOrEvent))
133
+ return false;
134
+ return false;
135
+ };
136
+ export const isWeb3Post = (unknownPostOrEvent) => {
137
+ if (!isObjectWithValues(unknownPostOrEvent))
138
+ return false;
139
+ if ('signed_message' in unknownPostOrEvent &&
140
+ typeof (unknownPostOrEvent.signed_message) === "string") {
141
+ return true;
142
+ }
143
+ return false;
144
+ };
145
+ export const identifyEvent = (unknownPostOrEvent) => {
146
+ console.log("identifyEvent called");
147
+ const eventInfo = {
148
+ type: "unknown",
149
+ hasSignature: false,
150
+ baseProtocol: false,
151
+ privateKey: false,
152
+ isSpasmCompatible: false,
153
+ hasExtraSpasmFields: false,
154
+ license: false
155
+ };
156
+ if (!isObjectWithValues(unknownPostOrEvent))
157
+ return eventInfo;
158
+ eventInfo.license = identifyLicense(unknownPostOrEvent);
159
+ // TODO: refactor
160
+ // verifySignature()
161
+ // add key 'isSignatureValid' to EventInfo
162
+ // check privateKey after discovering eventInfo.type
163
+ if (hasSignature(unknownPostOrEvent))
164
+ eventInfo.hasSignature = true;
165
+ // Another approach is to set a private key after identifying
166
+ // the event type (eventInfo.type).
167
+ if (eventInfo.hasSignature) {
168
+ eventInfo.privateKey = identifyPrivateKey(unknownPostOrEvent);
169
+ }
170
+ if (hasExtraSpasmFields(unknownPostOrEvent))
171
+ eventInfo.hasExtraSpasmFields = true;
172
+ // DMP
173
+ // Might be DMP or Nostr or web2 Post
174
+ if (!eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields) {
175
+ if (isDmpEvent(unknownPostOrEvent)) {
176
+ eventInfo.type = "DmpEvent";
177
+ eventInfo.baseProtocol = "dmp";
178
+ eventInfo.isSpasmCompatible = true;
179
+ return eventInfo;
180
+ }
181
+ // Might be DMP or Nostr with signature or Post with signature
182
+ }
183
+ else if (eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields) {
184
+ if (isDmpEventSignedOpened(unknownPostOrEvent)) {
185
+ eventInfo.type = "DmpEventSignedOpened";
186
+ eventInfo.baseProtocol = "dmp";
187
+ eventInfo.isSpasmCompatible = true;
188
+ return eventInfo;
189
+ }
190
+ else if (isDmpEventSignedClosed(unknownPostOrEvent)) {
191
+ eventInfo.type = "DmpEventSignedClosed";
192
+ eventInfo.baseProtocol = "dmp";
193
+ eventInfo.isSpasmCompatible = true;
194
+ return eventInfo;
195
+ }
196
+ }
197
+ // Nostr
198
+ if (eventInfo.hasSignature && eventInfo.hasExtraSpasmFields) {
199
+ // Looks like Nostr event with signature and SPASM fields
200
+ if (isNostrSpasmEventSignedOpened(unknownPostOrEvent)) {
201
+ eventInfo.type = "NostrSpasmEventSignedOpened";
202
+ eventInfo.baseProtocol = "nostr";
203
+ eventInfo.isSpasmCompatible = true;
204
+ return eventInfo;
205
+ }
206
+ }
207
+ else if (eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields) {
208
+ // Looks like Nostr event with signature without SPASM fields
209
+ if (isNostrEventSignedOpened(unknownPostOrEvent)) {
210
+ eventInfo.type = "NostrEventSignedOpened";
211
+ eventInfo.baseProtocol = "nostr";
212
+ eventInfo.isSpasmCompatible = false;
213
+ return eventInfo;
214
+ }
215
+ }
216
+ else if (!eventInfo.hasSignature && eventInfo.hasExtraSpasmFields) {
217
+ // Looks like Nostr event without signature, but with SPASM fields
218
+ if (isNostrSpasmEvent(unknownPostOrEvent)) {
219
+ eventInfo.type = "NostrSpasmEvent";
220
+ eventInfo.baseProtocol = "nostr";
221
+ eventInfo.isSpasmCompatible = true;
222
+ return eventInfo;
223
+ }
224
+ }
225
+ else if (!eventInfo.hasSignature && !eventInfo.hasExtraSpasmFields) {
226
+ // Looks like Nostr event without signature and without SPASM fields
227
+ if (isNostrEvent(unknownPostOrEvent)) {
228
+ eventInfo.type = "NostrEvent";
229
+ eventInfo.baseProtocol = "nostr";
230
+ eventInfo.isSpasmCompatible = false;
231
+ return eventInfo;
232
+ }
233
+ }
234
+ };
235
+ export const hasSignature = (unknownPostOrEvent, signatureKey, signatureLength = 40) => {
236
+ if (!isObjectWithValues(unknownPostOrEvent))
237
+ return false;
238
+ let keys = signatureKey
239
+ ? [signatureKey] // signature key is provided
240
+ : ['signature', 'sig']; // check all signature keys
241
+ for (let key of keys) {
242
+ if (key in unknownPostOrEvent &&
243
+ typeof (unknownPostOrEvent[key]) === 'string' &&
244
+ unknownPostOrEvent[key].length > signatureLength) {
245
+ return true;
246
+ }
247
+ }
248
+ return false;
249
+ };
250
+ export const identifyLicense = (unknownPostOrEvent) => {
251
+ if (!isObjectWithValues(unknownPostOrEvent))
252
+ return false;
253
+ let license = false;
254
+ // Option 1.
255
+ // A license can be inside a 'license' key
256
+ // or inside tags (e.g. SPASM tags in Nostr events).
257
+ if ('license' in unknownPostOrEvent &&
258
+ typeof (unknownPostOrEvent['license']) === 'string' &&
259
+ unknownPostOrEvent['license'].length > 0) {
260
+ license = unknownPostOrEvent['license'];
261
+ if (license)
262
+ return license;
263
+ }
264
+ license = identifyLicenseInsideTags(unknownPostOrEvent);
265
+ if (license)
266
+ return license;
267
+ // Option 2.
268
+ // If no license was found, then we should try to extract
269
+ // an object (event) from a signed string if such a string
270
+ // exists, and then check that object for a license.
271
+ const signedObject = extractSealedEvent(unknownPostOrEvent);
272
+ if (!signedObject)
273
+ return false;
274
+ if (!isObjectWithValues(signedObject))
275
+ return false;
276
+ if ('license' in signedObject &&
277
+ typeof (signedObject['license']) === 'string' &&
278
+ signedObject['license'].length > 0) {
279
+ license = signedObject['license'];
280
+ if (license)
281
+ return license;
282
+ }
283
+ license = identifyLicenseInsideTags(signedObject);
284
+ if (license)
285
+ return license;
286
+ return false;
287
+ };
288
+ export const identifyLicenseInsideTags = (unknownPostOrEvent) => {
289
+ if (!isObjectWithValues(unknownPostOrEvent))
290
+ return false;
291
+ let license = false;
292
+ // A license can be placed inside SPASM tags
293
+ if ('tags' in unknownPostOrEvent &&
294
+ Array.isArray(unknownPostOrEvent.tags)) {
295
+ // Nostr events have tags of array type: NostrSpasmTag | AnyTag
296
+ // Post events have tags of string type: string
297
+ unknownPostOrEvent.tags.forEach(function (tag) {
298
+ if (Array.isArray(tag)) {
299
+ if (tag[0] === "license" &&
300
+ typeof (tag[1]) === "string") {
301
+ license = tag[1];
302
+ }
303
+ }
304
+ });
305
+ }
306
+ return license;
307
+ };
308
+ export const extractSealedEvent = (unknownPostOrEvent) => {
309
+ if (!isObjectWithValues(unknownPostOrEvent))
310
+ return false;
311
+ let signedObject = false;
312
+ if ('signed_message' in unknownPostOrEvent &&
313
+ typeof (unknownPostOrEvent['signed_message'] === "string")) {
314
+ signedObject = JSON.parse(unknownPostOrEvent['signed_message']);
315
+ }
316
+ else if ('signedString' in unknownPostOrEvent &&
317
+ typeof (unknownPostOrEvent['signedString'] === "string")) {
318
+ signedObject = JSON.parse(unknownPostOrEvent['signedString']);
319
+ }
320
+ return signedObject;
321
+ };
322
+ export const identifyPrivateKey = (unknownPostOrEvent) => {
323
+ if (!isObjectWithValues(unknownPostOrEvent))
324
+ return false;
325
+ /**
326
+ * If an object has 'sig' key, then it's most likely a Nostr event.
327
+ * Currently, all Nostr events can only be signed with a Nostr private
328
+ * key, so we can assume that a private key is "nostr".
329
+ * In the future there might be an option to sign Nostr events with
330
+ * different keys, so we will have to update the logic.
331
+ */
332
+ if ('sig' in unknownPostOrEvent &&
333
+ typeof (unknownPostOrEvent['sig']) === 'string' &&
334
+ unknownPostOrEvent['sig'].length > 40) {
335
+ return 'nostr';
336
+ }
337
+ /**
338
+ * A post with a sealed event (hidden inside the signed string)
339
+ * under the key like 'signed_message' or 'signedObject' will
340
+ * usually have a signature inside a 'signature' key.
341
+ *
342
+ * So if an object has a 'signature' key, then it can be:
343
+ * - a DMP event signed with the Ethereum private key,
344
+ * - has 'signature'
345
+ * - a Post with a sealed DMP event signed with an Ethereum private key,
346
+ * - has 'signature'
347
+ * - a Post with a sealed Nostr event signed with a Nostr private key.
348
+ * - has 'signature'
349
+ * - and also has 'sig' inside 'signed_message'
350
+ *
351
+ * In other words, a Post with a sealed Nostr event will have
352
+ * the same signature recorded twice in different places:
353
+ * 1. Inside a 'signature' key in the Post (envelope).
354
+ * 2. Inside a 'sig' key in the object extracted from a signed string.
355
+ * While, a Post with a sealed DMP event will only have the signature
356
+ * specified once inside the 'signature' key.
357
+ *
358
+ * Thus, we have to convert the signed string into an object,
359
+ * then check whether it has a 'sig' key (Nostr event),
360
+ * otherwise assume that it was signed with an Ethereum private key.
361
+ */
362
+ if ('signature' in unknownPostOrEvent &&
363
+ typeof (unknownPostOrEvent?.['signature']) === 'string' &&
364
+ unknownPostOrEvent?.['signature'].length > 40) {
365
+ if ('signed_message' in unknownPostOrEvent &&
366
+ typeof (unknownPostOrEvent?.['signed_message']) === 'string') {
367
+ const signedObject = JSON.parse(unknownPostOrEvent?.['signed_message']);
368
+ if (!isObjectWithValues(signedObject))
369
+ return false;
370
+ if ('sig' in unknownPostOrEvent &&
371
+ typeof (unknownPostOrEvent['sig']) === 'string' &&
372
+ unknownPostOrEvent['sig'].length > 40) {
373
+ return 'nostr';
374
+ }
375
+ else {
376
+ return 'ethereum';
377
+ }
378
+ }
379
+ if ('signedString' in unknownPostOrEvent &&
380
+ typeof (unknownPostOrEvent?.['signedString']) === 'string') {
381
+ const signedObject = JSON.parse(unknownPostOrEvent?.['signedString']);
382
+ if (!isObjectWithValues(signedObject))
383
+ return false;
384
+ if (isDmpEvent(signedObject))
385
+ return 'ethereum';
386
+ }
387
+ }
388
+ };
389
+ export const hasExtraSpasmFields = (unknownPostOrEvent) => {
390
+ if (!isObjectWithValues(unknownPostOrEvent))
391
+ return false;
392
+ if ('tags' in unknownPostOrEvent &&
393
+ Array.isArray(unknownPostOrEvent.tags)) {
394
+ // spasm_version is optional
395
+ let hasSpasmVersion = false;
396
+ // spasm_target is optional (e.g. new posts have no target)
397
+ let hasSpasmTarget = false;
398
+ // spasm_action is optional for Nostr (can be taken from 'kind' key)
399
+ let hasSpasmAction = false;
400
+ // spasm_title is optional (e.g. comments/reactions have no title)
401
+ let hasSpasmTitle = false;
402
+ // Nostr events have tags of array type: NostrSpasmTag | AnyTag
403
+ // Post events have tags of string type: string
404
+ unknownPostOrEvent.tags.forEach(function (tag) {
405
+ if (Array.isArray(tag)) {
406
+ if (tag[0] === "spasm_version") {
407
+ hasSpasmVersion = true;
408
+ }
409
+ if (tag[0] === "spasm_target") {
410
+ hasSpasmTarget = true;
411
+ }
412
+ if (tag[0] === "spasm_action") {
413
+ hasSpasmAction = true;
414
+ }
415
+ if (tag[0] === "spasm_title") {
416
+ hasSpasmTitle = true;
417
+ }
418
+ }
419
+ });
420
+ if (hasSpasmVersion || hasSpasmTarget || hasSpasmAction || hasSpasmTitle) {
421
+ return true;
422
+ }
423
+ }
424
+ return false;
425
+ };
426
+ export const isNostrEvent = (unknownPostOrEvent) => {
427
+ if (!isObjectWithValues(unknownPostOrEvent))
428
+ return false;
429
+ // Unsigned Nostr event can be without 'id'
430
+ // if (!('id' in unknownPostOrEvent)) return false
431
+ // content
432
+ if (!('content' in unknownPostOrEvent))
433
+ return false;
434
+ if (!unknownPostOrEvent.content)
435
+ return false;
436
+ if (typeof (unknownPostOrEvent.content) !== "string")
437
+ return false;
438
+ // created_at
439
+ if (!('created_at' in unknownPostOrEvent))
440
+ return false;
441
+ // 0 is a valid created_at
442
+ if (!unknownPostOrEvent.created_at &&
443
+ unknownPostOrEvent.created_at !== 0)
444
+ return false;
445
+ if (typeof (unknownPostOrEvent.created_at) !== "number")
446
+ return false;
447
+ // kind
448
+ if (!('kind' in unknownPostOrEvent))
449
+ return false;
450
+ // 0 is a valid kind
451
+ if (!unknownPostOrEvent.kind &&
452
+ unknownPostOrEvent.kind !== 0)
453
+ return false;
454
+ if (typeof (unknownPostOrEvent.kind) !== "number")
455
+ return false;
456
+ // pubkey
457
+ if (!('pubkey' in unknownPostOrEvent))
458
+ return false;
459
+ if (!unknownPostOrEvent.pubkey)
460
+ return false;
461
+ if (typeof (unknownPostOrEvent.pubkey) !== "string")
462
+ return false;
463
+ // tags
464
+ // TODO: check if tags is a mandatory field
465
+ // if (!('tags' in unknownPostOrEvent)) return false
466
+ // sig
467
+ // Unsigned Nostr event can be without 'sig'
468
+ // if (!('sig' in unknownPostOrEvent)) return false
469
+ return true;
470
+ };
471
+ export const isNostrEventSignedOpened = (unknownPostOrEvent) => {
472
+ if (!isObjectWithValues(unknownPostOrEvent))
473
+ return false;
474
+ if (!isNostrEvent(unknownPostOrEvent))
475
+ return false;
476
+ // Signed Nostr event must have 'id'
477
+ if (!('id' in unknownPostOrEvent))
478
+ return false;
479
+ if (!hasSignature(unknownPostOrEvent, 'sig'))
480
+ return false;
481
+ return true;
482
+ };
483
+ export const isNostrSpasmEvent = (unknownPostOrEvent) => {
484
+ if (!isObjectWithValues(unknownPostOrEvent))
485
+ return false;
486
+ if (!isNostrEvent(unknownPostOrEvent))
487
+ return false;
488
+ if (!hasExtraSpasmFields(unknownPostOrEvent))
489
+ return false;
490
+ return true;
491
+ };
492
+ export const isNostrSpasmEventSignedOpened = (unknownPostOrEvent) => {
493
+ if (!isObjectWithValues(unknownPostOrEvent))
494
+ return false;
495
+ if (!isNostrSpasmEvent(unknownPostOrEvent))
496
+ return false;
497
+ if (!isNostrEventSignedOpened(unknownPostOrEvent))
498
+ return false;
499
+ return true;
500
+ };
501
+ export const isDmpEvent = (unknownPostOrEvent) => {
502
+ if (!isObjectWithValues(unknownPostOrEvent))
503
+ return false;
504
+ // TODO: think what if unknownPostOrEvent is Post with version, action, license
505
+ if (!('version' in unknownPostOrEvent))
506
+ return false;
507
+ if (!('action' in unknownPostOrEvent))
508
+ return false;
509
+ if (!('license' in unknownPostOrEvent))
510
+ return false;
511
+ // time is optional
512
+ // if (!('time' in unknownPostOrEvent)) return false
513
+ // target is optional (e.g. a new post has no target)
514
+ // if (!('target' in unknownPostOrEvent)) return false
515
+ // title is optional (e.g. a comment has no title)
516
+ // if (!('title' in unknownPostOrEvent)) return false
517
+ // text is optional (e.g. an event has only title)
518
+ // if (!('text' in unknownPostOrEvent)) return false
519
+ if (!unknownPostOrEvent?.version?.startsWith("dmp"))
520
+ return false;
521
+ return true;
522
+ };
523
+ export const isDmpEventSignedClosed = (unknownPostOrEvent) => {
524
+ if (!isObjectWithValues(unknownPostOrEvent))
525
+ return false;
526
+ if (!('signedString' in unknownPostOrEvent))
527
+ return false;
528
+ if (!('signature' in unknownPostOrEvent))
529
+ return false;
530
+ // signer is optional
531
+ // if (!('signer' in unknownPostOrEvent)) return false
532
+ if (typeof (unknownPostOrEvent.signedString) !== "string")
533
+ return false;
534
+ const signedObject = JSON.parse(unknownPostOrEvent.signedString);
535
+ if (!isDmpEvent(signedObject))
536
+ return false;
537
+ return true;
538
+ };
539
+ export const isDmpEventSignedOpened = (unknownPostOrEvent) => {
540
+ if (!isObjectWithValues(unknownPostOrEvent))
541
+ return false;
542
+ if (!isDmpEventSignedClosed)
543
+ return false;
544
+ if ('signedObject' in unknownPostOrEvent) {
545
+ if (isDmpEvent(unknownPostOrEvent.signedObject))
546
+ return true;
547
+ }
548
+ return false;
549
+ };
550
+ /**
551
+ * 1. Post can contain DPM event without signature as a string inside
552
+ * signed_message, so signature is attached with 'signature' key.
553
+ *
554
+ * isPostWithDmpEventSignedOpened()
555
+ * - contains isDmpEventSignedOpened()
556
+ * isPostWithDmpEventSignedClosed()
557
+ * - contains isDmpEventSignedClosed()
558
+ * isPostWithDmpEvent()
559
+ * - contains isDmpEvent()
560
+ *
561
+ * 2. Post can contain Nostr event with signature as a string inside
562
+ * signed_message, and signature is also attached with 'signature' key.
563
+ * In other words, signature will be passed twice:
564
+ * - inside 'sig' key of parsed 'signed_message'
565
+ * - inside 'signature' key of the post object.
566
+ *
567
+ * isPostWithNostrSpasmEventSignedOpened()
568
+ * - contains isNostrSpasmEventSignedOpened()
569
+ * isPostWithNostrEventSignedOpened()
570
+ * - contains isNostrEventSignedOpened()
571
+ * isPostWithNostrSpasmEvent()
572
+ * - contains isNostrSpasmEvent()
573
+ * isPostWithNostrEvent
574
+ * - contains isNostrEvent()
575
+ */
576
+ export const isPostWithDmpEvent = (unknownPostOrEvent) => {
577
+ return !!(identifyEventInsidePost(unknownPostOrEvent) === "DmpEvent");
578
+ };
579
+ export const isPostWithDmpEventSignedOpened = (unknownPostOrEvent) => {
580
+ return !!(identifyEventInsidePost(unknownPostOrEvent) === "DmpEventSignedOpened");
581
+ };
582
+ export const isPostWithDmpEventSignedClosed = (unknownPostOrEvent) => {
583
+ return !!(identifyEventInsidePost(unknownPostOrEvent) === "DmpEventSignedClosed");
584
+ };
585
+ export const isPostWithNostrSpasmEventSignedOpened = (unknownPostOrEvent) => {
586
+ return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrSpasmEventSignedOpened");
587
+ };
588
+ export const isPostWithNostrEventSignedOpened = (unknownPostOrEvent) => {
589
+ return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrEventSignedOpened");
590
+ };
591
+ export const isPostWithNostrSpasmEvent = (unknownPostOrEvent) => {
592
+ return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrSpasmEvent");
593
+ };
594
+ export const isPostWithNostrEvent = (unknownPostOrEvent) => {
595
+ return !!(identifyEventInsidePost(unknownPostOrEvent) === "NostrEvent");
596
+ };
597
+ export const identifyEventInsidePost = (unknownPostOrEvent) => {
598
+ if (!isObjectWithValues(unknownPostOrEvent))
599
+ return false;
600
+ if (!('signed_message' in unknownPostOrEvent))
601
+ return false;
602
+ if (typeof (unknownPostOrEvent.signed_message) !== "string")
603
+ return false;
604
+ const signedObject = JSON.parse(unknownPostOrEvent.signed_message);
605
+ // isNostrEvent() will return true for NostrSpasmEvent,
606
+ // so the order of calling the functions is important.
607
+ // Sorted by popularity/frequency.
608
+ // TODO: Ideally, refactor and call identifyEvent(), which has
609
+ // if statements and calls isSomething... functions in the right
610
+ // order, depending on whether event has signature, etc.
611
+ // DMP
612
+ if (isDmpEventSignedOpened(signedObject))
613
+ return "DmpEventSignedOpened";
614
+ if (isDmpEventSignedClosed(signedObject))
615
+ return "DmpEventSignedClosed";
616
+ if (isDmpEvent(signedObject))
617
+ return "DmpEvent";
618
+ // Nostr
619
+ if (isNostrSpasmEventSignedOpened(signedObject))
620
+ return "NostrSpasmEventSignedOpened";
621
+ if (isNostrSpasmEvent(signedObject))
622
+ return "NostrSpasmEvent";
623
+ if (isNostrEventSignedOpened(signedObject))
624
+ return "NostrEventSignedOpened";
625
+ if (isNostrEvent(signedObject))
626
+ return "NostrEvent";
627
+ };
628
+ export const standartizePostOrEvent = (unknownPostOrEvent, info) => {
629
+ if (!isObjectWithValues(unknownPostOrEvent))
630
+ return false;
631
+ // Info about post/event might be provided.
632
+ // If not, then we should identify an event.
633
+ if (!info) {
634
+ info = identifyPostOrEvent(unknownPostOrEvent);
635
+ }
636
+ if (!info || !info.webType)
637
+ return false;
638
+ let standartizedEvent = {};
639
+ // DMP event submitted via UI
640
+ if (info.eventInfo &&
641
+ info.eventInfo.type === "DmpEventSignedClosed" &&
642
+ info.eventIsSealed === false) {
643
+ standartizedEvent = standartizeDmpEventSignedClosed(unknownPostOrEvent);
644
+ }
645
+ // Nostr SPASM event submitted via UI
646
+ if (info.eventInfo &&
647
+ info.eventInfo.type === "NostrSpasmEventSignedOpened" &&
648
+ info.eventIsSealed === false) {
649
+ standartizedEvent = standartizeNostrSpasmEventSignedOpened(unknownPostOrEvent);
650
+ }
651
+ // Post with sealed DMP event (received e.g. via SPASM module)
652
+ if (info.eventInfo &&
653
+ info.eventInfo.type === "DmpEventSignedClosed" &&
654
+ info.eventIsSealed === true) {
655
+ standartizedEvent = standartizePostWithDmpEventSignedClosed(unknownPostOrEvent);
656
+ }
657
+ // Post with sealed Nostr event (received e.g. via SPASM module)
658
+ if (info.eventInfo &&
659
+ info.eventInfo.type === "NostrSpasmEventSignedOpened" &&
660
+ info.eventIsSealed === true) {
661
+ standartizedEvent = standartizePostWithNostrSpasmEventSignedOpened(unknownPostOrEvent);
662
+ }
663
+ return standartizedEvent;
664
+ };
665
+ // standardizeDmpEventSignedClosed
666
+ export const standartizeDmpEventSignedClosed = (event) => {
667
+ if (!isObjectWithValues(event))
668
+ return null;
669
+ if (!isDmpEventSignedClosed(event))
670
+ return null;
671
+ const signedString = event.signedString;
672
+ const signedObject = JSON.parse(signedString);
673
+ const signature = event.signature;
674
+ const signer = event.signer;
675
+ const target = signedObject.target;
676
+ const action = signedObject.action;
677
+ const title = signedObject.title;
678
+ const text = signedObject.text;
679
+ const signedDate = signedObject.time;
680
+ return {
681
+ signedString,
682
+ signature,
683
+ signer,
684
+ target,
685
+ action,
686
+ title,
687
+ text,
688
+ signedDate
689
+ };
690
+ };
691
+ // standardizeNostrSpasmEventSignedOpened
692
+ export const standartizeNostrSpasmEventSignedOpened = (event) => {
693
+ if (!isObjectWithValues(event))
694
+ return null;
695
+ if (!isNostrSpasmEventSignedOpened(event))
696
+ return null;
697
+ const signedString = JSON.stringify(event);
698
+ // const signedObject: DmpEvent = JSON.parse(signedString)
699
+ const signature = event.sig;
700
+ const signer = convertHexToBech32(event.pubkey);
701
+ const text = event.content;
702
+ // Convert the Unix timestamp to a JavaScript Date object
703
+ const date = new Date(event.created_at * 1000);
704
+ // Format the date in ISO format
705
+ const timestamptz = date.toISOString();
706
+ const signedDate = timestamptz;
707
+ let target;
708
+ let action;
709
+ let title;
710
+ if (event.tags &&
711
+ Array.isArray(event.tags)) {
712
+ event.tags.forEach(function (tag) {
713
+ if (Array.isArray(tag) && tag[0] === "spasm_target") {
714
+ target = tag[1];
715
+ }
716
+ if (Array.isArray(tag) && tag[0] === "spasm_action") {
717
+ action = tag[1];
718
+ }
719
+ if (Array.isArray(tag) && tag[0] === "spasm_title") {
720
+ title = tag[1];
721
+ }
722
+ });
723
+ }
724
+ return {
725
+ signedString,
726
+ signature,
727
+ signer,
728
+ target,
729
+ action,
730
+ title,
731
+ text,
732
+ signedDate
733
+ };
734
+ };
735
+ // standartizePostWithDmpEventSignedClosed
736
+ export const standartizePostWithDmpEventSignedClosed = (post) => {
737
+ if (!isObjectWithValues(post))
738
+ return null;
739
+ if (!('signed_message' in post) ||
740
+ typeof (post.signed_message) !== "string") {
741
+ return null;
742
+ }
743
+ const signedString = post.signed_message;
744
+ const signedObject = JSON.parse(signedString);
745
+ const signature = post.signature;
746
+ const signer = post.signer;
747
+ const target = signedObject.target;
748
+ const action = signedObject.action;
749
+ const title = signedObject.title;
750
+ const text = signedObject.text;
751
+ const signedDate = signedObject.time;
752
+ return {
753
+ signedString,
754
+ signature,
755
+ signer,
756
+ target,
757
+ action,
758
+ title,
759
+ text,
760
+ signedDate
761
+ };
762
+ };
763
+ // standardizePostWithNostrSpasmEventSignedOpened
764
+ export const standartizePostWithNostrSpasmEventSignedOpened = (post) => {
765
+ if (!isObjectWithValues(post))
766
+ return null;
767
+ if (!('signed_message' in post) ||
768
+ typeof (post.signed_message) !== "string") {
769
+ return null;
770
+ }
771
+ // Extract the event
772
+ const event = extractSealedEvent(post);
773
+ return standartizeNostrSpasmEventSignedOpened(event);
774
+ };
775
+ export const convertToSpasm = (unknownPostOrEvent) => {
776
+ return standartizePostOrEvent(unknownPostOrEvent);
777
+ };
778
+ //# sourceMappingURL=identifyEvent.js.map