applesauce-actions 1.1.0 → 2.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/dist/action-hub.d.ts +6 -4
- package/dist/action-hub.js +4 -2
- package/dist/actions/bookmarks.d.ts +1 -1
- package/dist/actions/bookmarks.js +1 -1
- package/dist/actions/contacts.js +1 -1
- package/dist/actions/follow-sets.d.ts +0 -3
- package/dist/actions/follow-sets.js +0 -3
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/legacy-messages.d.ts +7 -0
- package/dist/actions/legacy-messages.js +20 -0
- package/dist/actions/pins.js +1 -1
- package/dist/actions/wrapped-messages.d.ts +20 -0
- package/dist/actions/wrapped-messages.js +37 -0
- package/package.json +6 -6
- package/dist/__tests__/action-hub.test.d.ts +0 -1
- package/dist/__tests__/action-hub.test.js +0 -67
- package/dist/__tests__/fake-user.d.ts +0 -10
- package/dist/__tests__/fake-user.js +0 -32
- package/dist/actions/__tests__/blossom.test.d.ts +0 -1
- package/dist/actions/__tests__/blossom.test.js +0 -93
- package/dist/actions/__tests__/bookmarks.test.d.ts +0 -1
- package/dist/actions/__tests__/bookmarks.test.js +0 -24
- package/dist/actions/__tests__/contacts.test.d.ts +0 -1
- package/dist/actions/__tests__/contacts.test.js +0 -53
- package/dist/actions/__tests__/follow-sets.test.d.ts +0 -1
- package/dist/actions/__tests__/follow-sets.test.js +0 -80
- package/dist/actions/__tests__/mute.test.d.ts +0 -1
- package/dist/actions/__tests__/mute.test.js +0 -67
- package/dist/helpers/observable.d.ts +0 -3
- package/dist/helpers/observable.js +0 -20
package/dist/action-hub.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Observable } from "rxjs";
|
|
2
2
|
import { NostrEvent } from "nostr-tools";
|
|
3
|
-
import { ISyncEventStore } from "applesauce-core/event-store";
|
|
4
3
|
import { EventFactory } from "applesauce-factory";
|
|
4
|
+
import { IEventStoreActions, IEventStoreRead } from "applesauce-core";
|
|
5
5
|
/**
|
|
6
6
|
* A callback used to tell the upstream app to publish an event
|
|
7
7
|
* @param label a label describing what
|
|
@@ -10,7 +10,7 @@ export type PublishMethod = (event: NostrEvent) => void | Promise<void>;
|
|
|
10
10
|
/** The context that is passed to actions for them to use to preform actions */
|
|
11
11
|
export type ActionContext = {
|
|
12
12
|
/** The event store to load events from */
|
|
13
|
-
events:
|
|
13
|
+
events: IEventStoreRead;
|
|
14
14
|
/** The pubkey of the signer in the event factory */
|
|
15
15
|
self: string;
|
|
16
16
|
/** The event factory used to build and modify events */
|
|
@@ -21,10 +21,12 @@ export type Action = (ctx: ActionContext) => Observable<NostrEvent> | AsyncGener
|
|
|
21
21
|
export type ActionConstructor<Args extends Array<any>> = (...args: Args) => Action;
|
|
22
22
|
/** The main class that runs actions */
|
|
23
23
|
export declare class ActionHub {
|
|
24
|
-
events:
|
|
24
|
+
events: IEventStoreRead & IEventStoreActions;
|
|
25
25
|
factory: EventFactory;
|
|
26
26
|
publish?: PublishMethod | undefined;
|
|
27
|
-
|
|
27
|
+
/** Whether to save all events created by actions to the event store */
|
|
28
|
+
saveToStore: boolean;
|
|
29
|
+
constructor(events: IEventStoreRead & IEventStoreActions, factory: EventFactory, publish?: PublishMethod | undefined);
|
|
28
30
|
protected context: ActionContext | undefined;
|
|
29
31
|
protected getContext(): Promise<ActionContext>;
|
|
30
32
|
/** Runs an action in a ActionContext and converts the result to an Observable */
|
package/dist/action-hub.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { from, isObservable, lastValueFrom, switchMap, toArray } from "rxjs";
|
|
1
|
+
import { from, isObservable, lastValueFrom, switchMap, tap, toArray } from "rxjs";
|
|
2
2
|
/** The main class that runs actions */
|
|
3
3
|
export class ActionHub {
|
|
4
4
|
events;
|
|
5
5
|
factory;
|
|
6
6
|
publish;
|
|
7
|
+
/** Whether to save all events created by actions to the event store */
|
|
8
|
+
saveToStore = true;
|
|
7
9
|
constructor(events, factory, publish) {
|
|
8
10
|
this.events = events;
|
|
9
11
|
this.factory = factory;
|
|
@@ -44,6 +46,6 @@ export class ActionHub {
|
|
|
44
46
|
return from(this.getContext()).pipe(switchMap((ctx) => {
|
|
45
47
|
const action = Action(...args);
|
|
46
48
|
return ActionHub.runAction(ctx, action);
|
|
47
|
-
}));
|
|
49
|
+
}), tap((event) => this.saveToStore && this.events.add(event)));
|
|
48
50
|
}
|
|
49
51
|
}
|
|
@@ -13,7 +13,7 @@ export declare function BookmarkEvent(event: NostrEvent, identifier?: string, hi
|
|
|
13
13
|
* @param identifier the "d" tag of the bookmark set
|
|
14
14
|
* @param hidden set to true to remove from hidden bookmarks
|
|
15
15
|
*/
|
|
16
|
-
export declare function UnbookmarkEvent(event: NostrEvent, identifier
|
|
16
|
+
export declare function UnbookmarkEvent(event: NostrEvent, identifier?: string, hidden?: boolean): Action;
|
|
17
17
|
/** An action that creates a new bookmark list for a user */
|
|
18
18
|
export declare function CreateBookmarkList(bookmarks?: NostrEvent[]): Action;
|
|
19
19
|
/** An action that creates a new bookmark set for a user */
|
|
@@ -52,7 +52,7 @@ export function CreateBookmarkSet(title, description, additional) {
|
|
|
52
52
|
const existing = getBookmarkEvent(events, self);
|
|
53
53
|
if (existing)
|
|
54
54
|
throw new Error("Bookmark list already exists");
|
|
55
|
-
const draft = await factory.
|
|
55
|
+
const draft = await factory.build({ kind: kinds.BookmarkList }, setListTitle(title), setListDescription(description), additional.image ? setListImage(additional.image) : undefined, additional.public ? modifyPublicTags(...additional.public.map(addEventBookmarkTag)) : undefined, additional.hidden ? modifyHiddenTags(...additional.hidden.map(addEventBookmarkTag)) : undefined);
|
|
56
56
|
yield await factory.sign(draft);
|
|
57
57
|
};
|
|
58
58
|
}
|
package/dist/actions/contacts.js
CHANGED
|
@@ -29,7 +29,7 @@ export function NewContacts(pubkeys) {
|
|
|
29
29
|
const contacts = events.getReplaceable(kinds.Contacts, self);
|
|
30
30
|
if (contacts)
|
|
31
31
|
throw new Error("Contact list already exists");
|
|
32
|
-
const draft = await factory.
|
|
32
|
+
const draft = await factory.build({ kind: kinds.Contacts, tags: pubkeys?.map((p) => ["p", p]) });
|
|
33
33
|
yield await factory.sign(draft);
|
|
34
34
|
};
|
|
35
35
|
}
|
|
@@ -3,9 +3,6 @@ import { ProfilePointer } from "nostr-tools/nip19";
|
|
|
3
3
|
import { Action } from "../action-hub.js";
|
|
4
4
|
/**
|
|
5
5
|
* An action that creates a new follow set
|
|
6
|
-
* @param identifier the "d" tag of the follow set
|
|
7
|
-
* @param pubkeys the pubkeys to add to the follow set
|
|
8
|
-
* @param hidden set to true to create a hidden follow set
|
|
9
6
|
* @throws if a follow set already exists
|
|
10
7
|
*/
|
|
11
8
|
export declare function CreateFollowSet(title: string, options?: {
|
|
@@ -11,9 +11,6 @@ function getFollowSetEvent(events, self, identifier) {
|
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* An action that creates a new follow set
|
|
14
|
-
* @param identifier the "d" tag of the follow set
|
|
15
|
-
* @param pubkeys the pubkeys to add to the follow set
|
|
16
|
-
* @param hidden set to true to create a hidden follow set
|
|
17
14
|
* @throws if a follow set already exists
|
|
18
15
|
*/
|
|
19
16
|
export function CreateFollowSet(title, options) {
|
package/dist/actions/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./contacts.js";
|
|
|
5
5
|
export * from "./dm-relays.js";
|
|
6
6
|
export * from "./favorite-relays.js";
|
|
7
7
|
export * from "./follow-sets.js";
|
|
8
|
+
export * from "./legacy-messages.js";
|
|
8
9
|
export * from "./lists.js";
|
|
9
10
|
export * from "./mailboxes.js";
|
|
10
11
|
export * from "./mute.js";
|
|
@@ -12,3 +13,4 @@ export * from "./pins.js";
|
|
|
12
13
|
export * from "./profile.js";
|
|
13
14
|
export * from "./relay-sets.js";
|
|
14
15
|
export * from "./search-relays.js";
|
|
16
|
+
export * from "./wrapped-messages.js";
|
package/dist/actions/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./contacts.js";
|
|
|
5
5
|
export * from "./dm-relays.js";
|
|
6
6
|
export * from "./favorite-relays.js";
|
|
7
7
|
export * from "./follow-sets.js";
|
|
8
|
+
export * from "./legacy-messages.js";
|
|
8
9
|
export * from "./lists.js";
|
|
9
10
|
export * from "./mailboxes.js";
|
|
10
11
|
export * from "./mute.js";
|
|
@@ -12,3 +13,4 @@ export * from "./pins.js";
|
|
|
12
13
|
export * from "./profile.js";
|
|
13
14
|
export * from "./relay-sets.js";
|
|
14
15
|
export * from "./search-relays.js";
|
|
16
|
+
export * from "./wrapped-messages.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LegacyMessageBlueprintOptions } from "applesauce-factory/blueprints";
|
|
2
|
+
import { NostrEvent } from "nostr-tools";
|
|
3
|
+
import { Action } from "../action-hub.js";
|
|
4
|
+
/** Sends a legacy NIP-04 message to a recipient */
|
|
5
|
+
export declare function SendLegacyMessage(recipient: string, message: string, opts?: LegacyMessageBlueprintOptions): Action;
|
|
6
|
+
/** Send a reply to a legacy message */
|
|
7
|
+
export declare function ReplyToLegacyMessage(parent: NostrEvent, message: string, opts?: LegacyMessageBlueprintOptions): Action;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LegacyMessageBlueprint, LegacyMessageReplyBlueprint, } from "applesauce-factory/blueprints";
|
|
2
|
+
import { kinds } from "nostr-tools";
|
|
3
|
+
/** Sends a legacy NIP-04 message to a recipient */
|
|
4
|
+
export function SendLegacyMessage(recipient, message, opts) {
|
|
5
|
+
return async function* ({ factory }) {
|
|
6
|
+
const draft = await factory.create(LegacyMessageBlueprint, recipient, message, opts);
|
|
7
|
+
// Return the signed message
|
|
8
|
+
yield await factory.sign(draft);
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** Send a reply to a legacy message */
|
|
12
|
+
export function ReplyToLegacyMessage(parent, message, opts) {
|
|
13
|
+
return async function* ({ factory }) {
|
|
14
|
+
if (parent.kind !== kinds.EncryptedDirectMessage)
|
|
15
|
+
throw new Error("Legacy messages can only reply to other legacy messages");
|
|
16
|
+
const draft = await factory.create(LegacyMessageReplyBlueprint, parent, message, opts);
|
|
17
|
+
// Return the signed message
|
|
18
|
+
yield await factory.sign(draft);
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/actions/pins.js
CHANGED
|
@@ -27,7 +27,7 @@ export function CreatePinList(pins = []) {
|
|
|
27
27
|
const existing = events.getReplaceable(kinds.Pinlist, self);
|
|
28
28
|
if (existing)
|
|
29
29
|
throw new Error("Pin list already exists");
|
|
30
|
-
const draft = await factory.
|
|
30
|
+
const draft = await factory.build({ kind: kinds.Pinlist }, modifyPublicTags(...pins.map((event) => addEventTag(event.id))));
|
|
31
31
|
yield await factory.sign(draft);
|
|
32
32
|
};
|
|
33
33
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Rumor } from "applesauce-core/helpers";
|
|
2
|
+
import { WrappedMessageBlueprintOptions } from "applesauce-factory/blueprints";
|
|
3
|
+
import { GiftWrapOptions } from "applesauce-factory/operations/event";
|
|
4
|
+
import { Action } from "../action-hub.js";
|
|
5
|
+
/**
|
|
6
|
+
* Sends a NIP-17 wrapped message to a conversation
|
|
7
|
+
* @param participants - A conversation identifier, user pubkey, or a list of participant pubkeys
|
|
8
|
+
* @param message - The message to send
|
|
9
|
+
* @param opts - Options for the wrapped message and gift wrap
|
|
10
|
+
* @returns Signed gift wrapped messages to send
|
|
11
|
+
*/
|
|
12
|
+
export declare function SendWrappedMessage(participants: string | string[], message: string, opts?: WrappedMessageBlueprintOptions & GiftWrapOptions): Action;
|
|
13
|
+
/**
|
|
14
|
+
* Sends a NIP-17 reply to a wrapped message
|
|
15
|
+
* @param parent - The parent wrapped message
|
|
16
|
+
* @param message - The message to send
|
|
17
|
+
* @param opts - Options for the wrapped message and gift wrap
|
|
18
|
+
* @returns Signed gift wrapped messages to send
|
|
19
|
+
*/
|
|
20
|
+
export declare function ReplyToWrappedMessage(parent: Rumor, message: string, opts?: WrappedMessageBlueprintOptions & GiftWrapOptions): Action;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getConversationParticipants } from "applesauce-core/helpers";
|
|
2
|
+
import { GiftWrapBlueprint, WrappedMessageBlueprint, WrappedMessageReplyBlueprint, } from "applesauce-factory/blueprints";
|
|
3
|
+
/**
|
|
4
|
+
* Sends a NIP-17 wrapped message to a conversation
|
|
5
|
+
* @param participants - A conversation identifier, user pubkey, or a list of participant pubkeys
|
|
6
|
+
* @param message - The message to send
|
|
7
|
+
* @param opts - Options for the wrapped message and gift wrap
|
|
8
|
+
* @returns Signed gift wrapped messages to send
|
|
9
|
+
*/
|
|
10
|
+
export function SendWrappedMessage(participants, message, opts) {
|
|
11
|
+
return async function* ({ factory }) {
|
|
12
|
+
const rumor = await factory.create(WrappedMessageBlueprint, participants, message, opts);
|
|
13
|
+
// Get the pubkeys to send this message to (will include the sender)
|
|
14
|
+
const pubkeys = getConversationParticipants(rumor);
|
|
15
|
+
for (const pubkey of pubkeys) {
|
|
16
|
+
yield await factory.create(GiftWrapBlueprint, pubkey, rumor, opts);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sends a NIP-17 reply to a wrapped message
|
|
22
|
+
* @param parent - The parent wrapped message
|
|
23
|
+
* @param message - The message to send
|
|
24
|
+
* @param opts - Options for the wrapped message and gift wrap
|
|
25
|
+
* @returns Signed gift wrapped messages to send
|
|
26
|
+
*/
|
|
27
|
+
export function ReplyToWrappedMessage(parent, message, opts) {
|
|
28
|
+
return async function* ({ factory }) {
|
|
29
|
+
// Create the reply message
|
|
30
|
+
const rumor = await factory.create(WrappedMessageReplyBlueprint, parent, message, opts);
|
|
31
|
+
// Get the pubkeys to send this message to (will include the sender)
|
|
32
|
+
const pubkeys = getConversationParticipants(parent);
|
|
33
|
+
for (const pubkey of pubkeys) {
|
|
34
|
+
yield await factory.create(GiftWrapBlueprint, pubkey, rumor, opts);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-actions",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A package for performing common nostr actions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,18 +32,18 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"applesauce-core": "^1.0
|
|
36
|
-
"applesauce-factory": "^
|
|
37
|
-
"nostr-tools": "^2.
|
|
35
|
+
"applesauce-core": "^2.1.0",
|
|
36
|
+
"applesauce-factory": "^2.0.0",
|
|
37
|
+
"nostr-tools": "^2.13",
|
|
38
38
|
"rxjs": "^7.8.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@hirez_io/observer-spy": "^2.2.0",
|
|
42
42
|
"@types/debug": "^4.1.12",
|
|
43
|
-
"applesauce-signers": "^
|
|
43
|
+
"applesauce-signers": "^2.0.0",
|
|
44
44
|
"nanoid": "^5.1.5",
|
|
45
45
|
"typescript": "^5.8.3",
|
|
46
|
-
"vitest": "^3.
|
|
46
|
+
"vitest": "^3.2.3"
|
|
47
47
|
},
|
|
48
48
|
"funding": {
|
|
49
49
|
"type": "lightning",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { from, Subject } from "rxjs";
|
|
2
|
-
import { describe, expect, it, vi } from "vitest";
|
|
3
|
-
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
4
|
-
import { EventFactory } from "applesauce-factory";
|
|
5
|
-
import { EventStore } from "applesauce-core";
|
|
6
|
-
import { FakeUser } from "./fake-user.js";
|
|
7
|
-
import { ActionHub } from "../action-hub.js";
|
|
8
|
-
import { CreateProfile } from "../actions/profile.js";
|
|
9
|
-
const user = new FakeUser();
|
|
10
|
-
const events = new EventStore();
|
|
11
|
-
const factory = new EventFactory({ signer: user });
|
|
12
|
-
describe("runAction", () => {
|
|
13
|
-
it("should handle action that return observables", async () => {
|
|
14
|
-
const e = [user.note(), user.profile({ name: "testing" })];
|
|
15
|
-
const action = () => from(e);
|
|
16
|
-
const spy = subscribeSpyTo(ActionHub.runAction({ events, factory, self: await user.getPublicKey() }, action));
|
|
17
|
-
await spy.onComplete();
|
|
18
|
-
expect(spy.getValues()).toEqual(e);
|
|
19
|
-
});
|
|
20
|
-
it("should handle action that return AsyncIterable", async () => {
|
|
21
|
-
const e = [user.note(), user.profile({ name: "testing" })];
|
|
22
|
-
async function* action() {
|
|
23
|
-
for (const event of e)
|
|
24
|
-
yield event;
|
|
25
|
-
}
|
|
26
|
-
const spy = subscribeSpyTo(ActionHub.runAction({ events, factory, self: await user.getPublicKey() }, action));
|
|
27
|
-
await spy.onComplete();
|
|
28
|
-
expect(spy.getValues()).toEqual(e);
|
|
29
|
-
});
|
|
30
|
-
it("should handle action that return Iterable", async () => {
|
|
31
|
-
const e = [user.note(), user.profile({ name: "testing" })];
|
|
32
|
-
function* action() {
|
|
33
|
-
for (const event of e)
|
|
34
|
-
yield event;
|
|
35
|
-
}
|
|
36
|
-
const spy = subscribeSpyTo(ActionHub.runAction({ events, factory, self: await user.getPublicKey() }, action));
|
|
37
|
-
await spy.onComplete();
|
|
38
|
-
expect(spy.getValues()).toEqual(e);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
describe("run", () => {
|
|
42
|
-
it("should throw if publish is not set", async () => {
|
|
43
|
-
const hub = new ActionHub(events, factory);
|
|
44
|
-
await expect(async () => hub.run(CreateProfile, { name: "fiatjaf" })).rejects.toThrow();
|
|
45
|
-
});
|
|
46
|
-
it("should call publish with all events", async () => {
|
|
47
|
-
const publish = vi.fn().mockResolvedValue(undefined);
|
|
48
|
-
const hub = new ActionHub(events, factory, publish);
|
|
49
|
-
await hub.run(CreateProfile, { name: "fiatjaf" });
|
|
50
|
-
expect(publish).toHaveBeenCalledWith(expect.objectContaining({ content: JSON.stringify({ name: "fiatjaf" }) }));
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
describe("exec", () => {
|
|
54
|
-
it("should support forEach to stream to publish", async () => {
|
|
55
|
-
const publish = vi.fn().mockResolvedValue(undefined);
|
|
56
|
-
const hub = new ActionHub(events, factory);
|
|
57
|
-
await hub.exec(CreateProfile, { name: "fiatjaf" }).forEach(publish);
|
|
58
|
-
expect(publish).toHaveBeenCalledWith(expect.objectContaining({ content: JSON.stringify({ name: "fiatjaf" }) }));
|
|
59
|
-
});
|
|
60
|
-
it("should support streaming to a publish subject", async () => {
|
|
61
|
-
const publish = new Subject();
|
|
62
|
-
const spy = subscribeSpyTo(publish);
|
|
63
|
-
const hub = new ActionHub(events, factory);
|
|
64
|
-
await hub.exec(CreateProfile, { name: "fiatjaf" }).forEach((v) => publish.next(v));
|
|
65
|
-
expect(spy.getValues()).toEqual([expect.objectContaining({ content: JSON.stringify({ name: "fiatjaf" }) })]);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { SimpleSigner } from "applesauce-signers/signers/simple-signer";
|
|
2
|
-
import type { NostrEvent } from "nostr-tools";
|
|
3
|
-
export declare class FakeUser extends SimpleSigner {
|
|
4
|
-
pubkey: string;
|
|
5
|
-
event(data?: Partial<NostrEvent>): NostrEvent;
|
|
6
|
-
note(content?: string, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
|
|
7
|
-
profile(profile: any, extra?: Partial<NostrEvent>): import("nostr-tools").Event;
|
|
8
|
-
contacts(pubkeys?: string[]): import("nostr-tools").Event;
|
|
9
|
-
list(tags?: string[][], extra?: Partial<NostrEvent>): import("nostr-tools").Event;
|
|
10
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { unixNow } from "applesauce-core/helpers";
|
|
2
|
-
import { SimpleSigner } from "applesauce-signers/signers/simple-signer";
|
|
3
|
-
import { nanoid } from "nanoid";
|
|
4
|
-
import { finalizeEvent, getPublicKey, kinds } from "nostr-tools";
|
|
5
|
-
export class FakeUser extends SimpleSigner {
|
|
6
|
-
pubkey = getPublicKey(this.key);
|
|
7
|
-
event(data) {
|
|
8
|
-
return finalizeEvent({
|
|
9
|
-
kind: data?.kind ?? kinds.ShortTextNote,
|
|
10
|
-
content: data?.content || "",
|
|
11
|
-
created_at: data?.created_at ?? unixNow(),
|
|
12
|
-
tags: data?.tags || [],
|
|
13
|
-
}, this.key);
|
|
14
|
-
}
|
|
15
|
-
note(content = "Hello World", extra) {
|
|
16
|
-
return this.event({ kind: kinds.ShortTextNote, content, ...extra });
|
|
17
|
-
}
|
|
18
|
-
profile(profile, extra) {
|
|
19
|
-
return this.event({ kind: kinds.Metadata, content: JSON.stringify({ ...profile }), ...extra });
|
|
20
|
-
}
|
|
21
|
-
contacts(pubkeys = []) {
|
|
22
|
-
return this.event({ kind: kinds.Contacts, tags: pubkeys.map((p) => ["p", p]) });
|
|
23
|
-
}
|
|
24
|
-
list(tags = [], extra) {
|
|
25
|
-
return this.event({
|
|
26
|
-
kind: kinds.Bookmarksets,
|
|
27
|
-
content: "",
|
|
28
|
-
tags: [["d", nanoid()], ...tags],
|
|
29
|
-
...extra,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { EventStore } from "applesauce-core";
|
|
2
|
-
import { BLOSSOM_SERVER_LIST_KIND } from "applesauce-core/helpers/blossom";
|
|
3
|
-
import { EventFactory } from "applesauce-factory";
|
|
4
|
-
import { firstValueFrom, lastValueFrom } from "rxjs";
|
|
5
|
-
import { toArray } from "rxjs/operators";
|
|
6
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
7
|
-
import { FakeUser } from "../../__tests__/fake-user.js";
|
|
8
|
-
import { ActionHub } from "../../action-hub.js";
|
|
9
|
-
import { AddBlossomServer, NewBlossomServers, RemoveBlossomServer, SetDefaultBlossomServer } from "../blossom.js";
|
|
10
|
-
const user = new FakeUser();
|
|
11
|
-
let events;
|
|
12
|
-
let factory;
|
|
13
|
-
let hub;
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
events = new EventStore();
|
|
16
|
-
factory = new EventFactory({ signer: user });
|
|
17
|
-
hub = new ActionHub(events, factory);
|
|
18
|
-
});
|
|
19
|
-
describe("NewBlossomServers", () => {
|
|
20
|
-
it("should publish a kind 10063 blossom server list", async () => {
|
|
21
|
-
const result = await lastValueFrom(hub.exec(NewBlossomServers, ["https://cdn.example.com/"]).pipe(toArray()));
|
|
22
|
-
expect(result[0]).toMatchObject({ kind: BLOSSOM_SERVER_LIST_KIND, tags: [["server", "https://cdn.example.com/"]] });
|
|
23
|
-
});
|
|
24
|
-
it("should throw if a blossom servers event already exists", async () => {
|
|
25
|
-
// Create the initial event
|
|
26
|
-
await hub.exec(NewBlossomServers, ["https://cdn.example.com/"]).forEach((e) => events.add(e));
|
|
27
|
-
// Attempt to create another one
|
|
28
|
-
await expect(lastValueFrom(hub.exec(NewBlossomServers, ["https://other.example.com/"]).pipe(toArray()))).rejects.toThrow("Blossom servers event already exists");
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
describe("AddBlossomServer", () => {
|
|
32
|
-
beforeEach(async () => {
|
|
33
|
-
// Create an initial empty server list
|
|
34
|
-
await hub.exec(NewBlossomServers, ["https://cdn.example.com/"]).forEach((e) => events.add(e));
|
|
35
|
-
});
|
|
36
|
-
it("should add a single server to the list", async () => {
|
|
37
|
-
const result = await firstValueFrom(hub.exec(AddBlossomServer, "https://other.example.com/"));
|
|
38
|
-
expect(result).toMatchObject({
|
|
39
|
-
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
40
|
-
tags: expect.arrayContaining([["server", expect.stringContaining("other.example.com")]]),
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
it("should add multiple servers to the list", async () => {
|
|
44
|
-
const result = await firstValueFrom(hub.exec(AddBlossomServer, ["https://other.example.com/", "https://other2.example.com/"]));
|
|
45
|
-
expect(result).toMatchObject({
|
|
46
|
-
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
47
|
-
tags: expect.arrayContaining([
|
|
48
|
-
["server", expect.stringContaining("other.example.com")],
|
|
49
|
-
["server", expect.stringContaining("other2.example.com")],
|
|
50
|
-
]),
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe("RemoveBlossomServer", () => {
|
|
55
|
-
beforeEach(async () => {
|
|
56
|
-
// Create an initial server list with two servers
|
|
57
|
-
await hub
|
|
58
|
-
.exec(NewBlossomServers, ["https://cdn.example.com/", "https://other.example.com/"])
|
|
59
|
-
.forEach((e) => events.add(e));
|
|
60
|
-
});
|
|
61
|
-
it("should remove a server from the list", async () => {
|
|
62
|
-
const result = await firstValueFrom(hub.exec(RemoveBlossomServer, "https://cdn.example.com/"));
|
|
63
|
-
expect(result).toMatchObject({
|
|
64
|
-
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
65
|
-
tags: expect.arrayContaining([["server", expect.stringContaining("other.example.com")]]),
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
it("should remove multiple servers from the list", async () => {
|
|
69
|
-
const result = await firstValueFrom(hub.exec(RemoveBlossomServer, ["https://cdn.example.com/", "https://other.example.com/"]));
|
|
70
|
-
expect(result).toMatchObject({
|
|
71
|
-
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
72
|
-
tags: [],
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
describe("SetDefaultBlossomServer", () => {
|
|
77
|
-
beforeEach(async () => {
|
|
78
|
-
// Create an initial server list with two servers
|
|
79
|
-
await hub
|
|
80
|
-
.exec(NewBlossomServers, ["https://cdn.example.com/", "https://other.example.com/"])
|
|
81
|
-
.forEach((e) => events.add(e));
|
|
82
|
-
});
|
|
83
|
-
it("should move the specified server to the top of the list", async () => {
|
|
84
|
-
const result = await firstValueFrom(hub.exec(SetDefaultBlossomServer, "https://other.example.com/"));
|
|
85
|
-
expect(result).toMatchObject({
|
|
86
|
-
kind: BLOSSOM_SERVER_LIST_KIND,
|
|
87
|
-
tags: expect.arrayContaining([
|
|
88
|
-
["server", expect.stringContaining("other.example.com")],
|
|
89
|
-
["server", expect.stringContaining("cdn.example.com")],
|
|
90
|
-
]),
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it, vitest } from "vitest";
|
|
2
|
-
import { EventStore } from "applesauce-core";
|
|
3
|
-
import { EventFactory } from "applesauce-factory";
|
|
4
|
-
import { kinds } from "nostr-tools";
|
|
5
|
-
import { FakeUser } from "../../__tests__/fake-user.js";
|
|
6
|
-
import { ActionHub } from "../../action-hub.js";
|
|
7
|
-
import { CreateBookmarkList } from "../bookmarks.js";
|
|
8
|
-
const user = new FakeUser();
|
|
9
|
-
let events;
|
|
10
|
-
let factory;
|
|
11
|
-
let publish;
|
|
12
|
-
let hub;
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
events = new EventStore();
|
|
15
|
-
factory = new EventFactory({ signer: user });
|
|
16
|
-
publish = vitest.fn().mockResolvedValue(undefined);
|
|
17
|
-
hub = new ActionHub(events, factory, publish);
|
|
18
|
-
});
|
|
19
|
-
describe("CreateBookmarkList", () => {
|
|
20
|
-
it("should publish a kind 10003 bookmark list", async () => {
|
|
21
|
-
await hub.run(CreateBookmarkList);
|
|
22
|
-
expect(publish).toBeCalledWith(expect.objectContaining({ kind: kinds.BookmarkList }));
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vitest } from "vitest";
|
|
2
|
-
import { FakeUser } from "../../__tests__/fake-user.js";
|
|
3
|
-
import { EventFactory } from "applesauce-factory";
|
|
4
|
-
import { EventStore } from "applesauce-core";
|
|
5
|
-
import { ActionHub } from "../../action-hub.js";
|
|
6
|
-
import { FollowUser, NewContacts, UnfollowUser } from "../contacts.js";
|
|
7
|
-
const user = new FakeUser();
|
|
8
|
-
let events;
|
|
9
|
-
let factory;
|
|
10
|
-
let publish;
|
|
11
|
-
let hub;
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
events = new EventStore();
|
|
14
|
-
factory = new EventFactory({ signer: user });
|
|
15
|
-
publish = vitest.fn().mockResolvedValue(undefined);
|
|
16
|
-
hub = new ActionHub(events, factory, publish);
|
|
17
|
-
});
|
|
18
|
-
describe("FollowUser", () => {
|
|
19
|
-
it("should throw an error if contacts does not exist", async () => {
|
|
20
|
-
// don't add any events to the store
|
|
21
|
-
await expect(hub.run(FollowUser, user.pubkey)).rejects.toThrow();
|
|
22
|
-
expect(publish).not.toHaveBeenCalled();
|
|
23
|
-
});
|
|
24
|
-
it('should publish an event with a new "p" tag', async () => {
|
|
25
|
-
events.add(user.contacts());
|
|
26
|
-
await hub.run(FollowUser, user.pubkey);
|
|
27
|
-
expect(publish).toHaveBeenCalledWith(expect.objectContaining({ tags: expect.arrayContaining([["p", user.pubkey]]) }));
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
describe("UnfollowUser", () => {
|
|
31
|
-
it("should throw an error if contacts does not exist", async () => {
|
|
32
|
-
// don't add any events to the store
|
|
33
|
-
await expect(hub.run(UnfollowUser, user.pubkey)).rejects.toThrow();
|
|
34
|
-
expect(publish).not.toHaveBeenCalled();
|
|
35
|
-
});
|
|
36
|
-
it('should publish an event with a new "p" tag', async () => {
|
|
37
|
-
events.add(user.contacts([user.pubkey]));
|
|
38
|
-
await hub.run(UnfollowUser, user.pubkey);
|
|
39
|
-
expect(publish).toHaveBeenCalledWith(expect.objectContaining({ kind: 3, tags: [] }));
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
describe("NewContacts", () => {
|
|
43
|
-
it("should throw if contact list already exists", async () => {
|
|
44
|
-
events.add(user.contacts([user.pubkey]));
|
|
45
|
-
await expect(hub.run(NewContacts, [])).rejects.toThrow();
|
|
46
|
-
expect(publish).not.toBeCalled();
|
|
47
|
-
});
|
|
48
|
-
it("should publish a new contact event with pubkeys", async () => {
|
|
49
|
-
await hub.run(NewContacts, [user.pubkey]);
|
|
50
|
-
expect(publish).toHaveBeenCalled();
|
|
51
|
-
expect(publish).toHaveBeenCalledWith(expect.objectContaining({ kind: 3, tags: expect.arrayContaining([["p", user.pubkey]]) }));
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { EventStore } from "applesauce-core";
|
|
3
|
-
import { EventFactory } from "applesauce-factory";
|
|
4
|
-
import { kinds } from "nostr-tools";
|
|
5
|
-
import { unlockHiddenTags } from "applesauce-core/helpers";
|
|
6
|
-
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
7
|
-
import { FakeUser } from "../../__tests__/fake-user.js";
|
|
8
|
-
import { ActionHub } from "../../action-hub.js";
|
|
9
|
-
import { AddUserToFollowSet, RemoveUserFromFollowSet } from "../follow-sets.js";
|
|
10
|
-
const user = new FakeUser();
|
|
11
|
-
const testPubkey = "test-pubkey";
|
|
12
|
-
const testIdentifier = "test-list";
|
|
13
|
-
let events;
|
|
14
|
-
let factory;
|
|
15
|
-
let hub;
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
events = new EventStore();
|
|
18
|
-
factory = new EventFactory({ signer: user });
|
|
19
|
-
hub = new ActionHub(events, factory);
|
|
20
|
-
// Add a follow set event to work with
|
|
21
|
-
const followSet = user.event({
|
|
22
|
-
kind: kinds.Followsets,
|
|
23
|
-
tags: [["d", testIdentifier]],
|
|
24
|
-
content: "",
|
|
25
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
26
|
-
});
|
|
27
|
-
events.add(followSet);
|
|
28
|
-
});
|
|
29
|
-
describe("AddUserToList", () => {
|
|
30
|
-
it("should add a pubkey to public tags in a follow set", async () => {
|
|
31
|
-
const spy = subscribeSpyTo(hub.exec(AddUserToFollowSet, testPubkey, testIdentifier), { expectErrors: false });
|
|
32
|
-
await spy.onComplete();
|
|
33
|
-
const emittedEvent = spy.getLastValue();
|
|
34
|
-
expect(emittedEvent).toMatchObject({
|
|
35
|
-
kind: kinds.Followsets,
|
|
36
|
-
tags: expect.arrayContaining([
|
|
37
|
-
["d", testIdentifier],
|
|
38
|
-
["p", testPubkey],
|
|
39
|
-
]),
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
it("should add a pubkey to hidden tags in a follow set", async () => {
|
|
43
|
-
const spy = subscribeSpyTo(hub.exec(AddUserToFollowSet, testPubkey, testIdentifier, true), { expectErrors: false });
|
|
44
|
-
await spy.onComplete();
|
|
45
|
-
const emittedEvent = spy.getLastValue();
|
|
46
|
-
expect(await unlockHiddenTags(emittedEvent, user)).toEqual(expect.arrayContaining([["p", testPubkey]]));
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
describe("RemoveUserFromList", () => {
|
|
50
|
-
beforeEach(async () => {
|
|
51
|
-
// Add a follow set with existing tags to remove
|
|
52
|
-
const followSetWithTags = user.event({
|
|
53
|
-
kind: kinds.Followsets,
|
|
54
|
-
tags: [
|
|
55
|
-
["d", testIdentifier],
|
|
56
|
-
["p", testPubkey],
|
|
57
|
-
],
|
|
58
|
-
content: await user.nip04.encrypt(user.pubkey, JSON.stringify(["p", testPubkey])),
|
|
59
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
60
|
-
});
|
|
61
|
-
events.add(followSetWithTags);
|
|
62
|
-
});
|
|
63
|
-
it("should remove a pubkey from public tags in a follow set", async () => {
|
|
64
|
-
const spy = subscribeSpyTo(hub.exec(RemoveUserFromFollowSet, testPubkey, testIdentifier), { expectErrors: false });
|
|
65
|
-
await spy.onComplete();
|
|
66
|
-
const emittedEvent = spy.getLastValue();
|
|
67
|
-
expect(emittedEvent).toMatchObject({
|
|
68
|
-
kind: kinds.Followsets,
|
|
69
|
-
tags: expect.not.arrayContaining([["p", testPubkey]]),
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
it("should remove a pubkey from hidden tags in a follow set", async () => {
|
|
73
|
-
const spy = subscribeSpyTo(hub.exec(RemoveUserFromFollowSet, testPubkey, testIdentifier, true), {
|
|
74
|
-
expectErrors: false,
|
|
75
|
-
});
|
|
76
|
-
await spy.onComplete();
|
|
77
|
-
const emittedEvent = spy.getLastValue();
|
|
78
|
-
expect(await unlockHiddenTags(emittedEvent, user)).toEqual(expect.not.arrayContaining([["hidden", "p", testPubkey]]));
|
|
79
|
-
});
|
|
80
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { EventStore } from "applesauce-core";
|
|
3
|
-
import { EventFactory } from "applesauce-factory";
|
|
4
|
-
import { kinds } from "nostr-tools";
|
|
5
|
-
import { getMutedThings, getHiddenMutedThings } from "applesauce-core/helpers";
|
|
6
|
-
import { subscribeSpyTo } from "@hirez_io/observer-spy";
|
|
7
|
-
import { FakeUser } from "../../__tests__/fake-user.js";
|
|
8
|
-
import { ActionHub } from "../../action-hub.js";
|
|
9
|
-
import { MuteThread, UnmuteThread } from "../mute.js";
|
|
10
|
-
const user = new FakeUser();
|
|
11
|
-
const testEventId = "test-event-id";
|
|
12
|
-
let events;
|
|
13
|
-
let factory;
|
|
14
|
-
let hub;
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
events = new EventStore();
|
|
17
|
-
factory = new EventFactory({ signer: user });
|
|
18
|
-
hub = new ActionHub(events, factory);
|
|
19
|
-
// Add a mute list event to work with
|
|
20
|
-
const muteList = user.event({
|
|
21
|
-
kind: kinds.Mutelist,
|
|
22
|
-
tags: [],
|
|
23
|
-
content: "",
|
|
24
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
25
|
-
});
|
|
26
|
-
events.add(muteList);
|
|
27
|
-
});
|
|
28
|
-
describe("MuteThread", () => {
|
|
29
|
-
it("should add an event to public tags in mute list", async () => {
|
|
30
|
-
const spy = subscribeSpyTo(hub.exec(MuteThread, testEventId), { expectErrors: false });
|
|
31
|
-
await spy.onComplete();
|
|
32
|
-
const emittedEvent = spy.getLastValue();
|
|
33
|
-
const mutedThings = getMutedThings(emittedEvent);
|
|
34
|
-
expect(mutedThings.threads).toContain(testEventId);
|
|
35
|
-
});
|
|
36
|
-
it("should add an event to hidden tags in mute list", async () => {
|
|
37
|
-
const spy = subscribeSpyTo(hub.exec(MuteThread, testEventId, true), { expectErrors: false });
|
|
38
|
-
await spy.onComplete();
|
|
39
|
-
const emittedEvent = spy.getLastValue();
|
|
40
|
-
const hiddenMutedThings = await getHiddenMutedThings(emittedEvent);
|
|
41
|
-
expect(hiddenMutedThings?.threads).toContain(testEventId);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
describe("UnmuteThread", () => {
|
|
45
|
-
it("should remove an event from public tags in mute list", async () => {
|
|
46
|
-
// First add the thread to mute list
|
|
47
|
-
const addSpy = subscribeSpyTo(hub.exec(MuteThread, testEventId), { expectErrors: false });
|
|
48
|
-
await addSpy.onComplete();
|
|
49
|
-
// Then unmute it
|
|
50
|
-
const spy = subscribeSpyTo(hub.exec(UnmuteThread, testEventId), { expectErrors: false });
|
|
51
|
-
await spy.onComplete();
|
|
52
|
-
const emittedEvent = spy.getLastValue();
|
|
53
|
-
const mutedThings = getMutedThings(emittedEvent);
|
|
54
|
-
expect(mutedThings.threads).not.toContain(testEventId);
|
|
55
|
-
});
|
|
56
|
-
it("should remove an event from hidden tags in mute list", async () => {
|
|
57
|
-
// First add the thread to hidden mute list
|
|
58
|
-
const addSpy = subscribeSpyTo(hub.exec(MuteThread, testEventId, true), { expectErrors: false });
|
|
59
|
-
await addSpy.onComplete();
|
|
60
|
-
// Then unmute it
|
|
61
|
-
const spy = subscribeSpyTo(hub.exec(UnmuteThread, testEventId, true), { expectErrors: false });
|
|
62
|
-
await spy.onComplete();
|
|
63
|
-
const emittedEvent = spy.getLastValue();
|
|
64
|
-
const hiddenMutedThings = await getHiddenMutedThings(emittedEvent);
|
|
65
|
-
expect(hiddenMutedThings?.threads).not.toContain(testEventId);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/** Subscribes to an observable, send all the values to a method and unsubscribes when complete */
|
|
2
|
-
export function play(stream, method) {
|
|
3
|
-
return new Promise((resolve, reject) => {
|
|
4
|
-
const sub = stream.subscribe({
|
|
5
|
-
next: (v) => {
|
|
6
|
-
try {
|
|
7
|
-
method(v);
|
|
8
|
-
}
|
|
9
|
-
catch (error) {
|
|
10
|
-
reject(error);
|
|
11
|
-
}
|
|
12
|
-
},
|
|
13
|
-
error: (error) => reject(error),
|
|
14
|
-
complete: () => {
|
|
15
|
-
sub.unsubscribe();
|
|
16
|
-
resolve();
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
}
|