{
const theme = useTheme()
- const [isLoading, setIsLoading] = useState(false)
- const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('')
-
- const handleClearData = async () => {
- setIsLoading(true)
- setLoadingSpinnerDesc('Clearing cache data')
- localCache
- .clearCacheData()
- .then(() => {
- toast.success('cleared cached data')
- })
- .catch((err) => {
- console.log('An error occurred in clearing cache data', err)
- toast.error(err.message || 'An error occurred in clearing cache data')
- })
- .finally(() => {
- setIsLoading(false)
- })
- }
-
const listItem = (label: string) => {
return (
{
return (
<>
- {isLoading && }
{
{listItem('Import (coming soon)')}
-
-
-
-
-
- {listItem('Clear Cache')}
-
diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx
index 07ffd4d..a1a3039 100644
--- a/src/pages/sign/index.tsx
+++ b/src/pages/sign/index.tsx
@@ -28,7 +28,8 @@ import {
signEventForMetaFile,
unixNow,
updateMarks,
- uploadMetaToFileStorage
+ uploadMetaToFileStorage,
+ parseNostrEvent
} from '../../utils'
import { CurrentUserMark, Mark } from '../../types/mark.ts'
import PdfMarking from '../../components/PDFView/PdfMarking.tsx'
@@ -36,12 +37,14 @@ import { convertToSigitFile, SigitFile } from '../../utils/file.ts'
import { generateTimestamp } from '../../utils/opentimestamps.ts'
import { MARK_TYPE_CONFIG } from '../../components/MarkTypeStrategy/MarkStrategy.tsx'
import { useNDK } from '../../hooks/useNDK.ts'
+import { SendDMError } from '../../types/errors/SendDMError.ts'
export const SignPage = () => {
const navigate = useNavigate()
const location = useLocation()
const params = useParams()
- const { updateUsersAppData, sendNotification } = useNDK()
+ const { updateUsersAppData, sendNotification, sendPrivateDirectMessage } =
+ useNDK()
const usersAppData = useAppSelector((state) => state.userAppData)
@@ -602,6 +605,66 @@ export const SignPage = () => {
toast.error('Failed to publish notifications')
})
+ // Send DMs
+ setLoadingSpinnerDesc('Sending DMs')
+ const createSignatureEvent = parseNostrEvent(meta.createSignature)
+ const { id } = createSignatureEvent
+
+ if (isLastSigner) {
+ // Final sign sends to everyone (creator, signers, viewers - /verify)
+ const areSent: boolean[] = Array(users.length).fill(false)
+ for (let i = 0; i < users.length; i++) {
+ try {
+ areSent[i] = await sendPrivateDirectMessage(
+ `Sigit completed, visit ${window.location.origin}/#/verify/${id}`,
+ npubToHex(users[i])!
+ )
+ } catch (error) {
+ if (error instanceof SendDMError) {
+ toast.error(error.message)
+ }
+ console.error(error)
+ }
+ }
+
+ if (areSent.some((r) => r)) {
+ toast.success(
+ `DMs sent ${areSent.filter((r) => r).length}/${users.length}`
+ )
+ }
+ } else {
+ // Notify the creator and
+ // the next signer (/sign).
+ try {
+ await sendPrivateDirectMessage(
+ `Sigit signed by ${usersNpub}, visit ${window.location.origin}/#/sign/${id}`,
+ npubToHex(submittedBy!)!
+ )
+ } catch (error) {
+ if (error instanceof SendDMError) {
+ toast.error(error.message)
+ }
+ console.error(error)
+ }
+
+ // No need to notify creator twice, skipping
+ const currentSignerIndex = signers.indexOf(usersNpub)
+ const nextSigner = npubToHex(signers[currentSignerIndex + 1])
+ if (nextSigner !== submittedBy) {
+ try {
+ await sendPrivateDirectMessage(
+ `You're the next signer, visit ${window.location.origin}/#/sign/${id}`,
+ nextSigner!
+ )
+ } catch (error) {
+ if (error instanceof SendDMError) {
+ toast.error(error.message)
+ }
+ console.error(error)
+ }
+ }
+ }
+
setIsLoading(false)
}
diff --git a/src/routes/PrivateRoute.tsx b/src/routes/PrivateRoute.tsx
new file mode 100644
index 0000000..410ecea
--- /dev/null
+++ b/src/routes/PrivateRoute.tsx
@@ -0,0 +1,21 @@
+import { Navigate, useLocation } from 'react-router-dom'
+import { useAppSelector } from '../hooks'
+import { appPublicRoutes } from '.'
+
+export function PrivateRoute({ children }: { children: JSX.Element }) {
+ const location = useLocation()
+ const isLoggedIn = useAppSelector((state) => state.auth?.loggedIn)
+ if (!isLoggedIn) {
+ return (
+
+ )
+ }
+
+ return children
+}
diff --git a/src/routes/util.tsx b/src/routes/util.tsx
index 8773b81..2e1be26 100644
--- a/src/routes/util.tsx
+++ b/src/routes/util.tsx
@@ -11,6 +11,7 @@ import { RelaysPage } from '../pages/settings/relays'
import { SettingsPage } from '../pages/settings/Settings'
import { SignPage } from '../pages/sign'
import { VerifyPage } from '../pages/verify'
+import { PrivateRoute } from './PrivateRoute'
/**
* Helper type allows for extending react-router-dom's **RouteProps** with generic type
@@ -70,34 +71,66 @@ export const publicRoutes: PublicRouteProps[] = [
export const privateRoutes = [
{
path: appPrivateRoutes.homePage,
- element:
+ element: (
+
+
+
+ )
},
{
path: appPrivateRoutes.create,
- element:
+ element: (
+
+
+
+ )
},
{
path: `${appPrivateRoutes.sign}/:id?`,
- element:
+ element: (
+
+
+
+ )
},
{
path: appPrivateRoutes.settings,
- element:
+ element: (
+
+
+
+ )
},
{
path: appPrivateRoutes.profileSettings,
- element:
+ element: (
+
+
+
+ )
},
{
path: appPrivateRoutes.cacheSettings,
- element:
+ element: (
+
+
+
+ )
},
{
path: appPrivateRoutes.relays,
- element:
+ element: (
+
+
+
+ )
},
{
path: appPrivateRoutes.nostrLogin,
- element:
+ element: (
+
+
+
+ )
}
]
diff --git a/src/services/cache/index.ts b/src/services/cache/index.ts
deleted file mode 100644
index 957e45b..0000000
--- a/src/services/cache/index.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { IDBPDatabase, openDB } from 'idb'
-import { Event } from 'nostr-tools'
-import { CachedEvent } from '../../types'
-import { SchemaV2 } from './schema'
-
-class LocalCache {
- // Static property to hold the single instance of LocalCache
- private static instance: LocalCache | null = null
- private db!: IDBPDatabase
-
- // Private constructor to prevent direct instantiation
- private constructor() {}
-
- // Method to initialize the database
- private async init() {
- this.db = await openDB('sigit-cache', 2, {
- upgrade(db, oldVersion) {
- if (oldVersion < 1) {
- db.createObjectStore('userMetadata', { keyPath: 'event.pubkey' })
- }
-
- if (oldVersion < 2) {
- const v6 = db as unknown as IDBPDatabase
-
- v6.createObjectStore('userRelayListMetadata', {
- keyPath: 'event.pubkey'
- })
- }
- }
- })
- }
-
- // Static method to get the single instance of LocalCache
- public static async getInstance(): Promise {
- // If the instance doesn't exist, create it
- if (!LocalCache.instance) {
- LocalCache.instance = new LocalCache()
- await LocalCache.instance.init()
- }
- // Return the single instance of LocalCache
- return LocalCache.instance
- }
-
- // Method to add user metadata
- public async addUserMetadata(event: Event) {
- await this.db.put('userMetadata', { event, cachedAt: Date.now() })
- }
-
- // Method to get user metadata by key
- public async getUserMetadata(key: string): Promise {
- const data = await this.db.get('userMetadata', key)
- return data || null
- }
-
- // Method to delete user metadata by key
- public async deleteUserMetadata(key: string) {
- await this.db.delete('userMetadata', key)
- }
-
- public async addUserRelayListMetadata(event: Event) {
- await this.db.put('userRelayListMetadata', { event, cachedAt: Date.now() })
- }
-
- public async getUserRelayListMetadata(
- key: string
- ): Promise {
- const data = await this.db.get('userRelayListMetadata', key)
- return data || null
- }
-
- public async deleteUserRelayListMetadata(key: string) {
- await this.db.delete('userRelayListMetadata', key)
- }
-
- // Method to clear cache data
- public async clearCacheData() {
- // Clear the 'userMetadata' store in the IndexedDB database
- await this.db.clear('userMetadata')
-
- // Reload the current page to ensure any cached data is reset
- window.location.reload()
- }
-}
-
-// Export the single instance of LocalCache
-export const localCache = await LocalCache.getInstance()
diff --git a/src/services/cache/schema.ts b/src/services/cache/schema.ts
deleted file mode 100644
index bc21956..0000000
--- a/src/services/cache/schema.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { DBSchema } from 'idb'
-import { CachedEvent } from '../../types'
-
-export interface SchemaV1 extends DBSchema {
- userMetadata: {
- key: string
- value: CachedEvent
- }
-}
-
-export interface SchemaV2 extends SchemaV1 {
- userRelayListMetadata: {
- key: string
- value: CachedEvent
- }
-}
diff --git a/src/services/index.ts b/src/services/index.ts
index b8d275a..eb0b67f 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -1,2 +1 @@
-export * from './cache'
export * from './signer'
diff --git a/src/types/errors/SendDMError.ts b/src/types/errors/SendDMError.ts
new file mode 100644
index 0000000..70cc94a
--- /dev/null
+++ b/src/types/errors/SendDMError.ts
@@ -0,0 +1,23 @@
+import { Jsonable } from '.'
+
+export enum SendDMErrorType {
+ 'MISSING_RECIEVER' = 'Sending DM failed. Reciever is required.',
+ 'ENCRYPTION_FAILED' = 'Sending DM failed. An error occurred in encrypting dm message.',
+ 'RELAY_PUBLISH_FAILED' = 'Sending DM failed. Publishing events failed.'
+}
+
+export class SendDMError extends Error {
+ public readonly context?: Jsonable
+
+ constructor(
+ message: string,
+ options: { cause?: Error; context?: Jsonable } = {}
+ ) {
+ const { cause, context } = options
+
+ super(message, { cause })
+ this.name = this.constructor.name
+
+ this.context = context
+ }
+}
diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts
index 472e092..8196e35 100644
--- a/src/utils/localStorage.ts
+++ b/src/utils/localStorage.ts
@@ -26,30 +26,6 @@ export const clearState = () => {
localStorage.removeItem('state')
}
-export const saveVisitedLink = (pathname: string, search: string) => {
- localStorage.setItem(
- 'visitedLink',
- JSON.stringify({
- pathname,
- search
- })
- )
-}
-
-export const getVisitedLink = () => {
- const visitedLink = localStorage.getItem('visitedLink')
- if (!visitedLink) return null
-
- try {
- return JSON.parse(visitedLink) as {
- pathname: string
- search: string
- }
- } catch {
- return null
- }
-}
-
export const saveAuthToken = (token: string) => {
localStorage.setItem('authToken', token)
}
diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts
index 600bd08..824a967 100644
--- a/src/utils/nostr.ts
+++ b/src/utils/nostr.ts
@@ -6,6 +6,7 @@ import {
Event,
EventTemplate,
UnsignedEvent,
+ VerifiedEvent,
finalizeEvent,
generateSecretKey,
getEventHash,
@@ -214,6 +215,12 @@ export const toUnixTimestamp = (date: number | Date) => {
export const fromUnixTimestamp = (unix: number) => {
return unix * 1000
}
+export const randomTimeUpTo2DaysInThePast = (): number => {
+ const now = Date.now()
+ const twoDaysInMilliseconds = 2 * 24 * 60 * 60 * 1000
+ const randomPastTime = now - Math.floor(Math.random() * twoDaysInMilliseconds)
+ return toUnixTimestamp(randomPastTime)
+}
/**
* Generate nip44 conversation key
@@ -263,19 +270,21 @@ export const countLeadingZeroes = (hex: string) => {
/**
* Function to create a wrapped event with PoW
- * @param event Original event to be wrapped
+ * @param event Original event to be wrapped (can be unsigned or verified)
* @param receiver Public key of the receiver
- * @param difficulty PoW difficulty level (default is 20)
* @returns
*/
//
-export const createWrap = (unsignedEvent: UnsignedEvent, receiver: string) => {
+export const createWrap = (
+ event: UnsignedEvent | VerifiedEvent,
+ receiver: string
+) => {
// Generate a random secret key and its corresponding public key
const randomKey = generateSecretKey()
const pubkey = getPublicKey(randomKey)
// Encrypt the event content using nip44 encryption
- const content = nip44Encrypt(unsignedEvent, randomKey, receiver)
+ const content = nip44Encrypt(event, randomKey, receiver)
// Initialize nonce and leadingZeroes for PoW calculation
let nonce = 0
@@ -286,11 +295,12 @@ export const createWrap = (unsignedEvent: UnsignedEvent, receiver: string) => {
// eslint-disable-next-line no-constant-condition
while (true) {
// Create an unsigned event with the necessary fields
+ // TODO: kinds.GiftWrap (wrong kind number in nostr-tools 10/11/2024 at v2.7.2)
const event: UnsignedEvent = {
kind: 1059, // Event kind
content, // Encrypted content
pubkey, // Public key of the creator
- created_at: unixNow(), // Current timestamp
+ created_at: randomTimeUpTo2DaysInThePast(),
tags: [
// Tags including receiver and nonce
['p', receiver],
diff --git a/src/utils/relays.ts b/src/utils/relays.ts
index bcb2e98..ef351d9 100644
--- a/src/utils/relays.ts
+++ b/src/utils/relays.ts
@@ -37,7 +37,6 @@ export const getRelayMapFromNDKRelayList = (ndkRelayList: NDKRelayList) => {
export const getDefaultRelayMap = (): RelayMap => ({
[SIGIT_RELAY]: { write: true, read: true }
})
-
/**
* Publishes relay map.
* @param relayMap - relay map.