diff --git a/package.json b/package.json
index 83b67e6e..affc0670 100644
--- a/package.json
+++ b/package.json
@@ -87,7 +87,7 @@
"tauri-controls": "^0.2.0",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.2",
- "virtua": "^0.14.0",
+ "virtua": "^0.15.0",
"zustand": "^4.4.3"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ab3e1f91..05121e25 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -213,8 +213,8 @@ dependencies:
specifier: ^0.8.2
version: 0.8.2(@tiptap/core@2.1.12)
virtua:
- specifier: ^0.14.0
- version: 0.14.0(react-dom@18.2.0)(react@18.2.0)
+ specifier: ^0.15.0
+ version: 0.15.0(react-dom@18.2.0)(react@18.2.0)
zustand:
specifier: ^4.4.3
version: 4.4.3(@types/react@18.2.29)(react@18.2.0)
@@ -6871,8 +6871,8 @@ packages:
vfile-message: 3.1.4
dev: false
- /virtua@0.14.0(react-dom@18.2.0)(react@18.2.0):
- resolution: {integrity: sha512-+g3fxgFuQCqw6PpU5qzTRKhbSUGOeMEap0VbPaIRB1RiK5MfLiGXIMwID1iX1DmvUC/SqsBsJfVvlUaPNGWSVQ==}
+ /virtua@0.15.0(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-kzwin55Tj85tcpNO7p5p7U12+wT6+CJaDSr98BTNKD6t7QEmigDwE7h6dcP170LrY8tW+scsMUtipcroSeSpAw==}
peerDependencies:
react: '>=16.14.0'
react-dom: '>=16.14.0'
diff --git a/src/app.tsx b/src/app.tsx
index 1ec6544a..62edb50a 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -65,13 +65,6 @@ export default function App() {
return { Component: UserScreen };
},
},
- {
- path: 'notifications',
- async lazy() {
- const { NotificationScreen } = await import('@app/notifications');
- return { Component: NotificationScreen };
- },
- },
{
path: 'nwc',
async lazy() {
diff --git a/src/app/notifications/components/content.tsx b/src/app/notifications/components/content.tsx
deleted file mode 100644
index 1b97277e..00000000
--- a/src/app/notifications/components/content.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import ReactMarkdown from 'react-markdown';
-import remarkGfm from 'remark-gfm';
-
-import { Hashtag, MentionUser } from '@shared/notes';
-
-import { RichContent } from '@utils/types';
-
-export function NotiContent({ content }: { content: RichContent }) {
- return (
- <>
- {
- const key = children[0] as string;
- if (key.startsWith('pub') && key.length > 50 && key.length < 100)
- return ;
- if (key.startsWith('tag')) return ;
- },
- }}
- >
- {content?.parsed}
-
- >
- );
-}
diff --git a/src/app/notifications/components/mention.tsx b/src/app/notifications/components/mention.tsx
deleted file mode 100644
index 1e918f54..00000000
--- a/src/app/notifications/components/mention.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { NDKEvent } from '@nostr-dev-kit/ndk';
-import { Link } from 'react-router-dom';
-
-import { NotiUser } from '@app/notifications/components/user';
-
-import { formatCreatedAt } from '@utils/createdAt';
-
-export function NotiMention({ event }: { event: NDKEvent }) {
- const createdAt = formatCreatedAt(event.created_at);
- const rootId = event.tags.find((el) => el[0])?.[1];
-
- return (
-
-
-
-
-
- has mention you · {createdAt}
-
-
-
- View
-
-
-
- );
-}
diff --git a/src/app/notifications/components/reaction.tsx b/src/app/notifications/components/reaction.tsx
deleted file mode 100644
index 1043cff2..00000000
--- a/src/app/notifications/components/reaction.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { NDKEvent } from '@nostr-dev-kit/ndk';
-import { Link } from 'react-router-dom';
-
-import { NotiUser } from '@app/notifications/components/user';
-
-import { formatCreatedAt } from '@utils/createdAt';
-
-export function NotiReaction({ event }: { event: NDKEvent }) {
- const createdAt = formatCreatedAt(event.created_at);
- const rootId = event.tags.find((el) => el[0])?.[1];
-
- return (
-
-
-
-
-
- reacted {event.content} · {createdAt}
-
-
-
- View
-
-
-
- );
-}
diff --git a/src/app/notifications/components/repost.tsx b/src/app/notifications/components/repost.tsx
deleted file mode 100644
index c50e7e36..00000000
--- a/src/app/notifications/components/repost.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NDKEvent } from '@nostr-dev-kit/ndk';
-import { Link } from 'react-router-dom';
-
-import { NotiUser } from '@app/notifications/components/user';
-
-import { useStorage } from '@libs/storage/provider';
-
-import { formatCreatedAt } from '@utils/createdAt';
-
-export function NotiRepost({ event }: { event: NDKEvent }) {
- const { db } = useStorage();
-
- const createdAt = formatCreatedAt(event.created_at);
- const rootId = event.tags.find((el) => el[0])?.[1];
-
- return (
-
-
-
-
-
- repost{' '}
- {event.pubkey !== db.account.pubkey ? 'a post that mention you' : 'your post'}{' '}
- · {createdAt}
-
-
-
- View
-
-
-
- );
-}
diff --git a/src/app/notifications/components/simpleNote.tsx b/src/app/notifications/components/simpleNote.tsx
deleted file mode 100644
index 963ef47d..00000000
--- a/src/app/notifications/components/simpleNote.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import { memo } from 'react';
-
-import { useStorage } from '@libs/storage/provider';
-
-import { NoteSkeleton } from '@shared/notes';
-import { User } from '@shared/user';
-
-import { WidgetKinds, useWidgets } from '@stores/widgets';
-
-import { useEvent } from '@utils/hooks/useEvent';
-
-export const SimpleNote = memo(function SimpleNote({ id }: { id: string }) {
- const { db } = useStorage();
- const { status, data } = useEvent(id);
-
- const setWidget = useWidgets((state) => state.setWidget);
-
- const openThread = (event, thread: string) => {
- const selection = window.getSelection();
- if (selection.toString().length === 0) {
- setWidget(db, { kind: WidgetKinds.local.thread, title: 'Thread', content: thread });
- } else {
- event.stopPropagation();
- }
- };
-
- if (status === 'loading') {
- return (
-
-
-
- );
- }
-
- if (status === 'error') {
- return (
-
-
Can't get event from relay
-
- );
- }
-
- return (
- openThread(e, id)}
- onKeyDown={(e) => openThread(e, id)}
- role="button"
- tabIndex={0}
- className="mb-2 mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl"
- >
-
-
-
- {data.content.length > 200
- ? data.content.substring(0, 200) + '...'
- : data.content}
-
-
-
- );
-});
diff --git a/src/app/notifications/components/user.tsx b/src/app/notifications/components/user.tsx
deleted file mode 100644
index 8d4f70d9..00000000
--- a/src/app/notifications/components/user.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Image } from '@shared/image';
-
-import { useProfile } from '@utils/hooks/useProfile';
-import { displayNpub } from '@utils/shortenKey';
-
-export function NotiUser({ pubkey }: { pubkey: string }) {
- const { status, user } = useProfile(pubkey);
-
- if (status === 'loading') {
- return (
-
- );
- }
-
- return (
-
-
-
- {user?.name || user?.display_name || user?.displayName || displayNpub(pubkey, 16)}
-
-
- );
-}
diff --git a/src/app/notifications/index.tsx b/src/app/notifications/index.tsx
deleted file mode 100644
index 2a2913f6..00000000
--- a/src/app/notifications/index.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { NDKEvent } from '@nostr-dev-kit/ndk';
-import { useCallback, useEffect } from 'react';
-
-import { NotiMention } from '@app/notifications/components/mention';
-import { NotiReaction } from '@app/notifications/components/reaction';
-import { NotiRepost } from '@app/notifications/components/repost';
-
-import { useStorage } from '@libs/storage/provider';
-
-import { LoaderIcon } from '@shared/icons';
-import { TitleBar } from '@shared/titleBar';
-
-import { useActivities } from '@stores/activities';
-
-import { useNostr } from '@utils/hooks/useNostr';
-
-export function NotificationScreen() {
- const { db } = useStorage();
- const { fetchActivities } = useNostr();
-
- const [activities, setActivities, clearTotalNewActivities] = useActivities((state) => [
- state.activities,
- state.setActivities,
- state.clearTotalNewActivities,
- ]);
-
- const renderItem = useCallback(
- (event: NDKEvent) => {
- switch (event.kind) {
- case 1:
- return ;
- case 6:
- return ;
- case 7:
- return ;
- default:
- return null;
- }
- },
- [activities]
- );
-
- useEffect(() => {
- async function getActivities() {
- const events = await fetchActivities();
- setActivities(events, db.account.last_login_at);
- }
-
- getActivities();
-
- // clear total new activities
- clearTotalNewActivities();
- }, []);
-
- return (
-
-
-
-
-
- {!activities ? (
-
- ) : activities.length <= 1 ? (
-
-
🎉
-
- Yo!, no new activities around you in the last 24 hours
-
-
- ) : (
- activities.map((event) => renderItem(event))
- )}
-
-
-
-
- );
-}
diff --git a/src/app/space/index.tsx b/src/app/space/index.tsx
index 5f2a5186..b0ef9122 100644
--- a/src/app/space/index.tsx
+++ b/src/app/space/index.tsx
@@ -17,6 +17,7 @@ import {
LocalFilesWidget,
LocalFollowsWidget,
LocalNetworkWidget,
+ LocalNotificationWidget,
LocalThreadWidget,
LocalUserWidget,
TrendingAccountsWidget,
@@ -74,6 +75,8 @@ export function SpaceScreen() {
return ;
case WidgetKinds.other.learnNostr:
return ;
+ case WidgetKinds.local.notification:
+ return ;
default:
return null;
}
@@ -83,7 +86,7 @@ export function SpaceScreen() {
useEffect(() => {
fetchWidgets(db);
- }, [fetchWidgets]);
+ }, []);
return (
{
const filter: NDKFilter = {
- '#p': [db.account.pubkey],
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
since: Math.floor(Date.now() / 1000),
+ '#p': [db.account.pubkey],
};
sub(
filter,
async (event) => {
addActivity(event);
+
+ const user = ndk.getUser({ hexpubkey: event.pubkey });
+ await user.fetchProfile();
+
switch (event.kind) {
case NDKKind.Text:
- return await sendNativeNotification('Mention');
+ return await sendNativeNotification(
+ `${user.profile.displayName || user.profile.name} has replied to your note`
+ );
case NDKKind.Repost:
- return await sendNativeNotification('Repost');
+ return await sendNativeNotification(
+ `${user.profile.displayName || user.profile.name} has reposted to your note`
+ );
case NDKKind.Reaction:
- return await sendNativeNotification('Reaction');
+ return await sendNativeNotification(
+ `${user.profile.displayName || user.profile.name} has reacted ${
+ event.content
+ } to your note`
+ );
case NDKKind.Zap:
- return await sendNativeNotification('Zap');
+ return await sendNativeNotification(
+ `${user.profile.displayName || user.profile.name} has zapped to your note`
+ );
default:
break;
}
@@ -71,7 +87,7 @@ export function ActiveAccount() {
style={{ contentVisibility: 'auto' }}
className="aspect-square h-auto w-full rounded-md"
/>
-
+
{
+ switch (event.kind) {
+ case NDKKind.Text:
+ return ;
+ case NDKKind.Article:
+ return ;
+ case 1063:
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ const renderText = (kind: number) => {
+ switch (kind) {
+ case NDKKind.Text:
+ return 'replied';
+ case NDKKind.Reaction:
+ return `reacted ${content}`;
+ case NDKKind.Repost:
+ return 'reposted';
+ case NDKKind.Zap:
+ return 'zapped';
+ default:
+ return 'Unknown';
+ }
+ };
+
+ if (status === 'loading') {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {renderText(kind)}
+
+
+
+ {createdAt}
+
+
+
+
+
+
+
+
+ {renderKind(data)}
+
+
+
+
+
+
+ );
+}
diff --git a/src/shared/titleBar.tsx b/src/shared/titleBar.tsx
index b15838f3..fe3fbb92 100644
--- a/src/shared/titleBar.tsx
+++ b/src/shared/titleBar.tsx
@@ -26,7 +26,7 @@ export function TitleBar({ id, title }: { id?: string; title?: string }) {
) : null}
) : (
-
+
{title}
)}
diff --git a/src/shared/user.tsx b/src/shared/user.tsx
index 98cf7a13..da963d08 100644
--- a/src/shared/user.tsx
+++ b/src/shared/user.tsx
@@ -28,6 +28,7 @@ export const User = memo(function User({
| 'default'
| 'simple'
| 'mention'
+ | 'notify'
| 'repost'
| 'chat'
| 'large'
@@ -51,7 +52,7 @@ export const User = memo(function User({
);
}
- if (variant === 'mention') {
+ if (variant === 'mention' || variant === 'notify') {
return (
@@ -86,7 +87,7 @@ export const User = memo(function User({
@@ -104,6 +105,36 @@ export const User = memo(function User({
);
}
+ if (variant === 'notify') {
+ return (
+
+
+
+
+
+
+
+
+ {user?.name ||
+ user?.display_name ||
+ user?.displayName ||
+ displayNpub(pubkey, 16)}
+
+
+ );
+ }
+
if (variant === 'large') {
return (
diff --git a/src/shared/widgets/index.ts b/src/shared/widgets/index.ts
index 3dd176af..0402c585 100644
--- a/src/shared/widgets/index.ts
+++ b/src/shared/widgets/index.ts
@@ -6,6 +6,7 @@ export * from './local/thread';
export * from './local/files';
export * from './local/articles';
export * from './local/follows';
+export * from './local/notification';
export * from './global/articles';
export * from './global/files';
export * from './global/hashtag';
diff --git a/src/shared/widgets/local/notification.tsx b/src/shared/widgets/local/notification.tsx
new file mode 100644
index 00000000..842cab39
--- /dev/null
+++ b/src/shared/widgets/local/notification.tsx
@@ -0,0 +1,81 @@
+import { NDKEvent } from '@nostr-dev-kit/ndk';
+import { useCallback, useEffect } from 'react';
+import { VList } from 'virtua';
+
+import { useStorage } from '@libs/storage/provider';
+
+import { LoaderIcon } from '@shared/icons';
+import { NotifyNote } from '@shared/notification/notifyNote';
+import { TitleBar } from '@shared/titleBar';
+import { WidgetWrapper } from '@shared/widgets';
+
+import { useActivities } from '@stores/activities';
+
+import { useNostr } from '@utils/hooks/useNostr';
+import { Widget } from '@utils/types';
+
+export function LocalNotificationWidget({ params }: { params: Widget }) {
+ const { db } = useStorage();
+ const { getAllActivities } = useNostr();
+
+ const [activities, setActivities] = useActivities((state) => [
+ state.activities,
+ state.setActivities,
+ ]);
+
+ const renderEvent = useCallback(
+ (event: NDKEvent) => {
+ const rootEventId = event.tags.find((el) => el[0] === 'e')?.[1];
+ if (!rootEventId) return null;
+ if (event.pubkey === db.account.pubkey) return null;
+ return (
+
+ );
+ },
+ [activities]
+ );
+
+ useEffect(() => {
+ async function getActivities() {
+ const events = await getAllActivities(48);
+ setActivities(events);
+ }
+
+ getActivities();
+ }, []);
+
+ return (
+
+
+
+ {!activities ? (
+
+ ) : activities.length < 1 ? (
+
+
🎉
+
+ Hmm! Nothing new yet.
+
+
+ ) : (
+
+ {activities.map((event) => renderEvent(event))}
+
+ )}
+
+
+ );
+}
diff --git a/src/stores/activities.ts b/src/stores/activities.ts
index 21a1b0f7..1087dd82 100644
--- a/src/stores/activities.ts
+++ b/src/stores/activities.ts
@@ -3,29 +3,20 @@ import { create } from 'zustand';
interface ActivitiesState {
activities: Array
;
- totalNewActivities: number;
- setActivities: (events: NDKEvent[], lastLogin: number) => void;
+ setActivities: (events: NDKEvent[]) => void;
addActivity: (event: NDKEvent) => void;
- clearTotalNewActivities: () => void;
}
export const useActivities = create((set) => ({
activities: null,
- totalNewActivities: 0,
- setActivities: (events: NDKEvent[], lastLogin: number) => {
- const totalLatest = events.filter((ev) => ev.created_at > lastLogin)?.length ?? 0;
+ setActivities: (events: NDKEvent[]) => {
set(() => ({
activities: events,
- totalNewActivities: totalLatest,
}));
},
addActivity: (event: NDKEvent) => {
set((state) => ({
activities: state.activities ? [event, ...state.activities] : [event],
- totalNewActivities: state.totalNewActivities++,
}));
},
- clearTotalNewActivities: () => {
- set(() => ({ totalNewActivities: 0 }));
- },
}));
diff --git a/src/stores/widgets.ts b/src/stores/widgets.ts
index 68fe3562..1b6f3adb 100644
--- a/src/stores/widgets.ts
+++ b/src/stores/widgets.ts
@@ -24,6 +24,7 @@ export const WidgetKinds = {
user: 104,
thread: 105,
follows: 106,
+ notification: 107,
},
global: {
feeds: 1000,
@@ -109,6 +110,11 @@ export const DefaultWidgets: Array = [
{
title: 'Other',
data: [
+ {
+ kind: WidgetKinds.local.notification,
+ title: 'Notification',
+ description: 'Everything happens around you',
+ },
{
kind: WidgetKinds.other.learnNostr,
title: 'Learn Nostr',
@@ -125,9 +131,14 @@ export const useWidgets = create()(
isFetched: false,
fetchWidgets: async (db: LumeStorage) => {
const dbWidgets = await db.getWidgets();
- console.log('db widgets: ', dbWidgets);
- // default: add network widget
+ dbWidgets.unshift({
+ id: '9998',
+ title: 'Notification',
+ content: '',
+ kind: WidgetKinds.local.notification,
+ });
+
dbWidgets.unshift({
id: '9999',
title: '',
diff --git a/src/utils/createBlobFromFile.ts b/src/utils/createBlobFromFile.ts
deleted file mode 100644
index 0825c099..00000000
--- a/src/utils/createBlobFromFile.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { readBinaryFile } from '@tauri-apps/plugin-fs';
-
-export async function createBlobFromFile(path: string): Promise {
- const file = await readBinaryFile(path);
- const blob = new Blob([file]);
- const arr = new Uint8Array(await blob.arrayBuffer());
- return arr;
-}
diff --git a/src/utils/hooks/useNostr.ts b/src/utils/hooks/useNostr.ts
index 5af41258..706ba5a6 100644
--- a/src/utils/hooks/useNostr.ts
+++ b/src/utils/hooks/useNostr.ts
@@ -1,6 +1,4 @@
import { NDKEvent, NDKFilter, NDKKind, NDKSubscription } from '@nostr-dev-kit/ndk';
-import { message, open } from '@tauri-apps/plugin-dialog';
-import { fetch } from '@tauri-apps/plugin-http';
import { LRUCache } from 'lru-cache';
import { NostrEventExt } from 'nostr-fetch';
import { useMemo } from 'react';
@@ -10,7 +8,7 @@ import { useStorage } from '@libs/storage/provider';
import { nHoursAgo } from '@utils/date';
import { getMultipleRandom } from '@utils/transform';
-import { NDKEventWithReplies, NostrBuildResponse } from '@utils/types';
+import { NDKEventWithReplies } from '@utils/types';
export function useNostr() {
const { db } = useStorage();
@@ -53,9 +51,6 @@ export function useNostr() {
list.forEach((item) => {
tags.push(['p', item]);
});
-
- // publish event
- publish({ content: '', kind: NDKKind.Contacts, tags: tags });
};
const removeContact = async (pubkey: string) => {
@@ -66,30 +61,17 @@ export function useNostr() {
list.forEach((item) => {
tags.push(['p', item]);
});
-
- // publish event
- publish({ content: '', kind: NDKKind.Contacts, tags: tags });
};
- const fetchActivities = async () => {
+ const getAllActivities = async (limit?: number) => {
try {
- const events = await fetcher.fetchAllEvents(
- relayUrls,
- {
- kinds: [
- NDKKind.Text,
- NDKKind.Contacts,
- NDKKind.Repost,
- NDKKind.Reaction,
- NDKKind.Zap,
- ],
- '#p': [db.account.pubkey],
- },
- { since: nHoursAgo(24) },
- { sort: true }
- );
+ const events = await ndk.fetchEvents({
+ kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Reaction, NDKKind.Zap],
+ '#p': [db.account.pubkey],
+ limit: limit ?? 100,
+ });
- return events as unknown as NDKEvent[];
+ return [...events];
} catch (e) {
console.error('Error fetching activities', e);
}
@@ -289,28 +271,6 @@ export function useNostr() {
return relayMap;
};
- const publish = async ({
- content,
- kind,
- tags,
- }: {
- content: string;
- kind: NDKKind | number;
- tags: string[][];
- }): Promise => {
- const event = new NDKEvent(ndk);
- event.content = content;
- event.kind = kind;
- event.created_at = Math.floor(Date.now() / 1000);
- event.pubkey = db.account.pubkey;
- event.tags = tags;
-
- await event.sign();
- await event.publish();
-
- return event;
- };
-
const createZap = async (event: NDKEvent, amount: number, message?: string) => {
// @ts-expect-error, NostrEvent to NDKEvent
const ndkEvent = new NDKEvent(ndk, event);
@@ -319,87 +279,6 @@ export function useNostr() {
return res;
};
- const upload = async (file: null | string, nip94?: boolean) => {
- try {
- let filepath = file;
-
- if (!file) {
- const selected = await open({
- multiple: false,
- filters: [
- {
- name: 'Media',
- extensions: [
- 'png',
- 'jpeg',
- 'jpg',
- 'gif',
- 'mp4',
- 'mp3',
- 'webm',
- 'mkv',
- 'avi',
- 'mov',
- ],
- },
- ],
- });
- if (Array.isArray(selected)) {
- // user selected multiple files
- } else if (selected === null) {
- return {
- url: null,
- error: 'Cancelled',
- };
- } else {
- filepath = selected.path;
- }
- }
-
- const formData = new FormData();
- formData.append('file', filepath);
-
- const res: NostrBuildResponse = await fetch(
- 'https://nostr.build/api/v2/upload/files',
- {
- method: 'POST',
- headers: { 'Content-Type': 'multipart/form-data' },
- body: formData,
- }
- );
-
- if (res.ok) {
- const data = res.data.data[0];
- const url = data.url;
-
- if (nip94) {
- const tags = [
- ['url', url],
- ['x', data.sha256 ?? ''],
- ['m', data.mime ?? 'application/octet-stream'],
- ['size', data.size.toString() ?? '0'],
- ['dim', `${data.dimensions.width}x${data.dimensions.height}` ?? '0'],
- ['blurhash', data.blurhash ?? ''],
- ];
-
- await publish({ content: '', kind: 1063, tags: tags });
- }
-
- return {
- url: url,
- error: null,
- };
- }
-
- return {
- url: null,
- error: 'Upload failed',
- };
- } catch (e) {
- await message(e, { title: 'Lume', type: 'error' });
- }
- };
-
return {
sub,
addContact,
@@ -409,11 +288,9 @@ export function useNostr() {
getContactsByPubkey,
getEventsByPubkey,
getAllRelaysByUsers,
- fetchActivities,
+ getAllActivities,
fetchNIP04Messages,
fetchAllReplies,
- publish,
createZap,
- upload,
};
}