Compare commits
7 Commits
staging
...
92-send-co
Author | SHA1 | Date | |
---|---|---|---|
|
efe3c2c9c7 | ||
|
4b5955fa9c | ||
|
664ed9de06 | ||
|
1e643c60e5 | ||
b04f4fb88d | |||
3b4bf9aa29 | |||
e85e9519d2 |
@ -26,27 +26,9 @@ jobs:
|
||||
- name: Create Build
|
||||
run: npm run build
|
||||
|
||||
- name: Deploy Build
|
||||
- name: Release Build
|
||||
run: |
|
||||
npm -g install cloudron-surfer
|
||||
surfer config --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io
|
||||
surfer config --token ${{ secrets.CLOUDRON_SURFER_TOKEN }} --server sigit.io
|
||||
surfer put dist/* / --all -d
|
||||
surfer put dist/.well-known / --all
|
||||
|
||||
- name: Create Empty Release (assets are posted later)
|
||||
run: |
|
||||
npm i
|
||||
npm i -g semantic-release
|
||||
# We do a semantic-release DRY RUN to make the job fail if there are no changes to release
|
||||
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.nostrdev.com/sigit/sigit.io semantic-release --dry-run | grep -q "There are no relevant changes, so no new version is released." && exit 1
|
||||
GITEA_TOKEN=${{ secrets.RELEASE_TOKEN }} GITEA_URL=https://git.nostrdev.com/sigit/sigit.io semantic-release
|
||||
|
||||
- name: Upload assets to release
|
||||
run: |
|
||||
RELEASE_ID=`curl -k 'https://git.nostrdev.com/sigit/sigit.io/api/v1/repos/sigit/sigit.io/releases/latest?access_token=${{ secrets.RELEASE_TOKEN }}' | jq -r '.id'`
|
||||
RELEASE_BODY=`curl -k 'https://git.nostrdev.com/sigit/sigit.io/api/v1/repos/sigit/sigit.io/releases/latest?access_token=${{ secrets.RELEASE_TOKEN }}' | jq -r '.body'`
|
||||
# Update body
|
||||
curl --data '{"draft": false,"body":"'"$RELEASE_BODY\n\nFor installation instructions, please visit https://docs.sigit.io/#/"'"}' -X PATCH --header 'Content-Type: application/json' -k https://git.nostrdev.com/sigit/sigit.io/api/v1/repos/sigit/sigit.io/releases/$RELEASE_ID?access_token=${{ secrets.RELEASE_TOKEN }}
|
||||
# Upload assets
|
||||
URL="https://git.nostrdev.com/sigit/sigit.io/api/v1/repos/sigit/sigit.io/releases/$RELEASE_ID/assets?access_token=${{ secrets.RELEASE_TOKEN }}"
|
||||
curl -k $URL -F attachment=@dist.zip
|
||||
surfer put dist/.well-known / --all
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,7 +8,7 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist-zip
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
|
31
.releaserc
31
.releaserc
@ -1,31 +0,0 @@
|
||||
{
|
||||
"branches": [
|
||||
"main"
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-gitea",
|
||||
{
|
||||
"giteaUrl": "https://git.nostrdev.com/sigit/sigit.io",
|
||||
"assets": [
|
||||
{
|
||||
"path": "dist-zip/dist.zip"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
8906
package-lock.json
generated
8906
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -17,8 +17,7 @@
|
||||
"preview": "vite preview",
|
||||
"preinstall": "git config core.hooksPath .git-hooks",
|
||||
"license-checker": "node licenseChecker.cjs",
|
||||
"lint-staged": "lint-staged",
|
||||
"release": "commit-and-tag-version"
|
||||
"lint-staged": "lint-staged"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "11.11.4",
|
||||
@ -31,8 +30,8 @@
|
||||
"@mui/lab": "5.0.0-alpha.166",
|
||||
"@mui/material": "5.15.11",
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@nostr-dev-kit/ndk": "2.10.0",
|
||||
"@nostr-dev-kit/ndk-cache-dexie": "2.5.1",
|
||||
"@nostr-dev-kit/ndk": "2.11.0",
|
||||
"@nostr-dev-kit/ndk-cache-dexie": "2.5.9",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@reduxjs/toolkit": "2.2.1",
|
||||
"axios": "^1.7.4",
|
||||
@ -66,12 +65,6 @@
|
||||
"use-immer": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^10.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/npm": "11.0.0",
|
||||
"@semantic-release/release-notes-generator": "^11.0.4",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/file-saver": "2.0.7",
|
||||
"@types/lodash": "4.14.202",
|
||||
@ -82,7 +75,6 @@
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.2",
|
||||
"@typescript-eslint/parser": "^7.0.2",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"commit-and-tag-version": "^11.2.2",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
@ -93,7 +85,6 @@
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.1.4",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-zip-pack": "^1.2.4",
|
||||
"vite-tsconfig-paths": "4.3.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
@ -12,7 +12,9 @@ import {
|
||||
import _ from 'lodash'
|
||||
import {
|
||||
Event,
|
||||
finalizeEvent,
|
||||
generateSecretKey,
|
||||
getEventHash,
|
||||
getPublicKey,
|
||||
kinds,
|
||||
UnsignedEvent
|
||||
@ -40,17 +42,21 @@ import {
|
||||
getDTagForUserAppData,
|
||||
getUserAppDataFromBlossom,
|
||||
hexToNpub,
|
||||
nip44Encrypt,
|
||||
parseJson,
|
||||
randomTimeUpTo2DaysInThePast,
|
||||
SIGIT_RELAY,
|
||||
unixNow,
|
||||
uploadUserAppDataToBlossom
|
||||
} from '../utils'
|
||||
import { SendDMError, SendDMErrorType } from '../types/errors/SendDMError'
|
||||
|
||||
export const useNDK = () => {
|
||||
const dispatch = useAppDispatch()
|
||||
const {
|
||||
ndk,
|
||||
fetchEvent,
|
||||
fetchEventFromUserRelays,
|
||||
fetchEventsFromUserRelays,
|
||||
publish,
|
||||
getNDKRelayList
|
||||
@ -503,10 +509,139 @@ export const useNDK = () => {
|
||||
[ndk, usersPubkey, getNDKRelayList]
|
||||
)
|
||||
|
||||
/**
|
||||
* Modified {@link UnsignedEvent Unsigned Event} that includes an id
|
||||
*
|
||||
* Fields id and created_at are required.
|
||||
* @see {@link UnsignedEvent}
|
||||
* @see {@link https://github.com/nostr-protocol/nips/blob/master/17.md#direct-message-kind}
|
||||
*/
|
||||
type UnsignedEventWithId = UnsignedEvent & {
|
||||
id?: string
|
||||
}
|
||||
const sendPrivateDirectMessage = useCallback(
|
||||
async (message: string, receiver: string, subject?: string) => {
|
||||
if (!receiver) throw new SendDMError(SendDMErrorType.MISSING_RECIEVER)
|
||||
|
||||
// Get the direct message preferred relays list
|
||||
// https://github.com/nostr-protocol/nips/blob/master/17.md#publishing
|
||||
const preferredRelaysListEvent = await fetchEventFromUserRelays(
|
||||
{
|
||||
kinds: [NDKKind.DirectMessageReceiveRelayList],
|
||||
authors: [receiver]
|
||||
},
|
||||
receiver,
|
||||
UserRelaysType.Read
|
||||
)
|
||||
|
||||
const isRelayTag = (tag: string[]): boolean => tag[0] === 'relay'
|
||||
const finalRelaysList: string[] = []
|
||||
if (preferredRelaysListEvent) {
|
||||
const preferredRelaysList = preferredRelaysListEvent.tags
|
||||
.filter((t) => isRelayTag(t))
|
||||
.map((t) => t[1])
|
||||
|
||||
finalRelaysList.push(...preferredRelaysList)
|
||||
}
|
||||
|
||||
if (!finalRelaysList.length) {
|
||||
// Get receiver's read relay list
|
||||
const ndkRelayList = await getNDKRelayList(receiver).catch((err) => {
|
||||
// Log an error if retrieving relay list metadata fails
|
||||
console.log(
|
||||
`An error occurred while finding relay list metadata for ${hexToNpub(receiver)}`,
|
||||
err
|
||||
)
|
||||
return null
|
||||
})
|
||||
if (ndkRelayList?.readRelayUrls) {
|
||||
finalRelaysList.push(...ndkRelayList.readRelayUrls)
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalRelaysList.length) {
|
||||
finalRelaysList.push(SIGIT_RELAY)
|
||||
}
|
||||
|
||||
// Generate "sender"
|
||||
const senderSecret = generateSecretKey()
|
||||
const senderPubkey = getPublicKey(senderSecret)
|
||||
|
||||
// Prepare tags for the message
|
||||
const tags: string[][] = [['p', receiver]]
|
||||
|
||||
// Conversation title
|
||||
if (subject) tags.push(['subject', subject])
|
||||
|
||||
// Create private DM event containing the message and relevant metadata
|
||||
// TODO: kinds.PrivateDirectMessage (unavailabe in nostr-tools 10/10/2024 at v2.7.0)
|
||||
const dm: UnsignedEventWithId = {
|
||||
pubkey: senderPubkey,
|
||||
created_at: unixNow(),
|
||||
kind: 14,
|
||||
tags,
|
||||
content: message
|
||||
}
|
||||
|
||||
// Calculate the hash based on the UnverifiedEvent
|
||||
dm.id = getEventHash(dm)
|
||||
|
||||
// Encrypt the private dm using the sender secret and the receiver's public key
|
||||
const encryptedDm = nip44Encrypt(dm, senderSecret, receiver)
|
||||
if (!encryptedDm) {
|
||||
throw new SendDMError(SendDMErrorType.ENCRYPTION_FAILED, {
|
||||
context: {
|
||||
receiver,
|
||||
message,
|
||||
kind: dm.kind
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Seal the message
|
||||
// TODO: kinds.Seal (unavailabe in nostr-tools 10/10/2024 at v2.7.0)
|
||||
const sealedMessage: UnsignedEvent = {
|
||||
kind: 13, // seal
|
||||
pubkey: senderPubkey,
|
||||
content: encryptedDm,
|
||||
created_at: randomTimeUpTo2DaysInThePast(),
|
||||
tags: [] // no tags
|
||||
}
|
||||
|
||||
// Finalize and sign the sealed event
|
||||
const finalizedSeal = finalizeEvent(sealedMessage, senderSecret)
|
||||
|
||||
// Encrypt the seal and gift wrap
|
||||
const finalizedGiftWrap = createWrap(finalizedSeal, receiver)
|
||||
|
||||
const ndkEvent = new NDKEvent(ndk, finalizedGiftWrap)
|
||||
|
||||
// Publish the finalized gift wrap event (the encrypted DM) to the relays
|
||||
const publishedOnRelays = await ndkEvent.publish(
|
||||
NDKRelaySet.fromRelayUrls(finalRelaysList, ndk, true)
|
||||
)
|
||||
|
||||
// Handle cases where publishing to the relays failed
|
||||
if (publishedOnRelays.size === 0) {
|
||||
throw new SendDMError(SendDMErrorType.ENCRYPTION_FAILED, {
|
||||
context: {
|
||||
receiver,
|
||||
count: publishedOnRelays.size
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Return true indicating that the DM was successfully sent
|
||||
return true
|
||||
},
|
||||
[fetchEventFromUserRelays, getNDKRelayList, ndk]
|
||||
)
|
||||
|
||||
return {
|
||||
getUsersAppData,
|
||||
subscribeForSigits,
|
||||
updateUsersAppData,
|
||||
sendNotification
|
||||
sendNotification,
|
||||
sendPrivateDirectMessage
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ import {
|
||||
uploadToFileStorage,
|
||||
DEFAULT_TOOLBOX,
|
||||
settleAllFullfilfedPromises,
|
||||
parseNostrEvent,
|
||||
uploadMetaToFileStorage
|
||||
} from '../../utils'
|
||||
import { Container } from '../../components/Container'
|
||||
@ -72,6 +73,7 @@ import { getSigitFile, SigitFile } from '../../utils/file.ts'
|
||||
import { generateTimestamp } from '../../utils/opentimestamps.ts'
|
||||
import { Autocomplete } from '@mui/material'
|
||||
import _, { truncate } from 'lodash'
|
||||
import { SendDMError } from '../../types/errors/SendDMError.ts'
|
||||
import * as React from 'react'
|
||||
import { AvatarIconButton } from '../../components/UserAvatarIconButton'
|
||||
import { NDKUserProfile, NostrEvent } from '@nostr-dev-kit/ndk'
|
||||
@ -86,7 +88,8 @@ export const CreatePage = () => {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const { findMetadata, fetchEventsFromUserRelays } = useNDKContext()
|
||||
const { updateUsersAppData, sendNotification } = useNDK()
|
||||
const { updateUsersAppData, sendNotification, sendPrivateDirectMessage } =
|
||||
useNDK()
|
||||
|
||||
const { uploadedFiles } = location.state || {}
|
||||
const [currentFile, setCurrentFile] = useState<File>()
|
||||
@ -926,7 +929,29 @@ export const CreatePage = () => {
|
||||
toast.error('Failed to publish notifications')
|
||||
})
|
||||
|
||||
const isFirstSigner = signers[0].pubkey === usersPubkey
|
||||
const isFirstSigner =
|
||||
signers.length > 0 && signers[0].pubkey === usersPubkey
|
||||
|
||||
// Don't send notification if creator is next signer
|
||||
if (signers.length > 0 && !isFirstSigner) {
|
||||
// Send DM to the next signer
|
||||
setLoadingSpinnerDesc('Sending DMs')
|
||||
const nextSigner = signers[0].pubkey
|
||||
const createSignatureEvent = parseNostrEvent(meta.createSignature)
|
||||
const { id } = createSignatureEvent
|
||||
try {
|
||||
await sendPrivateDirectMessage(
|
||||
`Sigit created, visit ${window.location.origin}/#/sign/${id}`,
|
||||
npubToHex(nextSigner)!
|
||||
)
|
||||
} catch (error) {
|
||||
if (error instanceof SendDMError) {
|
||||
toast.error(error.message)
|
||||
}
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (isFirstSigner) {
|
||||
navigate(appPrivateRoutes.sign, { state: { meta } })
|
||||
} else {
|
||||
|
33
src/pages/settings/cache/index.tsx
vendored
33
src/pages/settings/cache/index.tsx
vendored
@ -1,4 +1,3 @@
|
||||
import ClearIcon from '@mui/icons-material/Clear'
|
||||
import InputIcon from '@mui/icons-material/Input'
|
||||
import IosShareIcon from '@mui/icons-material/IosShare'
|
||||
import {
|
||||
@ -9,36 +8,12 @@ import {
|
||||
ListSubheader,
|
||||
useTheme
|
||||
} from '@mui/material'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { localCache } from '../../../services'
|
||||
import { LoadingSpinner } from '../../../components/LoadingSpinner'
|
||||
import { Container } from '../../../components/Container'
|
||||
import { Footer } from '../../../components/Footer/Footer'
|
||||
|
||||
export const CacheSettingsPage = () => {
|
||||
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 (
|
||||
<ListItemText
|
||||
@ -53,7 +28,6 @@ export const CacheSettingsPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Container>
|
||||
{isLoading && <LoadingSpinner desc={loadingSpinnerDesc} />}
|
||||
<List
|
||||
sx={{
|
||||
width: '100%',
|
||||
@ -87,13 +61,6 @@ export const CacheSettingsPage = () => {
|
||||
</ListItemIcon>
|
||||
{listItem('Import (coming soon)')}
|
||||
</ListItemButton>
|
||||
|
||||
<ListItemButton onClick={handleClearData}>
|
||||
<ListItemIcon>
|
||||
<ClearIcon sx={{ color: theme.palette.error.main }} />
|
||||
</ListItemIcon>
|
||||
{listItem('Clear Cache')}
|
||||
</ListItemButton>
|
||||
</List>
|
||||
</Container>
|
||||
<Footer />
|
||||
|
@ -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,65 @@ 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 = signers[currentSignerIndex + 1]
|
||||
if (nextSigner !== submittedBy) {
|
||||
try {
|
||||
await sendPrivateDirectMessage(
|
||||
`You're the next signer, visit ${window.location.origin}/#/sign/${id}`,
|
||||
npubToHex(nextSigner)!
|
||||
)
|
||||
} catch (error) {
|
||||
if (error instanceof SendDMError) {
|
||||
toast.error(error.message)
|
||||
}
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
|
86
src/services/cache/index.ts
vendored
86
src/services/cache/index.ts
vendored
@ -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<SchemaV2>
|
||||
|
||||
// Private constructor to prevent direct instantiation
|
||||
private constructor() {}
|
||||
|
||||
// Method to initialize the database
|
||||
private async init() {
|
||||
this.db = await openDB<SchemaV2>('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<SchemaV2>
|
||||
|
||||
v6.createObjectStore('userRelayListMetadata', {
|
||||
keyPath: 'event.pubkey'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Static method to get the single instance of LocalCache
|
||||
public static async getInstance(): Promise<LocalCache> {
|
||||
// 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<CachedEvent | null> {
|
||||
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<CachedEvent | null> {
|
||||
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()
|
16
src/services/cache/schema.ts
vendored
16
src/services/cache/schema.ts
vendored
@ -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
|
||||
}
|
||||
}
|
@ -1,2 +1 @@
|
||||
export * from './cache'
|
||||
export * from './signer'
|
||||
|
23
src/types/errors/SendDMError.ts
Normal file
23
src/types/errors/SendDMError.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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],
|
||||
|
@ -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.
|
||||
|
@ -2,7 +2,6 @@ import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||
import zipPack from 'vite-plugin-zip-pack'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
@ -10,8 +9,7 @@ export default defineConfig({
|
||||
tsconfigPaths(),
|
||||
nodePolyfills({
|
||||
include: ['os']
|
||||
}),
|
||||
zipPack()
|
||||
})
|
||||
],
|
||||
build: {
|
||||
target: 'ES2022'
|
||||
|
Loading…
x
Reference in New Issue
Block a user