applesauce-core 0.9.0 → 0.11.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.
Files changed (153) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/fixtures.d.ts +8 -0
  3. package/dist/__tests__/fixtures.js +20 -0
  4. package/dist/event-store/__tests__/event-store.test.js +259 -0
  5. package/dist/event-store/database.d.ts +22 -16
  6. package/dist/event-store/database.js +62 -39
  7. package/dist/event-store/event-store.d.ts +52 -15
  8. package/dist/event-store/event-store.js +283 -191
  9. package/dist/helpers/__tests__/blossom.test.d.ts +1 -0
  10. package/dist/helpers/__tests__/blossom.test.js +13 -0
  11. package/dist/helpers/__tests__/comment.test.d.ts +1 -0
  12. package/dist/helpers/__tests__/comment.test.js +235 -0
  13. package/dist/helpers/__tests__/emoji.test.d.ts +1 -0
  14. package/dist/helpers/__tests__/emoji.test.js +15 -0
  15. package/dist/helpers/__tests__/event.test.d.ts +1 -0
  16. package/dist/helpers/__tests__/event.test.js +36 -0
  17. package/dist/helpers/__tests__/file-metadata.test.d.ts +1 -0
  18. package/dist/helpers/__tests__/file-metadata.test.js +103 -0
  19. package/dist/helpers/__tests__/hidden-tags.test.d.ts +1 -0
  20. package/dist/helpers/__tests__/hidden-tags.test.js +29 -0
  21. package/dist/helpers/__tests__/mailboxes.test.d.ts +1 -0
  22. package/dist/helpers/{mailboxes.test.js → __tests__/mailboxes.test.js} +14 -13
  23. package/dist/helpers/__tests__/relays.test.d.ts +1 -0
  24. package/dist/helpers/__tests__/relays.test.js +21 -0
  25. package/dist/helpers/__tests__/tags.test.d.ts +1 -0
  26. package/dist/helpers/__tests__/tags.test.js +24 -0
  27. package/dist/helpers/__tests__/threading.test.d.ts +1 -0
  28. package/dist/helpers/__tests__/threading.test.js +41 -0
  29. package/dist/helpers/blossom.d.ts +9 -0
  30. package/dist/helpers/blossom.js +22 -0
  31. package/dist/helpers/bolt11.d.ts +1 -0
  32. package/dist/helpers/bolt11.js +1 -0
  33. package/dist/helpers/bookmarks.d.ts +15 -0
  34. package/dist/helpers/bookmarks.js +27 -0
  35. package/dist/helpers/channels.d.ts +10 -0
  36. package/dist/helpers/channels.js +27 -0
  37. package/dist/helpers/comment.d.ts +47 -0
  38. package/dist/helpers/comment.js +120 -0
  39. package/dist/helpers/contacts.d.ts +3 -0
  40. package/dist/helpers/contacts.js +25 -0
  41. package/dist/helpers/content.d.ts +3 -0
  42. package/dist/helpers/content.js +8 -0
  43. package/dist/helpers/delete.d.ts +3 -0
  44. package/dist/helpers/delete.js +7 -0
  45. package/dist/helpers/dns-identity.d.ts +7 -0
  46. package/dist/helpers/dns-identity.js +10 -0
  47. package/dist/helpers/emoji.d.ts +12 -1
  48. package/dist/helpers/emoji.js +13 -1
  49. package/dist/helpers/event.d.ts +17 -3
  50. package/dist/helpers/event.js +54 -12
  51. package/dist/helpers/external-id.d.ts +29 -0
  52. package/dist/helpers/external-id.js +20 -0
  53. package/dist/helpers/file-metadata.d.ts +55 -0
  54. package/dist/helpers/file-metadata.js +99 -0
  55. package/dist/helpers/filter.d.ts +4 -2
  56. package/dist/helpers/filter.js +36 -7
  57. package/dist/helpers/groups.d.ts +24 -0
  58. package/dist/helpers/groups.js +39 -0
  59. package/dist/helpers/hidden-tags.d.ts +48 -0
  60. package/dist/helpers/hidden-tags.js +86 -0
  61. package/dist/helpers/index.d.ts +28 -8
  62. package/dist/helpers/index.js +28 -8
  63. package/dist/helpers/json.d.ts +1 -0
  64. package/dist/helpers/json.js +1 -0
  65. package/dist/helpers/lists.d.ts +28 -0
  66. package/dist/helpers/lists.js +65 -0
  67. package/dist/helpers/lnurl.d.ts +4 -0
  68. package/dist/helpers/lnurl.js +40 -0
  69. package/dist/helpers/mailboxes.js +16 -9
  70. package/dist/helpers/mutes.d.ts +14 -0
  71. package/dist/helpers/mutes.js +23 -0
  72. package/dist/helpers/picture-post.d.ts +4 -0
  73. package/dist/helpers/picture-post.js +6 -0
  74. package/dist/helpers/pointers.d.ts +38 -5
  75. package/dist/helpers/pointers.js +105 -25
  76. package/dist/helpers/profile.d.ts +6 -1
  77. package/dist/helpers/profile.js +5 -1
  78. package/dist/helpers/relays.d.ts +6 -3
  79. package/dist/helpers/relays.js +25 -18
  80. package/dist/helpers/share.d.ts +4 -0
  81. package/dist/helpers/share.js +12 -0
  82. package/dist/helpers/string.d.ts +6 -0
  83. package/dist/helpers/string.js +2 -0
  84. package/dist/helpers/tags.d.ts +23 -0
  85. package/dist/helpers/tags.js +34 -6
  86. package/dist/helpers/threading.d.ts +6 -6
  87. package/dist/helpers/threading.js +30 -9
  88. package/dist/helpers/url.d.ts +11 -1
  89. package/dist/helpers/url.js +31 -3
  90. package/dist/helpers/user-status.d.ts +18 -0
  91. package/dist/helpers/user-status.js +21 -0
  92. package/dist/helpers/zap.d.ts +25 -0
  93. package/dist/helpers/zap.js +32 -3
  94. package/dist/observable/__tests__/claim-events.test.d.ts +1 -0
  95. package/dist/observable/__tests__/claim-events.test.js +23 -0
  96. package/dist/observable/__tests__/claim-latest.test.d.ts +1 -0
  97. package/dist/observable/__tests__/claim-latest.test.js +37 -0
  98. package/dist/observable/__tests__/simple-timeout.test.d.ts +1 -0
  99. package/dist/observable/__tests__/simple-timeout.test.js +34 -0
  100. package/dist/observable/claim-events.d.ts +5 -0
  101. package/dist/observable/claim-events.js +28 -0
  102. package/dist/observable/claim-latest.d.ts +4 -0
  103. package/dist/observable/claim-latest.js +20 -0
  104. package/dist/observable/get-observable-value.d.ts +3 -0
  105. package/dist/observable/get-observable-value.js +9 -0
  106. package/dist/observable/index.d.ts +2 -1
  107. package/dist/observable/index.js +2 -1
  108. package/dist/observable/share-latest-value.d.ts +2 -4
  109. package/dist/observable/share-latest-value.js +19 -16
  110. package/dist/observable/simple-timeout.d.ts +4 -0
  111. package/dist/observable/simple-timeout.js +6 -0
  112. package/dist/promise/deferred.d.ts +1 -0
  113. package/dist/promise/deferred.js +1 -0
  114. package/dist/queries/blossom.d.ts +2 -0
  115. package/dist/queries/blossom.js +10 -0
  116. package/dist/queries/bookmarks.d.ts +8 -0
  117. package/dist/queries/bookmarks.js +23 -0
  118. package/dist/queries/channels.d.ts +11 -0
  119. package/dist/queries/channels.js +73 -0
  120. package/dist/queries/comments.d.ts +4 -0
  121. package/dist/queries/comments.js +14 -0
  122. package/dist/queries/contacts.d.ts +3 -0
  123. package/dist/queries/contacts.js +12 -0
  124. package/dist/queries/index.d.ts +9 -2
  125. package/dist/queries/index.js +9 -2
  126. package/dist/queries/mailboxes.d.ts +1 -0
  127. package/dist/queries/mailboxes.js +1 -0
  128. package/dist/queries/mutes.d.ts +8 -0
  129. package/dist/queries/mutes.js +23 -0
  130. package/dist/queries/pins.d.ts +3 -0
  131. package/dist/queries/pins.js +12 -0
  132. package/dist/queries/profile.d.ts +1 -0
  133. package/dist/queries/profile.js +1 -0
  134. package/dist/queries/reactions.d.ts +1 -1
  135. package/dist/queries/reactions.js +1 -1
  136. package/dist/queries/simple.d.ts +4 -4
  137. package/dist/queries/simple.js +13 -13
  138. package/dist/queries/thread.d.ts +2 -0
  139. package/dist/queries/thread.js +30 -4
  140. package/dist/queries/user-status.d.ts +11 -0
  141. package/dist/queries/user-status.js +39 -0
  142. package/dist/queries/zaps.d.ts +1 -0
  143. package/dist/queries/zaps.js +1 -0
  144. package/dist/query-store/index.d.ts +1 -47
  145. package/dist/query-store/index.js +1 -60
  146. package/dist/query-store/query-store.d.ts +51 -0
  147. package/dist/query-store/query-store.js +88 -0
  148. package/dist/query-store/query-store.test.d.ts +1 -0
  149. package/dist/query-store/query-store.test.js +33 -0
  150. package/package.json +24 -21
  151. package/dist/observable/getValue.d.ts +0 -2
  152. package/dist/observable/getValue.js +0 -13
  153. /package/dist/{helpers/mailboxes.test.d.ts → event-store/__tests__/event-store.test.d.ts} +0 -0
@@ -0,0 +1,235 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getCommentAddressPointer, getCommentEventPointer, getCommentExternalPointer, getCommentReplyPointer, getCommentRootPointer, } from "../comment.js";
3
+ import { FakeUser } from "../../__tests__/fixtures.js";
4
+ const user = new FakeUser();
5
+ describe("getCommentRootPointer", () => {
6
+ it("should throw if event is not a comment", () => {
7
+ expect(() => {
8
+ getCommentRootPointer(user.note("testing"));
9
+ }).toThrow("Event is not a comment");
10
+ });
11
+ });
12
+ describe("getCommentReplyPointer", () => {
13
+ it("should throw if event is not a comment", () => {
14
+ expect(() => {
15
+ getCommentReplyPointer(user.note("testing"));
16
+ }).toThrow("Event is not a comment");
17
+ });
18
+ });
19
+ describe("getCommentEventPointer", () => {
20
+ it("should get pubkey from P tag when root=true", () => {
21
+ const tags = [
22
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
23
+ ["K", "1621"],
24
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
25
+ ["k", "1621"],
26
+ ["p", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
27
+ ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
28
+ ];
29
+ expect(getCommentEventPointer(tags, true)).toEqual({
30
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
31
+ kind: 1621,
32
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
33
+ relay: undefined,
34
+ });
35
+ });
36
+ it("should default to pubkey in E tag when root pubkey do not match", () => {
37
+ const tags = [
38
+ [
39
+ "E",
40
+ "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
41
+ "",
42
+ "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
43
+ ],
44
+ ["K", "1621"],
45
+ ["P", "bad-pubkey"],
46
+ ];
47
+ expect(getCommentEventPointer(tags, true)).toEqual({
48
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
49
+ kind: 1621,
50
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
51
+ relay: undefined,
52
+ });
53
+ });
54
+ it("should get pubkey from E tag", () => {
55
+ const tags = [
56
+ [
57
+ "E",
58
+ "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
59
+ "",
60
+ "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
61
+ ],
62
+ ["K", "1621"],
63
+ ];
64
+ expect(getCommentEventPointer(tags, true)).toEqual({
65
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
66
+ kind: 1621,
67
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
68
+ relay: undefined,
69
+ });
70
+ });
71
+ it("should get relay from E tag", () => {
72
+ const tags = [
73
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
74
+ ["K", "1621"],
75
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
76
+ ];
77
+ expect(getCommentEventPointer(tags, true)).toEqual({
78
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
79
+ kind: 1621,
80
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
81
+ relay: "wss://relay.io/",
82
+ });
83
+ });
84
+ it("should throw if K tag is missing", () => {
85
+ const tags = [
86
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
87
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
88
+ ];
89
+ expect(() => {
90
+ getCommentEventPointer(tags, true);
91
+ }).toThrow("Missing kind tag");
92
+ });
93
+ it("should return null if missing E tag", () => {
94
+ const tags = [
95
+ ["K", "1621"],
96
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
97
+ ];
98
+ expect(getCommentEventPointer(tags, true)).toBe(null);
99
+ expect(getCommentEventPointer(tags)).toBe(null);
100
+ });
101
+ });
102
+ describe("getCommentAddressPointer", () => {
103
+ it("should get event id from E tag", () => {
104
+ // root
105
+ expect(getCommentAddressPointer([
106
+ ["A", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
107
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
108
+ ["K", "30000"],
109
+ ], true)).toEqual({
110
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
111
+ kind: 30000,
112
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
113
+ identifier: "list",
114
+ });
115
+ // reply
116
+ expect(getCommentAddressPointer([
117
+ ["a", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
118
+ ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f"],
119
+ ["k", "30000"],
120
+ ])).toEqual({
121
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
122
+ kind: 30000,
123
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
124
+ identifier: "list",
125
+ });
126
+ });
127
+ it("should get relay from A tag", () => {
128
+ // root
129
+ expect(getCommentAddressPointer([
130
+ ["A", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list", "wss://relay.io/"],
131
+ ["K", "30000"],
132
+ ], true)).toEqual({
133
+ kind: 30000,
134
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
135
+ identifier: "list",
136
+ relay: "wss://relay.io/",
137
+ });
138
+ // reply
139
+ expect(getCommentAddressPointer([
140
+ ["a", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list", "wss://relay.io/"],
141
+ ["k", "30000"],
142
+ ])).toEqual({
143
+ kind: 30000,
144
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
145
+ identifier: "list",
146
+ relay: "wss://relay.io/",
147
+ });
148
+ });
149
+ it("should get relay from E tag", () => {
150
+ // root
151
+ expect(getCommentAddressPointer([
152
+ ["A", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
153
+ ["E", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
154
+ ["K", "30000"],
155
+ ], true)).toEqual({
156
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
157
+ kind: 30000,
158
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
159
+ identifier: "list",
160
+ relay: "wss://relay.io/",
161
+ });
162
+ // reply
163
+ expect(getCommentAddressPointer([
164
+ ["a", "30000:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
165
+ ["e", "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f", "wss://relay.io/"],
166
+ ["k", "30000"],
167
+ ])).toEqual({
168
+ id: "86c0b95589b016ffb703bfc080d49e54106e74e2d683295119c3453e494dbe6f",
169
+ kind: 30000,
170
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
171
+ identifier: "list",
172
+ relay: "wss://relay.io/",
173
+ });
174
+ });
175
+ it("should return A tag kind over K tag", () => {
176
+ // root
177
+ expect(getCommentAddressPointer([
178
+ ["A", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
179
+ ["K", "30000"],
180
+ ], true)).toEqual({
181
+ kind: 30010,
182
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
183
+ identifier: "list",
184
+ });
185
+ // reply
186
+ expect(getCommentAddressPointer([
187
+ ["a", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"],
188
+ ["k", "30000"],
189
+ ])).toEqual({
190
+ kind: 30010,
191
+ pubkey: "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10",
192
+ identifier: "list",
193
+ });
194
+ });
195
+ it("should throw if missing K tag", () => {
196
+ // root
197
+ expect(() => getCommentAddressPointer([["A", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"]], true)).toThrow("Missing kind tag");
198
+ // reply
199
+ expect(() => getCommentAddressPointer([["a", "30010:e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10:list"]])).toThrow("Missing kind tag");
200
+ });
201
+ it("should return null if missing A tag", () => {
202
+ const tags = [
203
+ ["K", "1621"],
204
+ ["P", "e4336cd525df79fa4d3af364fd9600d4b10dce4215aa4c33ed77ea0842344b10"],
205
+ ];
206
+ expect(getCommentEventPointer(tags, true)).toBe(null);
207
+ expect(getCommentEventPointer(tags)).toBe(null);
208
+ });
209
+ });
210
+ describe("getCommentExternalPointer", () => {
211
+ it("should get kind prefix from I tag", () => {
212
+ // root
213
+ expect(getCommentExternalPointer([
214
+ ["I", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"],
215
+ ["K", "podcast:item:guid"],
216
+ ], true)).toEqual({
217
+ identifier: "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f",
218
+ kind: "podcast:item:guid",
219
+ });
220
+ // reply
221
+ expect(getCommentExternalPointer([
222
+ ["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"],
223
+ ["k", "podcast:item:guid"],
224
+ ])).toEqual({
225
+ identifier: "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f",
226
+ kind: "podcast:item:guid",
227
+ });
228
+ });
229
+ it("should throw if missing K tag", () => {
230
+ // root
231
+ expect(() => getCommentExternalPointer([["I", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"]], true)).toThrow("Missing kind tag");
232
+ // reply
233
+ expect(() => getCommentExternalPointer([["i", "podcast:item:guid:d98d189b-dc7b-45b1-8720-d4b98690f31f"]])).toThrow("Missing kind tag");
234
+ });
235
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getEmojiTag } from "../emoji.js";
3
+ import { FakeUser } from "../../__tests__/fixtures.js";
4
+ const user = new FakeUser();
5
+ describe("getEmojiTag", () => {
6
+ it("Should find emoji tag", () => {
7
+ expect(getEmojiTag(user.note("hello :custom:", { tags: [["emoji", "custom", "https://cdn.example.com/reaction1.png"]] }), "custom")).toEqual(["emoji", "custom", "https://cdn.example.com/reaction1.png"]);
8
+ });
9
+ it("Should custom leading and trailing :", () => {
10
+ expect(getEmojiTag(user.note("hello :custom:", { tags: [["emoji", "custom", "https://cdn.example.com/reaction1.png"]] }), ":custom:")).toEqual(["emoji", "custom", "https://cdn.example.com/reaction1.png"]);
11
+ });
12
+ it("Should convert to lowercase", () => {
13
+ expect(getEmojiTag(user.note("hello :custom:", { tags: [["emoji", "custom", "https://cdn.example.com/reaction1.png"]] }), "CustoM")).toEqual(["emoji", "custom", "https://cdn.example.com/reaction1.png"]);
14
+ });
15
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { EventIndexableTagsSymbol, getIndexableTags, getTagValue } from "../event.js";
3
+ const event = {
4
+ content: "",
5
+ created_at: 1732889913,
6
+ id: "2d53511f321cc82dd13eedfb597c9fe834d12d271c10d8068e9d8cfb8f58d1b4",
7
+ kind: 30000,
8
+ pubkey: "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
9
+ sig: "e6a442487ef44a8a00ec1e0a852e547991fcd5cbf19aa1a4219fa65d6f41022675e0745207649f4b16fe9a6c5c7c3693dc3e13966ffa5b2891634867c874cf22",
10
+ tags: [
11
+ ["d", "qRxLhBbTfRlxsvKSu0iUl"],
12
+ ["title", "Musicians"],
13
+ ["client", "noStrudel", "31990:266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5:1686066542546"],
14
+ ["p", "2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493"],
15
+ ["p", "28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc"],
16
+ ["p", "f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199"],
17
+ ],
18
+ };
19
+ describe("getIndexableTags", () => {
20
+ it("should return a set of indexable tags for event", () => {
21
+ expect(Array.from(getIndexableTags(event))).toEqual(expect.arrayContaining([
22
+ "p:2842e34860c59dfacd5df48ba7a65065e6760d08c35f779553d83c2c2310b493",
23
+ "p:28ca019b78b494c25a9da2d645975a8501c7e99b11302e5cbe748ee593fcb2cc",
24
+ "p:f46192b8b9be1b43fc30ea27c7cb16210aede17252b3aa9692fbb3f2ba153199",
25
+ ]));
26
+ });
27
+ it("should cache value on EventIndexableTagsSymbol", () => {
28
+ getIndexableTags(event);
29
+ expect(Reflect.has(event, EventIndexableTagsSymbol)).toBe(true);
30
+ });
31
+ });
32
+ describe("getTagValue", () => {
33
+ it("should return value of tag if present", () => {
34
+ expect(getTagValue(event, "title")).toBe("Musicians");
35
+ });
36
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getFileMetadataFromImetaTag, parseFileMetadataTags } from "../file-metadata.js";
3
+ describe("file metadata helpers", () => {
4
+ describe("parseFileMetadataTags", () => {
5
+ it("should parse a simple 1060 event", () => {
6
+ const tags = [
7
+ ["url", "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif"],
8
+ ["ox", "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae"],
9
+ ["fallback", "https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
10
+ ["x", "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5"],
11
+ ["m", "image/gif"],
12
+ ["dim", "360x306"],
13
+ ["bh", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
14
+ ["blurhash", "L38zleNL00~W^kRj0L-p0KM_^kx]"],
15
+ [
16
+ "thumb",
17
+ "https://image.nostr.build/thumb/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
18
+ ],
19
+ ["t", "gifbuddy"],
20
+ ["summary", "Khaleesi call dragons Daenerys Targaryen"],
21
+ ["alt", "a woman with blonde hair and a brooch on her shoulder"],
22
+ [
23
+ "thumb",
24
+ "https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
25
+ "5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
26
+ ],
27
+ [
28
+ "image",
29
+ "https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
30
+ "5d92423664fc15874b1d26c70a05a541ec09b5c438bf157977a87c8e64b31463",
31
+ ],
32
+ ];
33
+ expect(parseFileMetadataTags(tags)).toEqual({
34
+ url: "https://image.nostr.build/30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae.gif",
35
+ type: "image/gif",
36
+ dimensions: "360x306",
37
+ blurhash: "L38zleNL00~W^kRj0L-p0KM_^kx]",
38
+ sha256: "77fcf42b2b720babcdbe686eff67273d8a68862d74a2672db672bc48439a3ea5",
39
+ originalSha256: "30696696e57a2732d4e9f1b15ff4d4d4eaa64b759df6876863f436ff5d736eae",
40
+ thumbnail: "https://media.tenor.com/wpvrkjn192gAAAAx/daenerys-targaryen.webp",
41
+ image: "https://media.tenor.com/wpvrkjn192gAAAAe/daenerys-targaryen.png",
42
+ summary: "Khaleesi call dragons Daenerys Targaryen",
43
+ fallback: ["https://media.tenor.com/wpvrkjn192gAAAAC/daenerys-targaryen.gif"],
44
+ alt: "a woman with blonde hair and a brooch on her shoulder",
45
+ });
46
+ });
47
+ });
48
+ describe("getFileMetadataFromImetaTag", () => {
49
+ it("should parse simple imeta tag", () => {
50
+ expect(getFileMetadataFromImetaTag([
51
+ "imeta",
52
+ "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
53
+ "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
54
+ "dim 1024x1024",
55
+ "m image/jpeg",
56
+ "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
57
+ ])).toEqual({
58
+ url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
59
+ sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
60
+ dimensions: "1024x1024",
61
+ type: "image/jpeg",
62
+ blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
63
+ });
64
+ });
65
+ it("should parse thumbnail url", () => {
66
+ expect(getFileMetadataFromImetaTag([
67
+ "imeta",
68
+ "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
69
+ "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
70
+ "dim 1024x1024",
71
+ "m image/jpeg",
72
+ "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
73
+ "thumb https://exmaple.com/thumb.jpg",
74
+ ])).toEqual({
75
+ url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
76
+ sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
77
+ dimensions: "1024x1024",
78
+ type: "image/jpeg",
79
+ blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
80
+ thumbnail: "https://exmaple.com/thumb.jpg",
81
+ });
82
+ });
83
+ it("should parse multiple fallback urls", () => {
84
+ expect(getFileMetadataFromImetaTag([
85
+ "imeta",
86
+ "url https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
87
+ "x 3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
88
+ "dim 1024x1024",
89
+ "m image/jpeg",
90
+ "blurhash ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
91
+ "fallback https://exmaple.com/image2.jpg",
92
+ "fallback https://exmaple.com/image3.jpg",
93
+ ])).toEqual({
94
+ url: "https://blossom.primal.net/3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c.jpg",
95
+ sha256: "3f4dbf2797ac4e90b00bcfe2728e5c8367ed909c48230ac454cc325f1993646c",
96
+ dimensions: "1024x1024",
97
+ type: "image/jpeg",
98
+ blurhash: "ggH{Aws:RPWBRjaeay?^ozV@aeRjaej[$gt7kCofWVofkCrrofxuofa|ozbHx]s:tRofaet7ay",
99
+ fallback: ["https://exmaple.com/image2.jpg", "https://exmaple.com/image3.jpg"],
100
+ });
101
+ });
102
+ });
103
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import { describe, beforeEach, it, expect } from "vitest";
2
+ import { finalizeEvent, generateSecretKey, getPublicKey, kinds, nip04 } from "nostr-tools";
3
+ import { getHiddenTags, unlockHiddenTags } from "../hidden-tags.js";
4
+ import { unixNow } from "../time.js";
5
+ const key = generateSecretKey();
6
+ const pubkey = getPublicKey(key);
7
+ const signer = {
8
+ nip04: {
9
+ encrypt: (pubkey, plaintext) => nip04.encrypt(key, pubkey, plaintext),
10
+ decrypt: (pubkey, ciphertext) => nip04.decrypt(key, pubkey, ciphertext),
11
+ },
12
+ };
13
+ describe("Private Lists", () => {
14
+ describe("unlockHiddenTags", () => {
15
+ let list;
16
+ beforeEach(async () => {
17
+ list = finalizeEvent({
18
+ kind: kinds.Mutelist,
19
+ created_at: unixNow(),
20
+ content: await nip04.encrypt(key, pubkey, JSON.stringify([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]])),
21
+ tags: [],
22
+ }, key);
23
+ });
24
+ it("should unlock hidden tags", async () => {
25
+ await unlockHiddenTags(list, signer);
26
+ expect(getHiddenTags(list)).toEqual(expect.arrayContaining([["p", "npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr"]]));
27
+ });
28
+ });
29
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,5 @@
1
- import { getInboxes, getOutboxes } from "./mailboxes.js";
1
+ import { describe, test, expect } from "vitest";
2
+ import { getInboxes, getOutboxes } from "../mailboxes.js";
2
3
  const emptyEvent = {
3
4
  kind: 10002,
4
5
  content: "",
@@ -14,33 +15,33 @@ describe("Mailboxes", () => {
14
15
  expect(Array.from(getInboxes({
15
16
  ...emptyEvent,
16
17
  tags: [["r", "wss://inbox.com"]],
17
- }))).toIncludeAllMembers(["wss://inbox.com/"]);
18
+ }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
18
19
  });
19
20
  test("should remove bad urls", () => {
20
21
  expect(Array.from(getInboxes({
21
22
  ...emptyEvent,
22
23
  tags: [["r", "bad://inbox.com"]],
23
- }))).toBeArrayOfSize(0);
24
+ }))).toHaveLength(0);
24
25
  expect(Array.from(getInboxes({
25
26
  ...emptyEvent,
26
27
  tags: [["r", "something that is not a url"]],
27
- }))).toBeArrayOfSize(0);
28
+ }))).toHaveLength(0);
28
29
  expect(Array.from(getInboxes({
29
30
  ...emptyEvent,
30
31
  tags: [["r", "wss://inbox.com,wss://inbox.org"]],
31
- }))).toBeArrayOfSize(0);
32
+ }))).toHaveLength(0);
32
33
  });
33
34
  test("without marker", () => {
34
35
  expect(Array.from(getInboxes({
35
36
  ...emptyEvent,
36
37
  tags: [["r", "wss://inbox.com/"]],
37
- }))).toIncludeAllMembers(["wss://inbox.com/"]);
38
+ }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
38
39
  });
39
40
  test("with marker", () => {
40
41
  expect(Array.from(getInboxes({
41
42
  ...emptyEvent,
42
43
  tags: [["r", "wss://inbox.com/", "read"]],
43
- }))).toIncludeAllMembers(["wss://inbox.com/"]);
44
+ }))).toEqual(expect.arrayContaining(["wss://inbox.com/"]));
44
45
  });
45
46
  });
46
47
  describe("getOutboxes", () => {
@@ -48,33 +49,33 @@ describe("Mailboxes", () => {
48
49
  expect(Array.from(getOutboxes({
49
50
  ...emptyEvent,
50
51
  tags: [["r", "wss://outbox.com"]],
51
- }))).toIncludeAllMembers(["wss://outbox.com/"]);
52
+ }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
52
53
  });
53
54
  test("should remove bad urls", () => {
54
55
  expect(Array.from(getOutboxes({
55
56
  ...emptyEvent,
56
57
  tags: [["r", "bad://inbox.com"]],
57
- }))).toBeArrayOfSize(0);
58
+ }))).toHaveLength(0);
58
59
  expect(Array.from(getOutboxes({
59
60
  ...emptyEvent,
60
61
  tags: [["r", "something that is not a url"]],
61
- }))).toBeArrayOfSize(0);
62
+ }))).toHaveLength(0);
62
63
  expect(Array.from(getOutboxes({
63
64
  ...emptyEvent,
64
65
  tags: [["r", "wss://outbox.com,wss://inbox.org"]],
65
- }))).toBeArrayOfSize(0);
66
+ }))).toHaveLength(0);
66
67
  });
67
68
  test("without marker", () => {
68
69
  expect(Array.from(getOutboxes({
69
70
  ...emptyEvent,
70
71
  tags: [["r", "wss://outbox.com/"]],
71
- }))).toIncludeAllMembers(["wss://outbox.com/"]);
72
+ }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
72
73
  });
73
74
  test("with marker", () => {
74
75
  expect(Array.from(getOutboxes({
75
76
  ...emptyEvent,
76
77
  tags: [["r", "wss://outbox.com/", "write"]],
77
- }))).toIncludeAllMembers(["wss://outbox.com/"]);
78
+ }))).toEqual(expect.arrayContaining(["wss://outbox.com/"]));
78
79
  });
79
80
  });
80
81
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { isSafeRelayURL } from "../relays.js";
3
+ describe("isSafeRelayURL", () => {
4
+ it("should correctly filter URLs", () => {
5
+ // safe URLs
6
+ expect(isSafeRelayURL("wss://relay.damus.io/")).toBe(true);
7
+ expect(isSafeRelayURL("wss://nostrue.com")).toBe(true);
8
+ expect(isSafeRelayURL("ws://192.168.0.194:8080")).toBe(true);
9
+ expect(isSafeRelayURL("ws://localhost:4869/ws")).toBe(true);
10
+ expect(isSafeRelayURL("ws://localhost/testing")).toBe(true);
11
+ expect(isSafeRelayURL("ws://437fqnfqtcaquzvs5sd43ugznw7dsoatvtskoowgnpn6q5vqkljcrsyd.onion")).toBe(true);
12
+ expect(isSafeRelayURL("ws://hypr1fk4trjnhjf62r6hhkpettmvxhxx2uvkkg4u4ea44va2fvxvfkl4s82m6dy.hyper")).toBe(true);
13
+ // bad URLs
14
+ expect(isSafeRelayURL("")).toBe(false);
15
+ expect(isSafeRelayURL("bad")).toBe(false);
16
+ expect(isSafeRelayURL("bad wss://nostr.wine")).toBe(false);
17
+ expect(isSafeRelayURL("http://nostr.wine")).toBe(false);
18
+ expect(isSafeRelayURL("http://cache-relay.com")).toBe(false);
19
+ expect(isSafeRelayURL("wss://nostr.wine,wss://relayable.com")).toBe(false);
20
+ });
21
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { isATag, isNameValueTag, processTags } from "../tags.js";
3
+ import { getAddressPointerFromATag } from "../pointers.js";
4
+ describe("isNameValueTag", () => {
5
+ it("should return true if tag has at least two indexes", () => {
6
+ expect(isNameValueTag(["a", "30000:pubkey:list"])).toBe(true);
7
+ expect(isNameValueTag(["title", "article", "other-value"])).toBe(true);
8
+ });
9
+ it("should ignore tags without values", () => {
10
+ expect(isNameValueTag(["a"])).toBe(false);
11
+ expect(isNameValueTag(["title"])).toBe(false);
12
+ });
13
+ });
14
+ describe("processTags", () => {
15
+ it("should filter out errors", () => {
16
+ expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], getAddressPointerFromATag)).toEqual([{ identifier: "list", kind: 30000, pubkey: "pubkey" }]);
17
+ });
18
+ it("should filter out undefined", () => {
19
+ expect(processTags([["a", "bad coordinate"], ["e"], ["a", "30000:pubkey:list"]], (tag) => isATag(tag) ? tag : undefined)).toEqual([
20
+ ["a", "bad coordinate"],
21
+ ["a", "30000:pubkey:list"],
22
+ ]);
23
+ });
24
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { interpretThreadTags } from "../threading.js";
3
+ describe("threading helpers", () => {
4
+ describe("interpretThreadTags", () => {
5
+ it("should handle legacy tags", () => {
6
+ expect(interpretThreadTags([
7
+ ["e", "root-id"],
8
+ ["e", "reply-id"],
9
+ ])).toEqual({ root: { a: undefined, e: ["e", "root-id"] }, reply: { a: undefined, e: ["e", "reply-id"] } });
10
+ });
11
+ it("should handle nip-10 tags", () => {
12
+ expect(interpretThreadTags([
13
+ ["e", "root-id", "relay", "root"],
14
+ ["e", "reply-id", "relay", "reply"],
15
+ ])).toEqual({
16
+ root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
17
+ reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
18
+ });
19
+ });
20
+ it("should ignore mention nip-10 tags", () => {
21
+ expect(interpretThreadTags([
22
+ ["e", "root-id", "relay", "root"],
23
+ ["e", "mention-id", "relay", "mention"],
24
+ ["e", "reply-id", "relay", "reply"],
25
+ ])).toEqual({
26
+ root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
27
+ reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
28
+ });
29
+ });
30
+ it("should handle single nip-10 tags", () => {
31
+ expect(interpretThreadTags([["e", "root-id", "relay", "root"]])).toEqual({
32
+ root: { a: undefined, e: ["e", "root-id", "relay", "root"] },
33
+ reply: { a: undefined, e: ["e", "root-id", "relay", "root"] },
34
+ });
35
+ expect(interpretThreadTags([["e", "reply-id", "relay", "reply"]])).toEqual({
36
+ root: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
37
+ reply: { a: undefined, e: ["e", "reply-id", "relay", "reply"] },
38
+ });
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,9 @@
1
+ export declare const BLOSSOM_SERVER_LIST_KIND = 10063;
2
+ /** Check if two servers are the same */
3
+ export declare function areBlossomServersEqual(a: string | URL, b: string | URL): boolean;
4
+ /** Checks if a string is a sha256 hash */
5
+ export declare function isSha256(str: string): boolean;
6
+ /** Returns an ordered array of servers found in a server list event (10063) */
7
+ export declare function getBlossomServersFromList(event: {
8
+ tags: string[][];
9
+ } | string[][]): URL[];